guppylang-internals 0.21.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. guppylang_internals/__init__.py +3 -0
  2. guppylang_internals/ast_util.py +350 -0
  3. guppylang_internals/cfg/__init__.py +0 -0
  4. guppylang_internals/cfg/analysis.py +230 -0
  5. guppylang_internals/cfg/bb.py +221 -0
  6. guppylang_internals/cfg/builder.py +606 -0
  7. guppylang_internals/cfg/cfg.py +117 -0
  8. guppylang_internals/checker/__init__.py +0 -0
  9. guppylang_internals/checker/cfg_checker.py +388 -0
  10. guppylang_internals/checker/core.py +550 -0
  11. guppylang_internals/checker/errors/__init__.py +0 -0
  12. guppylang_internals/checker/errors/comptime_errors.py +106 -0
  13. guppylang_internals/checker/errors/generic.py +45 -0
  14. guppylang_internals/checker/errors/linearity.py +300 -0
  15. guppylang_internals/checker/errors/type_errors.py +344 -0
  16. guppylang_internals/checker/errors/wasm.py +34 -0
  17. guppylang_internals/checker/expr_checker.py +1413 -0
  18. guppylang_internals/checker/func_checker.py +269 -0
  19. guppylang_internals/checker/linearity_checker.py +821 -0
  20. guppylang_internals/checker/stmt_checker.py +447 -0
  21. guppylang_internals/compiler/__init__.py +0 -0
  22. guppylang_internals/compiler/cfg_compiler.py +233 -0
  23. guppylang_internals/compiler/core.py +613 -0
  24. guppylang_internals/compiler/expr_compiler.py +989 -0
  25. guppylang_internals/compiler/func_compiler.py +97 -0
  26. guppylang_internals/compiler/hugr_extension.py +224 -0
  27. guppylang_internals/compiler/qtm_platform_extension.py +0 -0
  28. guppylang_internals/compiler/stmt_compiler.py +212 -0
  29. guppylang_internals/decorator.py +246 -0
  30. guppylang_internals/definition/__init__.py +0 -0
  31. guppylang_internals/definition/common.py +214 -0
  32. guppylang_internals/definition/const.py +74 -0
  33. guppylang_internals/definition/custom.py +492 -0
  34. guppylang_internals/definition/declaration.py +171 -0
  35. guppylang_internals/definition/extern.py +89 -0
  36. guppylang_internals/definition/function.py +302 -0
  37. guppylang_internals/definition/overloaded.py +150 -0
  38. guppylang_internals/definition/parameter.py +82 -0
  39. guppylang_internals/definition/pytket_circuits.py +405 -0
  40. guppylang_internals/definition/struct.py +392 -0
  41. guppylang_internals/definition/traced.py +151 -0
  42. guppylang_internals/definition/ty.py +51 -0
  43. guppylang_internals/definition/value.py +115 -0
  44. guppylang_internals/definition/wasm.py +61 -0
  45. guppylang_internals/diagnostic.py +523 -0
  46. guppylang_internals/dummy_decorator.py +76 -0
  47. guppylang_internals/engine.py +295 -0
  48. guppylang_internals/error.py +107 -0
  49. guppylang_internals/experimental.py +92 -0
  50. guppylang_internals/ipython_inspect.py +28 -0
  51. guppylang_internals/nodes.py +427 -0
  52. guppylang_internals/py.typed +0 -0
  53. guppylang_internals/span.py +150 -0
  54. guppylang_internals/std/__init__.py +0 -0
  55. guppylang_internals/std/_internal/__init__.py +0 -0
  56. guppylang_internals/std/_internal/checker.py +573 -0
  57. guppylang_internals/std/_internal/compiler/__init__.py +0 -0
  58. guppylang_internals/std/_internal/compiler/arithmetic.py +136 -0
  59. guppylang_internals/std/_internal/compiler/array.py +569 -0
  60. guppylang_internals/std/_internal/compiler/either.py +131 -0
  61. guppylang_internals/std/_internal/compiler/frozenarray.py +68 -0
  62. guppylang_internals/std/_internal/compiler/futures.py +30 -0
  63. guppylang_internals/std/_internal/compiler/list.py +348 -0
  64. guppylang_internals/std/_internal/compiler/mem.py +13 -0
  65. guppylang_internals/std/_internal/compiler/option.py +78 -0
  66. guppylang_internals/std/_internal/compiler/prelude.py +271 -0
  67. guppylang_internals/std/_internal/compiler/qsystem.py +48 -0
  68. guppylang_internals/std/_internal/compiler/quantum.py +118 -0
  69. guppylang_internals/std/_internal/compiler/tket_bool.py +55 -0
  70. guppylang_internals/std/_internal/compiler/tket_exts.py +59 -0
  71. guppylang_internals/std/_internal/compiler/wasm.py +135 -0
  72. guppylang_internals/std/_internal/compiler.py +0 -0
  73. guppylang_internals/std/_internal/debug.py +95 -0
  74. guppylang_internals/std/_internal/util.py +271 -0
  75. guppylang_internals/tracing/__init__.py +0 -0
  76. guppylang_internals/tracing/builtins_mock.py +62 -0
  77. guppylang_internals/tracing/frozenlist.py +57 -0
  78. guppylang_internals/tracing/function.py +186 -0
  79. guppylang_internals/tracing/object.py +551 -0
  80. guppylang_internals/tracing/state.py +69 -0
  81. guppylang_internals/tracing/unpacking.py +194 -0
  82. guppylang_internals/tracing/util.py +86 -0
  83. guppylang_internals/tys/__init__.py +0 -0
  84. guppylang_internals/tys/arg.py +115 -0
  85. guppylang_internals/tys/builtin.py +382 -0
  86. guppylang_internals/tys/common.py +110 -0
  87. guppylang_internals/tys/const.py +114 -0
  88. guppylang_internals/tys/errors.py +178 -0
  89. guppylang_internals/tys/param.py +251 -0
  90. guppylang_internals/tys/parsing.py +425 -0
  91. guppylang_internals/tys/printing.py +174 -0
  92. guppylang_internals/tys/subst.py +112 -0
  93. guppylang_internals/tys/ty.py +876 -0
  94. guppylang_internals/tys/var.py +49 -0
  95. guppylang_internals-0.21.0.dist-info/METADATA +253 -0
  96. guppylang_internals-0.21.0.dist-info/RECORD +98 -0
  97. guppylang_internals-0.21.0.dist-info/WHEEL +4 -0
  98. guppylang_internals-0.21.0.dist-info/licenses/LICENCE +201 -0
@@ -0,0 +1,550 @@
1
+ import ast
2
+ import copy
3
+ import itertools
4
+ from collections.abc import Iterable, Iterator
5
+ from dataclasses import dataclass, field, replace
6
+ from functools import cache, cached_property
7
+ from types import FrameType
8
+ from typing import (
9
+ TYPE_CHECKING,
10
+ Any,
11
+ Generic,
12
+ NamedTuple,
13
+ TypeAlias,
14
+ TypeVar,
15
+ cast,
16
+ overload,
17
+ )
18
+
19
+ from typing_extensions import assert_never
20
+
21
+ from guppylang_internals.ast_util import AstNode, name_nodes_in_ast
22
+ from guppylang_internals.cfg.bb import VId
23
+ from guppylang_internals.definition.common import (
24
+ DefId,
25
+ Definition,
26
+ ParsedDef,
27
+ )
28
+ from guppylang_internals.definition.ty import TypeDef
29
+ from guppylang_internals.definition.value import CallableDef
30
+ from guppylang_internals.engine import BUILTIN_DEFS, DEF_STORE, ENGINE
31
+ from guppylang_internals.error import InternalGuppyError
32
+ from guppylang_internals.tys.builtin import (
33
+ callable_type_def,
34
+ float_type_def,
35
+ int_type_def,
36
+ nat_type_def,
37
+ none_type_def,
38
+ tuple_type_def,
39
+ )
40
+ from guppylang_internals.tys.param import Parameter
41
+ from guppylang_internals.tys.ty import (
42
+ BoundTypeVar,
43
+ ExistentialTypeVar,
44
+ FunctionType,
45
+ InputFlags,
46
+ NoneType,
47
+ NumericType,
48
+ OpaqueType,
49
+ StructType,
50
+ SumType,
51
+ TupleType,
52
+ Type,
53
+ )
54
+
55
+ if TYPE_CHECKING:
56
+ from guppylang_internals.definition.struct import StructField
57
+
58
+
59
+ #: A "place" is a description for a storage location of a local value that users
60
+ #: can refer to in their program.
61
+ #:
62
+ #: Roughly, these are values that can be lowered to a static wire within the Hugr
63
+ #: representation. The most basic example of a place is a single local variable. Beyond
64
+ #: that, we also treat some projections of local variables (e.g. nested struct field
65
+ #: accesses) as places.
66
+ #:
67
+ #: All places are equipped with a unique id, a type and an optional definition AST
68
+ #: location. During linearity checking, they are tracked separately.
69
+ Place: TypeAlias = "Variable | FieldAccess | SubscriptAccess | TupleAccess"
70
+
71
+ #: Unique identifier for a `Place`.
72
+ PlaceId: TypeAlias = (
73
+ "Variable.Id | FieldAccess.Id | SubscriptAccess.Id | TupleAccess.Id"
74
+ )
75
+
76
+
77
+ @dataclass(frozen=True)
78
+ class Variable:
79
+ """A place identifying a local variable."""
80
+
81
+ name: str
82
+ ty: Type
83
+ defined_at: AstNode | None
84
+ flags: InputFlags = InputFlags.NoFlags
85
+
86
+ # Remember if a variable is a function input. This way, we can e.g. suggest to add a
87
+ # `@comptime` annotation to users when appropriate
88
+ is_func_input: bool = field(default=False, kw_only=True)
89
+
90
+ @dataclass(frozen=True)
91
+ class Id:
92
+ """Identifier for variable places."""
93
+
94
+ name: str
95
+
96
+ @cached_property
97
+ def id(self) -> "Variable.Id":
98
+ """The unique `PlaceId` identifier for this place."""
99
+ return Variable.Id(self.name)
100
+
101
+ @cached_property
102
+ def root(self) -> "Variable":
103
+ """The root variable of this place."""
104
+ return self
105
+
106
+ @property
107
+ def describe(self) -> str:
108
+ """A human-readable description of this place for error messages."""
109
+ return f"Variable `{self}`"
110
+
111
+ def __str__(self) -> str:
112
+ """String representation of this place."""
113
+ return self.name
114
+
115
+ def replace_defined_at(self, node: AstNode | None) -> "Variable":
116
+ """Returns a new `Variable` instance with an updated definition location."""
117
+ return replace(self, defined_at=node)
118
+
119
+
120
+ @dataclass(frozen=True, kw_only=True)
121
+ class ComptimeVariable(Variable):
122
+ """A place identifying a variable that is passed from a compile-time context."""
123
+
124
+ # Store the static value of the variable.
125
+ static_value: Any
126
+
127
+
128
+ @dataclass(frozen=True)
129
+ class FieldAccess:
130
+ """A place identifying a field access on a local struct."""
131
+
132
+ parent: Place
133
+ field: "StructField"
134
+ exact_defined_at: AstNode | None
135
+
136
+ @dataclass(frozen=True)
137
+ class Id:
138
+ """Identifier for field places."""
139
+
140
+ parent: PlaceId
141
+ field: str
142
+
143
+ def __post_init__(self) -> None:
144
+ # Check that the field access is consistent
145
+ assert self.struct_ty.field_dict[self.field.name] == self.field
146
+
147
+ @cached_property
148
+ def id(self) -> "FieldAccess.Id":
149
+ """The unique `PlaceId` identifier for this place."""
150
+ return FieldAccess.Id(self.parent.id, self.field.name)
151
+
152
+ @cached_property
153
+ def root(self) -> "Variable":
154
+ """The root variable of this place."""
155
+ return self.parent.root
156
+
157
+ @property
158
+ def ty(self) -> Type:
159
+ """The type of this place."""
160
+ return self.field.ty
161
+
162
+ @cached_property
163
+ def struct_ty(self) -> StructType:
164
+ """The type of the struct whose field is accessed."""
165
+ assert isinstance(self.parent.ty, StructType)
166
+ return self.parent.ty
167
+
168
+ @cached_property
169
+ def defined_at(self) -> AstNode | None:
170
+ """Optional location where this place was last assigned to."""
171
+ return self.exact_defined_at or self.parent.defined_at
172
+
173
+ @cached_property
174
+ def describe(self) -> str:
175
+ """A human-readable description of this place for error messages."""
176
+ return f"Field `{self}`"
177
+
178
+ def __str__(self) -> str:
179
+ """String representation of this place."""
180
+ return f"{self.parent}.{self.field.name}"
181
+
182
+ def replace_defined_at(self, node: AstNode | None) -> "FieldAccess":
183
+ """Returns a new `FieldAccess` instance with an updated definition location."""
184
+ return replace(self, exact_defined_at=node)
185
+
186
+
187
+ class SetitemCall(NamedTuple):
188
+ """
189
+ Represents a `__setitem__` call for assigning values to array elements.
190
+
191
+ Holds the expression corresponding to the `__setitem__` call and the variable
192
+ holding the value to be written to the subscript.
193
+ """
194
+
195
+ #: Expression corresponding to the `__setitem__` call.
196
+ call: ast.expr
197
+
198
+ #: Variable holding the value that should be written to the subscript.
199
+ #: This variable *must be* assigned before compiling the call!
200
+ value_var: Variable
201
+
202
+
203
+ @dataclass(frozen=True)
204
+ class SubscriptAccess:
205
+ """A place identifying a subscript `place[item]` access."""
206
+
207
+ parent: Place
208
+ item: Variable
209
+ ty: Type
210
+ item_expr: ast.expr
211
+ getitem_call: ast.expr | None = None
212
+ setitem_call: SetitemCall | None = None
213
+
214
+ @dataclass(frozen=True)
215
+ class Id:
216
+ """Identifier for subscript places."""
217
+
218
+ parent: PlaceId
219
+ item: Variable.Id
220
+
221
+ @cached_property
222
+ def id(self) -> "SubscriptAccess.Id":
223
+ """The unique `PlaceId` identifier for this place."""
224
+ return SubscriptAccess.Id(self.parent.id, self.item.id)
225
+
226
+ @cached_property
227
+ def defined_at(self) -> AstNode | None:
228
+ """Optional location where this place was last assigned to."""
229
+ return self.parent.defined_at
230
+
231
+ @cached_property
232
+ def root(self) -> "Variable":
233
+ """The root variable of this place."""
234
+ return self.parent.root
235
+
236
+ @property
237
+ def describe(self) -> str:
238
+ """A human-readable description of this place for error messages."""
239
+ return f"Subscript `{self}`"
240
+
241
+ def __str__(self) -> str:
242
+ """String representation of this place."""
243
+ return f"{self.parent}[...]"
244
+
245
+
246
+ def contains_subscript(place: Place) -> SubscriptAccess | None:
247
+ """Checks if a place contains a subscript access and returns the rightmost one."""
248
+ while not isinstance(place, Variable):
249
+ if isinstance(place, SubscriptAccess):
250
+ return place
251
+ place = place.parent
252
+ return None
253
+
254
+
255
+ @dataclass(frozen=True)
256
+ class TupleAccess:
257
+ """A place identifying an index access on a tuple."""
258
+
259
+ parent: Place
260
+ elem_ty: Type
261
+ index: int
262
+ exact_defined_at: AstNode | None
263
+
264
+ @dataclass(frozen=True)
265
+ class Id:
266
+ """Identifier for tuple places."""
267
+
268
+ parent: PlaceId
269
+ index: int
270
+
271
+ @cached_property
272
+ def id(self) -> "TupleAccess.Id":
273
+ """The unique `PlaceId` identifier for this place."""
274
+ return TupleAccess.Id(self.parent.id, self.index)
275
+
276
+ @cached_property
277
+ def root(self) -> "Variable":
278
+ """The root variable of this place."""
279
+ return self.parent.root
280
+
281
+ @property
282
+ def ty(self) -> Type:
283
+ """The type of this place."""
284
+ return self.elem_ty
285
+
286
+ @cached_property
287
+ def defined_at(self) -> AstNode | None:
288
+ """Optional location where this place was last assigned to."""
289
+ return self.exact_defined_at or self.parent.defined_at
290
+
291
+ @cached_property
292
+ def describe(self) -> str:
293
+ """A human-readable description of this place for error messages."""
294
+ return f"Tuple index `{self}`"
295
+
296
+ def __str__(self) -> str:
297
+ """String representation of this place."""
298
+ return f"{self.parent}[{self.index}]"
299
+
300
+ def replace_defined_at(self, node: AstNode | None) -> "TupleAccess":
301
+ """Returns a new `TupleAccess` instance with an updated definition location."""
302
+ return replace(self, exact_defined_at=node)
303
+
304
+
305
+ @dataclass(frozen=True)
306
+ class PythonObject:
307
+ """Wrapper around an arbitrary Python object.
308
+
309
+ Used to distinguish between Guppy definitions and other values when looking up
310
+ object from the enclosing Python scope by name.
311
+ """
312
+
313
+ obj: Any
314
+
315
+
316
+ class Globals:
317
+ """Wrapper around the `DEF_STORE` that allows looking-up of definitions by name
318
+ based on which objects are in scope in a stack frame.
319
+
320
+ Additionally, keeps track of which definitions in the store have been used.
321
+ """
322
+
323
+ f_locals: dict[str, Any]
324
+ f_globals: dict[str, Any]
325
+ f_builtins: dict[str, Any]
326
+
327
+ def __init__(self, frame: FrameType | None) -> None:
328
+ if frame is not None:
329
+ self.f_locals = frame.f_locals
330
+ self.f_globals = frame.f_globals
331
+ self.f_builtins = frame.f_builtins
332
+ else:
333
+ self.f_locals = {}
334
+ self.f_globals = {}
335
+ self.f_builtins = {}
336
+
337
+ @staticmethod
338
+ @cache
339
+ def builtin_defs() -> dict[str, Definition]:
340
+ import guppylang.std.builtins
341
+ from guppylang.defs import GuppyDefinition
342
+
343
+ return BUILTIN_DEFS | {
344
+ name: val.wrapped
345
+ for name, val in guppylang.std.builtins.__dict__.items()
346
+ if isinstance(val, GuppyDefinition)
347
+ }
348
+
349
+ def get_instance_func(self, ty: Type | TypeDef, name: str) -> CallableDef | None:
350
+ """Looks up an instance function with a given name for a type.
351
+
352
+ Returns `None` if the name doesn't exist or isn't a function.
353
+ """
354
+ type_defn: TypeDef
355
+ match ty:
356
+ case TypeDef() as type_defn:
357
+ pass
358
+ case BoundTypeVar() | ExistentialTypeVar() | SumType():
359
+ return None
360
+ case NumericType(kind):
361
+ match kind:
362
+ case NumericType.Kind.Nat:
363
+ type_defn = nat_type_def
364
+ case NumericType.Kind.Int:
365
+ type_defn = int_type_def
366
+ case NumericType.Kind.Float:
367
+ type_defn = float_type_def
368
+ case kind:
369
+ return assert_never(kind)
370
+ case FunctionType():
371
+ type_defn = callable_type_def
372
+ case OpaqueType() as ty:
373
+ type_defn = ty.defn
374
+ case StructType() as ty:
375
+ type_defn = ty.defn
376
+ case TupleType():
377
+ type_defn = tuple_type_def
378
+ case NoneType():
379
+ type_defn = none_type_def
380
+ case _:
381
+ return assert_never(ty)
382
+
383
+ type_defn = cast(TypeDef, ENGINE.get_checked(type_defn.id))
384
+ if type_defn.id in DEF_STORE.impls and name in DEF_STORE.impls[type_defn.id]:
385
+ def_id = DEF_STORE.impls[type_defn.id][name]
386
+ defn = ENGINE.get_parsed(def_id)
387
+ if isinstance(defn, CallableDef):
388
+ return defn
389
+ return None
390
+
391
+ def __contains__(self, item: DefId | str) -> bool:
392
+ match item:
393
+ case DefId() as def_id:
394
+ return def_id in DEF_STORE.raw_defs
395
+ case str(x):
396
+ return (
397
+ x in self.builtin_defs()
398
+ or x in self.f_locals
399
+ or x in self.f_globals
400
+ )
401
+ case x:
402
+ return assert_never(x)
403
+
404
+ @overload
405
+ def __getitem__(self, item: DefId) -> ParsedDef: ...
406
+
407
+ @overload
408
+ def __getitem__(self, item: str) -> "ParsedDef | PythonObject": ...
409
+
410
+ def __getitem__(self, item: DefId | str) -> "ParsedDef | PythonObject":
411
+ from guppylang.defs import GuppyDefinition
412
+
413
+ match item:
414
+ case DefId() as def_id:
415
+ return ENGINE.get_parsed(def_id)
416
+ case str(name):
417
+ if name in self.f_locals:
418
+ val = self.f_locals[name]
419
+ if isinstance(val, GuppyDefinition):
420
+ return ENGINE.get_parsed(val.id)
421
+ # Before falling back to returning the Python object, check if we
422
+ # have defined the name as a builtin
423
+ elif name in self.builtin_defs():
424
+ defn = self.builtin_defs()[name]
425
+ return ENGINE.get_parsed(defn.id)
426
+ else:
427
+ return PythonObject(val)
428
+ elif name in self.f_globals:
429
+ val = self.f_globals[name]
430
+ if isinstance(val, GuppyDefinition):
431
+ return ENGINE.get_parsed(val.id)
432
+ # Before falling back to returning the Python object, check if we
433
+ # have defined the name as a builtin
434
+ elif name in self.builtin_defs():
435
+ defn = self.builtin_defs()[name]
436
+ return ENGINE.get_parsed(defn.id)
437
+ else:
438
+ return PythonObject(val)
439
+ elif name in self.builtin_defs():
440
+ defn = self.builtin_defs()[name]
441
+ return ENGINE.get_parsed(defn.id)
442
+ else:
443
+ raise InternalGuppyError(f"Cannot find definition `{name}`")
444
+ case x:
445
+ return assert_never(x)
446
+
447
+
448
+ V = TypeVar("V")
449
+
450
+
451
+ @dataclass
452
+ class Locals(Generic[VId, V]):
453
+ """Scoped mapping from program variable ids to the corresponding program variable.
454
+
455
+ Depending on which checking phase we are in (type checking or linearity checking),
456
+ we use this either as a mapping from strings to `Variable`s or as a mapping from
457
+ `PlaceId`s to `Place`s.
458
+ """
459
+
460
+ vars: dict[VId, V]
461
+ parent_scope: "Locals[VId, V] | None" = None
462
+
463
+ def __getitem__(self, item: VId) -> V:
464
+ if item not in self.vars and self.parent_scope:
465
+ return self.parent_scope[item]
466
+
467
+ return self.vars[item]
468
+
469
+ def __setitem__(self, key: VId, value: V) -> None:
470
+ self.vars[key] = value
471
+
472
+ def __iter__(self) -> Iterator[VId]:
473
+ parent_iter = iter(self.parent_scope) if self.parent_scope else iter(())
474
+ return itertools.chain(iter(self.vars), parent_iter)
475
+
476
+ def __contains__(self, item: VId) -> bool:
477
+ return (item in self.vars) or (
478
+ self.parent_scope is not None and item in self.parent_scope
479
+ )
480
+
481
+ def __copy__(self) -> "Locals[VId, V]":
482
+ # Make a copy of the var map so that mutating the copy doesn't
483
+ # mutate our variable mapping
484
+ return Locals(self.vars.copy(), copy.copy(self.parent_scope))
485
+
486
+ def keys(self) -> set[VId]:
487
+ parent_keys = self.parent_scope.keys() if self.parent_scope else set()
488
+ return parent_keys | self.vars.keys()
489
+
490
+ def values(self) -> Iterable[V]:
491
+ parent_values = (
492
+ iter(self.parent_scope.values()) if self.parent_scope else iter(())
493
+ )
494
+ return itertools.chain(self.vars.values(), parent_values)
495
+
496
+ def items(self) -> Iterable[tuple[VId, V]]:
497
+ parent_items = (
498
+ iter(self.parent_scope.items()) if self.parent_scope else iter(())
499
+ )
500
+ return itertools.chain(self.vars.items(), parent_items)
501
+
502
+
503
+ class Context(NamedTuple):
504
+ """The type checking context."""
505
+
506
+ globals: Globals
507
+ locals: Locals[str, Variable]
508
+ generic_params: dict[str, Parameter]
509
+
510
+
511
+ class DummyEvalDict(dict[str, Any]):
512
+ """A custom dict that can be passed to `eval` to give better error messages.
513
+ This class is used to implement the `py(...)` expression. If the user tries to
514
+ access a Guppy variable in the Python context, we give an informative error message.
515
+ """
516
+
517
+ ctx: Context
518
+ node: ast.expr
519
+
520
+ @dataclass
521
+ class GuppyVarUsedError(BaseException):
522
+ """Error that is raised when the user tries to access a Guppy variable."""
523
+
524
+ var: str
525
+ node: ast.Name | None
526
+
527
+ def __init__(self, ctx: Context, node: ast.expr):
528
+ super().__init__(**(ctx.globals.f_globals | ctx.globals.f_locals))
529
+ self.ctx = ctx
530
+ self.node = node
531
+
532
+ def _check_item(self, key: str) -> None:
533
+ # Catch the user trying to access Guppy variables
534
+ if key in self.ctx.locals:
535
+ # Find the name node in the AST where the usage occurs
536
+ n = next((n for n in name_nodes_in_ast(self.node) if n.id == key), None)
537
+ raise self.GuppyVarUsedError(key, n)
538
+
539
+ def __getitem__(self, key: str) -> Any:
540
+ self._check_item(key)
541
+ return super().__getitem__(key)
542
+
543
+ def __delitem__(self, key: str) -> None:
544
+ self._check_item(key)
545
+ super().__delitem__(key)
546
+
547
+ def __contains__(self, key: object) -> bool:
548
+ if isinstance(key, str) and key in self.ctx.locals:
549
+ return True
550
+ return super().__contains__(key)
File without changes
@@ -0,0 +1,106 @@
1
+ from dataclasses import dataclass
2
+ from typing import ClassVar
3
+
4
+ from guppylang_internals.checker.core import Place
5
+ from guppylang_internals.diagnostic import Error, Help, Note
6
+ from guppylang_internals.tys.ty import FunctionType
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class IllegalComptimeExpressionError(Error):
11
+ title: ClassVar[str] = "Unsupported Python expression"
12
+ span_label: ClassVar[str] = "Expression of type `{python_ty}` is not supported"
13
+ python_ty: type
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class ComptimeExprNotCPythonError(Error):
18
+ title: ClassVar[str] = "Not running CPython"
19
+ span_label: ClassVar[str] = "Comptime expressions are only supported in CPython"
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class ComptimeExprNotStaticError(Error):
24
+ title: ClassVar[str] = "Not compile-time evaluatable"
25
+ span_label: ClassVar[str] = (
26
+ "Guppy variable `{guppy_var}` cannot be accessed in a comptime expression"
27
+ )
28
+ guppy_var: str
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class ComptimeExprEvalError(Error):
33
+ title: ClassVar[str] = "Python error"
34
+ span_label: ClassVar[str] = (
35
+ "Error occurred while evaluating this comptime expression"
36
+ )
37
+ message: ClassVar[str] = "Traceback printed below:\n\n{err}"
38
+ err: str
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class ComptimeExprIncoherentListError(Error):
43
+ title: ClassVar[str] = "Unsupported list"
44
+ span_label: ClassVar[str] = "List contains elements with different types"
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class TketNotInstalled(Error):
49
+ title: ClassVar[str] = "Tket not installed"
50
+ span_label: ClassVar[str] = (
51
+ "Experimental pytket compatibility requires `tket` to be installed"
52
+ )
53
+
54
+ @dataclass(frozen=True)
55
+ class InstallInstruction(Help):
56
+ message: ClassVar[str] = "Install tket: `pip install tket`"
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class PytketSignatureMismatch(Error):
61
+ title: ClassVar[str] = "Signature mismatch"
62
+ span_label: ClassVar[str] = "Signature `{name}` doesn't match provided circuit"
63
+ name: str
64
+
65
+ @dataclass(frozen=True)
66
+ class TypeHint(Note):
67
+ message: ClassVar[str] = "Expected circuit signature is `{circ_sig}`"
68
+ circ_sig: FunctionType
69
+
70
+
71
+ @dataclass(frozen=True)
72
+ class ComptimeUnknownError(Error):
73
+ title: ClassVar[str] = "Not known at compile-time"
74
+ span_label: ClassVar[str] = "Value of this {thing} must be known at compile-time"
75
+ thing: str
76
+
77
+ @dataclass(frozen=True)
78
+ class InputHint(Help):
79
+ span_label: ClassVar[str] = (
80
+ "`{place.root}` is a function argument, so its value is not statically "
81
+ "known. Consider turning `{place.root}` into a comptime argument: "
82
+ "`{place.root}: @comptime`"
83
+ )
84
+ place: Place
85
+
86
+ @dataclass(frozen=True)
87
+ class VariableHint(Note):
88
+ span_label: ClassVar[str] = (
89
+ "`{place.root}` is a dynamic variable, so its value is not statically known"
90
+ )
91
+ place: Place
92
+
93
+ @dataclass(frozen=True)
94
+ class FallbackHint(Note):
95
+ span_label: ClassVar[str] = (
96
+ "This expression involves a dynamic computation, so its value is not "
97
+ "statically known"
98
+ )
99
+
100
+ @dataclass(frozen=True)
101
+ class Feedback(Note):
102
+ message: ClassVar[str] = (
103
+ "We are currently investigating ways to make Guppy's compile-time "
104
+ "reasoning smarter. Please leave your feedback at "
105
+ "https://github.com/CQCL/guppylang/discussions/987"
106
+ )
@@ -0,0 +1,45 @@
1
+ from dataclasses import dataclass
2
+ from typing import ClassVar
3
+
4
+ from guppylang_internals.diagnostic import Error
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class UnsupportedError(Error):
9
+ title: ClassVar[str] = "Unsupported"
10
+ span_label: ClassVar[str] = "{things} {is_are} not supported{extra}"
11
+ things: str
12
+ singular: bool = False
13
+ unsupported_in: str = ""
14
+
15
+ @property
16
+ def is_are(self) -> str:
17
+ return "is" if self.singular else "are"
18
+
19
+ @property
20
+ def extra(self) -> str:
21
+ return f" in {self.unsupported_in}" if self.unsupported_in else ""
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class UnexpectedError(Error):
26
+ title: ClassVar[str] = "Unexpected {things}"
27
+ span_label: ClassVar[str] = "Unexpected {things}{extra}"
28
+ things: str
29
+ unexpected_in: str = ""
30
+
31
+ @property
32
+ def extra(self) -> str:
33
+ return f" in {self.unexpected_in}" if self.unexpected_in else ""
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class ExpectedError(Error):
38
+ title: ClassVar[str] = "Expected {things}"
39
+ span_label: ClassVar[str] = "Expected {things}{extra}"
40
+ things: str
41
+ got: str = ""
42
+
43
+ @property
44
+ def extra(self) -> str:
45
+ return f", got {self.got}" if self.got else ""