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.
- guppylang_internals/__init__.py +3 -0
- guppylang_internals/ast_util.py +350 -0
- guppylang_internals/cfg/__init__.py +0 -0
- guppylang_internals/cfg/analysis.py +230 -0
- guppylang_internals/cfg/bb.py +221 -0
- guppylang_internals/cfg/builder.py +606 -0
- guppylang_internals/cfg/cfg.py +117 -0
- guppylang_internals/checker/__init__.py +0 -0
- guppylang_internals/checker/cfg_checker.py +388 -0
- guppylang_internals/checker/core.py +550 -0
- guppylang_internals/checker/errors/__init__.py +0 -0
- guppylang_internals/checker/errors/comptime_errors.py +106 -0
- guppylang_internals/checker/errors/generic.py +45 -0
- guppylang_internals/checker/errors/linearity.py +300 -0
- guppylang_internals/checker/errors/type_errors.py +344 -0
- guppylang_internals/checker/errors/wasm.py +34 -0
- guppylang_internals/checker/expr_checker.py +1413 -0
- guppylang_internals/checker/func_checker.py +269 -0
- guppylang_internals/checker/linearity_checker.py +821 -0
- guppylang_internals/checker/stmt_checker.py +447 -0
- guppylang_internals/compiler/__init__.py +0 -0
- guppylang_internals/compiler/cfg_compiler.py +233 -0
- guppylang_internals/compiler/core.py +613 -0
- guppylang_internals/compiler/expr_compiler.py +989 -0
- guppylang_internals/compiler/func_compiler.py +97 -0
- guppylang_internals/compiler/hugr_extension.py +224 -0
- guppylang_internals/compiler/qtm_platform_extension.py +0 -0
- guppylang_internals/compiler/stmt_compiler.py +212 -0
- guppylang_internals/decorator.py +246 -0
- guppylang_internals/definition/__init__.py +0 -0
- guppylang_internals/definition/common.py +214 -0
- guppylang_internals/definition/const.py +74 -0
- guppylang_internals/definition/custom.py +492 -0
- guppylang_internals/definition/declaration.py +171 -0
- guppylang_internals/definition/extern.py +89 -0
- guppylang_internals/definition/function.py +302 -0
- guppylang_internals/definition/overloaded.py +150 -0
- guppylang_internals/definition/parameter.py +82 -0
- guppylang_internals/definition/pytket_circuits.py +405 -0
- guppylang_internals/definition/struct.py +392 -0
- guppylang_internals/definition/traced.py +151 -0
- guppylang_internals/definition/ty.py +51 -0
- guppylang_internals/definition/value.py +115 -0
- guppylang_internals/definition/wasm.py +61 -0
- guppylang_internals/diagnostic.py +523 -0
- guppylang_internals/dummy_decorator.py +76 -0
- guppylang_internals/engine.py +295 -0
- guppylang_internals/error.py +107 -0
- guppylang_internals/experimental.py +92 -0
- guppylang_internals/ipython_inspect.py +28 -0
- guppylang_internals/nodes.py +427 -0
- guppylang_internals/py.typed +0 -0
- guppylang_internals/span.py +150 -0
- guppylang_internals/std/__init__.py +0 -0
- guppylang_internals/std/_internal/__init__.py +0 -0
- guppylang_internals/std/_internal/checker.py +573 -0
- guppylang_internals/std/_internal/compiler/__init__.py +0 -0
- guppylang_internals/std/_internal/compiler/arithmetic.py +136 -0
- guppylang_internals/std/_internal/compiler/array.py +569 -0
- guppylang_internals/std/_internal/compiler/either.py +131 -0
- guppylang_internals/std/_internal/compiler/frozenarray.py +68 -0
- guppylang_internals/std/_internal/compiler/futures.py +30 -0
- guppylang_internals/std/_internal/compiler/list.py +348 -0
- guppylang_internals/std/_internal/compiler/mem.py +13 -0
- guppylang_internals/std/_internal/compiler/option.py +78 -0
- guppylang_internals/std/_internal/compiler/prelude.py +271 -0
- guppylang_internals/std/_internal/compiler/qsystem.py +48 -0
- guppylang_internals/std/_internal/compiler/quantum.py +118 -0
- guppylang_internals/std/_internal/compiler/tket_bool.py +55 -0
- guppylang_internals/std/_internal/compiler/tket_exts.py +59 -0
- guppylang_internals/std/_internal/compiler/wasm.py +135 -0
- guppylang_internals/std/_internal/compiler.py +0 -0
- guppylang_internals/std/_internal/debug.py +95 -0
- guppylang_internals/std/_internal/util.py +271 -0
- guppylang_internals/tracing/__init__.py +0 -0
- guppylang_internals/tracing/builtins_mock.py +62 -0
- guppylang_internals/tracing/frozenlist.py +57 -0
- guppylang_internals/tracing/function.py +186 -0
- guppylang_internals/tracing/object.py +551 -0
- guppylang_internals/tracing/state.py +69 -0
- guppylang_internals/tracing/unpacking.py +194 -0
- guppylang_internals/tracing/util.py +86 -0
- guppylang_internals/tys/__init__.py +0 -0
- guppylang_internals/tys/arg.py +115 -0
- guppylang_internals/tys/builtin.py +382 -0
- guppylang_internals/tys/common.py +110 -0
- guppylang_internals/tys/const.py +114 -0
- guppylang_internals/tys/errors.py +178 -0
- guppylang_internals/tys/param.py +251 -0
- guppylang_internals/tys/parsing.py +425 -0
- guppylang_internals/tys/printing.py +174 -0
- guppylang_internals/tys/subst.py +112 -0
- guppylang_internals/tys/ty.py +876 -0
- guppylang_internals/tys/var.py +49 -0
- guppylang_internals-0.21.0.dist-info/METADATA +253 -0
- guppylang_internals-0.21.0.dist-info/RECORD +98 -0
- guppylang_internals-0.21.0.dist-info/WHEEL +4 -0
- 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 ""
|