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,95 @@
1
+ import ast
2
+ from dataclasses import dataclass
3
+ from typing import ClassVar, cast
4
+
5
+ from guppylang_internals.ast_util import with_loc
6
+ from guppylang_internals.checker.errors.generic import ExpectedError
7
+ from guppylang_internals.checker.errors.type_errors import WrongNumberOfArgsError
8
+ from guppylang_internals.checker.expr_checker import (
9
+ ExprChecker,
10
+ ExprSynthesizer,
11
+ synthesize_call,
12
+ )
13
+ from guppylang_internals.definition.custom import CustomCallChecker
14
+ from guppylang_internals.definition.ty import TypeDef
15
+ from guppylang_internals.diagnostic import Error
16
+ from guppylang_internals.error import GuppyTypeError
17
+ from guppylang_internals.nodes import StateResultExpr
18
+ from guppylang_internals.std._internal.checker import TAG_MAX_LEN, TooLongError
19
+ from guppylang_internals.tys.builtin import (
20
+ get_array_length,
21
+ get_element_type,
22
+ is_array_type,
23
+ string_type,
24
+ )
25
+ from guppylang_internals.tys.ty import (
26
+ FuncInput,
27
+ FunctionType,
28
+ InputFlags,
29
+ NoneType,
30
+ Type,
31
+ )
32
+
33
+
34
+ class StateResultChecker(CustomCallChecker):
35
+ """Call checker for the `state_result` function."""
36
+
37
+ @dataclass(frozen=True)
38
+ class MissingQubitsError(Error):
39
+ title: ClassVar[str] = "Missing qubit inputs"
40
+ span_label: ClassVar[str] = (
41
+ "Qubits whose state should be reported must be passed explicitly"
42
+ )
43
+
44
+ def synthesize(self, args: list[ast.expr]) -> tuple[ast.expr, Type]:
45
+ tag, _ = ExprChecker(self.ctx).check(args[0], string_type())
46
+ if not isinstance(tag, ast.Constant) or not isinstance(tag.value, str):
47
+ raise GuppyTypeError(ExpectedError(tag, "a string literal"))
48
+ if len(tag.value.encode("utf-8")) > TAG_MAX_LEN:
49
+ err: Error = TooLongError(tag)
50
+ err.add_sub_diagnostic(TooLongError.Hint(None))
51
+ raise GuppyTypeError(err)
52
+ syn_args: list[ast.expr] = [tag]
53
+
54
+ if len(args) < 2:
55
+ raise GuppyTypeError(self.MissingQubitsError(self.node))
56
+
57
+ from guppylang.defs import GuppyDefinition
58
+ from guppylang.std.quantum import qubit
59
+
60
+ assert isinstance(qubit, GuppyDefinition)
61
+ qubit_ty = cast(TypeDef, qubit.wrapped).check_instantiate([])
62
+
63
+ array_len = None
64
+ arg, ty = ExprSynthesizer(self.ctx).synthesize(args[1])
65
+ if is_array_type(ty):
66
+ if len(args) > 2:
67
+ err = WrongNumberOfArgsError(args[2], 2, len(args))
68
+ raise GuppyTypeError(err)
69
+ element_ty = get_element_type(ty)
70
+ if not element_ty == qubit_ty:
71
+ raise GuppyTypeError(ExpectedError(arg, "an array of qubits"))
72
+ syn_args.append(arg)
73
+ func_ty = FunctionType(
74
+ [
75
+ FuncInput(string_type(), InputFlags.NoFlags),
76
+ FuncInput(ty, InputFlags.Inout),
77
+ ],
78
+ NoneType(),
79
+ )
80
+ array_len = get_array_length(ty)
81
+ else:
82
+ for arg in args[1:]:
83
+ qbt, _ = ExprChecker(self.ctx).check(arg, qubit_ty)
84
+ syn_args.append(qbt)
85
+ func_ty = FunctionType(
86
+ [FuncInput(string_type(), InputFlags.NoFlags)]
87
+ + [FuncInput(qubit_ty, InputFlags.Inout)] * len(args[1:]),
88
+ NoneType(),
89
+ )
90
+ args, ret_ty, inst = synthesize_call(func_ty, syn_args, self.node, self.ctx)
91
+ assert len(inst) == 0, "func_ty is not generic"
92
+ node = StateResultExpr(
93
+ tag=tag.value, args=args, func_ty=func_ty, array_len=array_len
94
+ )
95
+ return with_loc(self.node, node), ret_ty
@@ -0,0 +1,271 @@
1
+ """Utilities for defining builtin functions.
2
+
3
+ Note: These custom definitions will be replaced with direct extension operation
4
+ definitions from the hugr library.
5
+ """
6
+
7
+ from collections.abc import Callable
8
+ from typing import TYPE_CHECKING
9
+
10
+ import hugr.std.collections.list
11
+ import hugr.std.float
12
+ import hugr.std.int
13
+ import hugr.std.logic
14
+ from hugr import ext as he
15
+ from hugr import ops
16
+ from hugr import tys as ht
17
+
18
+ from guppylang_internals.compiler.hugr_extension import UnsupportedOp
19
+ from guppylang_internals.std._internal.compiler.tket_bool import OpaqueBool
20
+ from guppylang_internals.std._internal.compiler.tket_exts import (
21
+ BOOL_EXTENSION,
22
+ QUANTUM_EXTENSION,
23
+ )
24
+ from guppylang_internals.tys.common import ToHugrContext
25
+ from guppylang_internals.tys.subst import Inst
26
+ from guppylang_internals.tys.ty import NumericType
27
+
28
+ if TYPE_CHECKING:
29
+ from guppylang_internals.tys.arg import Argument
30
+
31
+
32
+ def int_arg(n: int = NumericType.INT_WIDTH) -> ht.TypeArg:
33
+ """A bounded int type argument."""
34
+ return ht.BoundedNatArg(n=n)
35
+
36
+
37
+ def type_arg(idx: int = 0, bound: ht.TypeBound = ht.TypeBound.Linear) -> ht.TypeArg:
38
+ """A generic type argument."""
39
+ return ht.VariableArg(idx=idx, param=ht.TypeTypeParam(bound=bound))
40
+
41
+
42
+ def make_concrete_arg(
43
+ arg: ht.TypeArg,
44
+ inst: Inst,
45
+ ctx: ToHugrContext,
46
+ variable_remap: dict[int, int] | None = None,
47
+ ) -> ht.TypeArg:
48
+ """Makes a concrete hugr type argument using a guppy instantiation.
49
+
50
+ Args:
51
+ arg: The hugr type argument to make concrete, containing variable arguments.
52
+ inst: The guppy instantiation of the type arguments.
53
+ ctx: Context for the Guppy to Hugr type conversion.
54
+ variable_remap: A mapping from the hugr param variable indices to
55
+ de Bruijn indices in the guppy type. Defaults to identity.
56
+ """
57
+ remap = variable_remap or {}
58
+
59
+ if isinstance(arg, ht.VariableArg) and remap.get(arg.idx, arg.idx) < len(inst):
60
+ concrete_arg: Argument = inst[remap.get(arg.idx, arg.idx)]
61
+ return concrete_arg.to_hugr(ctx)
62
+ return arg
63
+
64
+
65
+ def external_op(
66
+ name: str,
67
+ args: list[ht.TypeArg],
68
+ ext: he.Extension,
69
+ *,
70
+ variable_remap: dict[int, int] | None = None,
71
+ ) -> Callable[[ht.FunctionType, Inst, ToHugrContext], ops.DataflowOp]:
72
+ """Custom hugr operation
73
+
74
+ Args:
75
+ op_name: The name of the operation.
76
+ args: The type arguments of the operation.
77
+ ext: The extension of the operation.
78
+ variable_remap: A mapping from the hugr param variable indices to
79
+ de Bruijn indices in the guppy type. Defaults to identity.
80
+
81
+ Returns:
82
+ A function that takes an instantiation of the type arguments as well as
83
+ the inferred input and output types and returns a concrete HUGR op.
84
+ """
85
+ op_def = ext.get_op(name)
86
+
87
+ def op(ty: ht.FunctionType, inst: Inst, ctx: ToHugrContext) -> ops.DataflowOp:
88
+ concrete_args = [
89
+ make_concrete_arg(arg, inst, ctx, variable_remap) for arg in args
90
+ ]
91
+ return op_def.instantiate(concrete_args, ty)
92
+
93
+ return op
94
+
95
+
96
+ def float_op(
97
+ op_name: str,
98
+ ext: he.Extension = hugr.std.float.FLOAT_OPS_EXTENSION,
99
+ ) -> Callable[[ht.FunctionType, Inst, ToHugrContext], ops.DataflowOp]:
100
+ """Utility method to create Hugr float arithmetic ops.
101
+
102
+ Args:
103
+ op_name: The name of the operation.
104
+ ext: The extension of the operation.
105
+
106
+ Returns:
107
+ A function that takes an instantiation of the type arguments and returns
108
+ a concrete HUGR op.
109
+ """
110
+ return external_op(op_name, args=[], ext=ext, variable_remap=None)
111
+
112
+
113
+ def int_op(
114
+ op_name: str,
115
+ ext: he.Extension = hugr.std.int.INT_OPS_EXTENSION,
116
+ n_vars: int = 1,
117
+ ) -> Callable[[ht.FunctionType, Inst, ToHugrContext], ops.DataflowOp]:
118
+ """Utility method to create Hugr integer arithmetic ops.
119
+
120
+ Args:
121
+ op_name: The name of the operation.
122
+ ext: The extension of the operation.
123
+ n_vars: The number of type arguments. Defaults to 1.
124
+
125
+ Returns:
126
+ A function that takes an instantiation of the type arguments and returns
127
+ a concrete HUGR op.
128
+ """
129
+ # Ideally we'd be able to derive the arguments from the input/output types,
130
+ # but the amount of variables does not correlate with the signature for the
131
+ # integer ops in hugr :/
132
+ # https://github.com/CQCL/hugr/blob/bfa13e59468feb0fc746677ea3b3a4341b2ed42e/hugr-core/src/std_extensions/arithmetic/int_ops.rs#L116
133
+ #
134
+ # For now, we just instantiate every type argument to a 64-bit integer.
135
+ args: list[ht.TypeArg] = [int_arg() for _ in range(n_vars)]
136
+
137
+ return external_op(
138
+ op_name,
139
+ args=args,
140
+ ext=ext,
141
+ variable_remap=None,
142
+ )
143
+
144
+
145
+ def logic_op(
146
+ op_name: str,
147
+ parametric_size: bool = False,
148
+ ext: he.Extension = hugr.std.logic.EXTENSION,
149
+ ) -> Callable[[ht.FunctionType, Inst, ToHugrContext], ops.DataflowOp]:
150
+ """Utility method to create Hugr logic ops.
151
+
152
+ If `parametric_size` is True, the generated operations has a single argument
153
+ encoding the number of boolean inputs to the operation.
154
+
155
+ args:
156
+ op_name: The name of the operation.
157
+ parametric_size: Whether the input count is a parameter to the operation.
158
+ ext: The extension of the operation.
159
+
160
+ Returns:
161
+ A function that takes an instantiation of the type arguments and returns
162
+ a concrete HUGR op.
163
+ """
164
+ op_def = ext.get_op(op_name)
165
+
166
+ def op(ty: ht.FunctionType, inst: Inst, ctx: ToHugrContext) -> ops.DataflowOp:
167
+ args = [int_arg(len(ty.input))] if parametric_size else []
168
+ return ops.ExtOp(
169
+ op_def,
170
+ ty,
171
+ args,
172
+ )
173
+
174
+ return op
175
+
176
+
177
+ def list_op(
178
+ op_name: str,
179
+ ext: he.Extension = hugr.std.collections.list.EXTENSION,
180
+ ) -> Callable[[ht.FunctionType, Inst, ToHugrContext], ops.DataflowOp]:
181
+ """Utility method to create Hugr list ops.
182
+
183
+ Args:
184
+ op_name: The name of the operation.
185
+ ext: The extension of the operation.
186
+
187
+ Returns:
188
+ A function that takes an instantiation of the type arguments and returns
189
+ a concrete HUGR op.
190
+ """
191
+ op_def = ext.get_op(op_name)
192
+
193
+ def op(ty: ht.FunctionType, inst: Inst, ctx: ToHugrContext) -> ops.DataflowOp:
194
+ return ops.ExtOp(
195
+ op_def,
196
+ ty,
197
+ [arg.to_hugr(ctx) for arg in inst],
198
+ )
199
+
200
+ return op
201
+
202
+
203
+ def quantum_op(
204
+ op_name: str,
205
+ ext: he.Extension = QUANTUM_EXTENSION,
206
+ ) -> Callable[[ht.FunctionType, Inst, ToHugrContext], ops.DataflowOp]:
207
+ """Utility method to create Hugr quantum ops.
208
+
209
+ args:
210
+ op_name: The name of the operation.
211
+ ext: The extension of the operation.
212
+
213
+ Returns:
214
+ A function that takes an instantiation of the type arguments and returns
215
+ a concrete HUGR op.
216
+ """
217
+ op_def = ext.get_op(op_name)
218
+
219
+ def op(ty: ht.FunctionType, inst: Inst, ctx: ToHugrContext) -> ops.DataflowOp:
220
+ return ops.ExtOp(
221
+ op_def,
222
+ ty,
223
+ args=[],
224
+ )
225
+
226
+ return op
227
+
228
+
229
+ def unsupported_op(
230
+ op_name: str,
231
+ ) -> Callable[[ht.FunctionType, Inst, ToHugrContext], ops.DataflowOp]:
232
+ """Utility method to define not-yet-implemented operations.
233
+
234
+ Args:
235
+ op_name: The name of the operation to define.
236
+
237
+ Returns:
238
+ A function that takes an instantiation of the type arguments as well as
239
+ the inferred input and output types and returns a concrete HUGR op.
240
+ """
241
+
242
+ def op(ty: ht.FunctionType, inst: Inst, ctx: ToHugrContext) -> ops.DataflowOp:
243
+ return UnsupportedOp(
244
+ op_name=op_name,
245
+ inputs=ty.input,
246
+ outputs=ty.output,
247
+ )
248
+
249
+ return op
250
+
251
+
252
+ def bool_logic_op(
253
+ op_name: str,
254
+ ) -> Callable[[ht.FunctionType, Inst, ToHugrContext], ops.DataflowOp]:
255
+ """Utility method to create binary `tket.bool` logic ops.
256
+
257
+ args:
258
+ op_name: The name of the operation.
259
+
260
+ Returns:
261
+ A function that takes an instantiation of the type arguments and returns
262
+ a concrete HUGR op.
263
+ """
264
+
265
+ def op(ty: ht.FunctionType, inst: Inst, ctx: ToHugrContext) -> ops.DataflowOp:
266
+ return ops.ExtOp(
267
+ BOOL_EXTENSION.get_op(op_name),
268
+ ht.FunctionType([OpaqueBool, OpaqueBool], [OpaqueBool]),
269
+ )
270
+
271
+ return op
File without changes
@@ -0,0 +1,62 @@
1
+ """Mocks for a number of builtin functions to be used during tracing.
2
+
3
+ For example, the builtin `int(x)` function tries calling `x.__int__()` and raises a
4
+ `TypeError` if this call doesn't return an `int`. During tracing however, we also want
5
+ to allow `int(x)` if `x` is an abstract `GuppyObject`. To allow this, we need to mock
6
+ the builtins to avoid raising the `TypeError` in that case.
7
+ """
8
+
9
+ import builtins
10
+ from collections.abc import Callable
11
+ from typing import Any
12
+
13
+ from guppylang_internals.tracing.object import GuppyObject, GuppyStructObject
14
+
15
+
16
+ def _mock_meta(cls: type) -> type:
17
+ """Returns a metaclass that replicates the behaviour of the provided class.
18
+
19
+ The only way to distinguishing a `_mock_meta(T)` from an actual `T` is via checking
20
+ reference equality using the `is` operator.
21
+ """
22
+
23
+ class MockMeta(type):
24
+ def __instancecheck__(self, instance: Any) -> bool:
25
+ return cls.__instancecheck__(instance)
26
+
27
+ def __subclasscheck__(self, subclass: type) -> bool:
28
+ return cls.__subclasscheck__(subclass)
29
+
30
+ def __eq__(self, other: object) -> Any:
31
+ return other == cls
32
+
33
+ def __ne__(self, other: object) -> Any:
34
+ return other != cls
35
+
36
+ MockMeta.__name__ = type.__name__
37
+ MockMeta.__qualname__ = type.__qualname__
38
+ return MockMeta
39
+
40
+
41
+ class float(builtins.float, metaclass=_mock_meta(builtins.float)): # type: ignore[misc]
42
+ def __new__(cls, x: Any = 0.0, /) -> Any:
43
+ if isinstance(x, GuppyObject):
44
+ return x.__float__()
45
+ return builtins.float(x)
46
+
47
+
48
+ class int(builtins.int, metaclass=_mock_meta(builtins.int)): # type: ignore[misc]
49
+ def __new__(cls, x: Any = 0, /, **kwargs: Any) -> Any:
50
+ if isinstance(x, GuppyObject):
51
+ return x.__int__(**kwargs)
52
+ return builtins.int(x, **kwargs)
53
+
54
+
55
+ def len(x: Any) -> Any:
56
+ if isinstance(x, GuppyObject | GuppyStructObject):
57
+ return x.__len__()
58
+ return builtins.len(x)
59
+
60
+
61
+ def mock_builtins(f: Callable[..., Any]) -> None:
62
+ f.__globals__.update({"float": float, "int": int, "len": len})
@@ -0,0 +1,57 @@
1
+ from typing import Any
2
+
3
+ from typing_extensions import Self
4
+
5
+ from guppylang_internals.error import GuppyComptimeError
6
+
7
+ ERROR_MSG = (
8
+ "This list is an owned function argument. Therefore, this mutation won't be "
9
+ "visible to the caller. Consider calling `copy()` to obtain a mutable local copy "
10
+ "of this list."
11
+ )
12
+
13
+
14
+ class frozenlist(list): # type: ignore[type-arg]
15
+ """An immutable list subclass.
16
+
17
+ Raises a `GuppyComptimeError` for any operation that would mutate the list.
18
+ """
19
+
20
+ def append(self, *args: Any, **kwargs: Any) -> None:
21
+ raise GuppyComptimeError(ERROR_MSG)
22
+
23
+ def copy(self) -> Any:
24
+ return list(self)
25
+
26
+ def clear(self, *args: Any, **kwargs: Any) -> None:
27
+ raise GuppyComptimeError(ERROR_MSG)
28
+
29
+ def extend(self, *args: Any, **kwargs: Any) -> None:
30
+ raise GuppyComptimeError(ERROR_MSG)
31
+
32
+ def insert(self, *args: Any, **kwargs: Any) -> None:
33
+ raise GuppyComptimeError(ERROR_MSG)
34
+
35
+ def pop(self, *args: Any, **kwargs: Any) -> Any:
36
+ raise GuppyComptimeError(ERROR_MSG)
37
+
38
+ def remove(self, *args: Any, **kwargs: Any) -> None:
39
+ raise GuppyComptimeError(ERROR_MSG)
40
+
41
+ def reverse(self, *args: Any, **kwargs: Any) -> None:
42
+ raise GuppyComptimeError(ERROR_MSG)
43
+
44
+ def sort(self, *args: Any, **kwargs: Any) -> None:
45
+ raise GuppyComptimeError(ERROR_MSG)
46
+
47
+ def __delitem__(self, *args: Any, **kwargs: Any) -> None:
48
+ raise GuppyComptimeError(ERROR_MSG)
49
+
50
+ def __iadd__(self, *args: Any, **kwargs: Any) -> Self: # type: ignore[misc]
51
+ raise GuppyComptimeError(ERROR_MSG)
52
+
53
+ def __imul__(self, *args: Any, **kwargs: Any) -> Self: # type: ignore[misc]
54
+ raise GuppyComptimeError(ERROR_MSG)
55
+
56
+ def __setitem__(self, key: Any, value: Any) -> Any:
57
+ raise GuppyComptimeError(ERROR_MSG)
@@ -0,0 +1,186 @@
1
+ from collections.abc import Callable
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING, Any, ClassVar
4
+
5
+ from hugr import ops
6
+ from hugr.build.dfg import DfBase
7
+
8
+ from guppylang_internals.ast_util import AstNode, with_loc, with_type
9
+ from guppylang_internals.cfg.builder import tmp_vars
10
+ from guppylang_internals.checker.core import ComptimeVariable, Context, Locals, Variable
11
+ from guppylang_internals.checker.errors.type_errors import TypeMismatchError
12
+ from guppylang_internals.compiler.core import CompilerContext, DFContainer
13
+ from guppylang_internals.compiler.expr_compiler import ExprCompiler
14
+ from guppylang_internals.definition.value import CallableDef
15
+ from guppylang_internals.diagnostic import Error
16
+ from guppylang_internals.error import GuppyComptimeError, GuppyError, exception_hook
17
+ from guppylang_internals.nodes import PlaceNode
18
+ from guppylang_internals.tracing.builtins_mock import mock_builtins
19
+ from guppylang_internals.tracing.object import GuppyObject
20
+ from guppylang_internals.tracing.state import (
21
+ TracingState,
22
+ get_tracing_state,
23
+ set_tracing_state,
24
+ )
25
+ from guppylang_internals.tracing.unpacking import (
26
+ P,
27
+ guppy_object_from_py,
28
+ unpack_guppy_object,
29
+ update_packed_value,
30
+ )
31
+ from guppylang_internals.tracing.util import capture_guppy_errors, tracing_except_hook
32
+ from guppylang_internals.tys.ty import FunctionType, InputFlags, type_to_row, unify
33
+
34
+ if TYPE_CHECKING:
35
+ import ast
36
+
37
+ from hugr import Wire
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class TracingReturnError(Error):
42
+ title: ClassVar[str] = "Error in comptime function return"
43
+ message: ClassVar[str] = "{msg}"
44
+ msg: str
45
+
46
+
47
+ def trace_function(
48
+ python_func: Callable[..., Any],
49
+ ty: FunctionType,
50
+ builder: DfBase[P],
51
+ ctx: CompilerContext,
52
+ node: AstNode,
53
+ ) -> None:
54
+ """Kicks off tracing of a function.
55
+
56
+ Invokes the passed Python callable and constructs the corresponding Hugr using the
57
+ passed builder.
58
+ """
59
+ state = TracingState(ctx, DFContainer(builder, ctx, {}), node)
60
+ with set_tracing_state(state):
61
+ inputs = [
62
+ unpack_guppy_object(
63
+ GuppyObject(inp.ty, wire),
64
+ builder,
65
+ # Function inputs are only allowed to be mutable if they are borrowed.
66
+ # For owned arguments, mutation wouldn't be observable by the caller,
67
+ # thus breaking the semantics expected from Python.
68
+ frozen=InputFlags.Inout not in inp.flags,
69
+ )
70
+ for wire, inp in zip(builder.inputs(), ty.inputs, strict=True)
71
+ ]
72
+
73
+ with exception_hook(tracing_except_hook):
74
+ mock_builtins(python_func)
75
+ py_out = python_func(*inputs)
76
+
77
+ try:
78
+ out_obj = guppy_object_from_py(py_out, builder, node, ctx)
79
+ except GuppyComptimeError as err:
80
+ # Error in the return statement. For example, this happens if users
81
+ # try to return a struct with invalid field values or there is a linearity
82
+ # violation.
83
+ raise GuppyError(TracingReturnError(node, str(err))) from None
84
+
85
+ # Check that the output type is correct
86
+ if unify(out_obj._ty, ty.output, {}) is None:
87
+ raise GuppyError(
88
+ TypeMismatchError(node, ty.output, out_obj._ty, "return value")
89
+ )
90
+
91
+ # Unpack regular returns
92
+ out_tys = type_to_row(out_obj._ty)
93
+ if len(out_tys) > 1:
94
+ regular_returns: list[Wire] = list(
95
+ builder.add_op(ops.UnpackTuple(), out_obj._use_wire(None)).outputs()
96
+ )
97
+ elif len(out_tys) > 0:
98
+ regular_returns = [out_obj._use_wire(None)]
99
+ else:
100
+ regular_returns = []
101
+
102
+ # Compute the inout extra outputs
103
+ inout_returns = []
104
+ assert ty.input_names is not None
105
+ for inout_obj, inp, name in zip(inputs, ty.inputs, ty.input_names, strict=True):
106
+ if InputFlags.Inout in inp.flags:
107
+ err_prefix = (
108
+ f"Argument `{name}` is borrowed, so it is implicitly returned to "
109
+ f"the caller. "
110
+ )
111
+ try:
112
+ obj = guppy_object_from_py(inout_obj, builder, node, ctx)
113
+ inout_returns.append(obj._use_wire(None))
114
+ except GuppyComptimeError as err:
115
+ msg = str(err)
116
+ if not msg.endswith("."):
117
+ msg += "."
118
+ e = TracingReturnError(node, err_prefix + msg)
119
+ raise GuppyError(e) from None
120
+ # Also check that the type hasn't changed (for example, the user could
121
+ # have changed the length of an array, thus changing its type)
122
+ if obj._ty != inp.ty:
123
+ msg = (
124
+ f"{err_prefix}Expected it to have type `{inp.ty}`, but got "
125
+ f"`{obj._ty}`."
126
+ )
127
+ e = TracingReturnError(node, msg)
128
+ raise GuppyError(e) from None
129
+
130
+ # Check that all allocated linear objects have been used
131
+ if state.unused_undroppable_objs:
132
+ _, unused = state.unused_undroppable_objs.popitem()
133
+ msg = f"Value with non-droppable type `{unused._ty}` is leaked by this function"
134
+ raise GuppyError(TracingReturnError(node, msg)) from None
135
+
136
+ builder.set_outputs(*regular_returns, *inout_returns)
137
+
138
+
139
+ @capture_guppy_errors
140
+ def trace_call(func: CallableDef, *args: Any) -> Any:
141
+ """Handles calls to Guppy functions during tracing.
142
+
143
+ Checks that the passed arguments match the signature of the function and also
144
+ handles inout arguments.
145
+ """
146
+ state = get_tracing_state()
147
+
148
+ # Try to turn args into `GuppyObjects`
149
+ args_objs = [
150
+ guppy_object_from_py(arg, state.dfg.builder, state.node, state.ctx)
151
+ for arg in args
152
+ ]
153
+
154
+ # Create dummy variables and bind the objects to them
155
+ arg_vars: list[Variable] = [
156
+ ComptimeVariable(next(tmp_vars), obj._ty, None, static_value=arg)
157
+ for (obj, arg) in zip(args_objs, args, strict=True)
158
+ ]
159
+ locals = Locals({var.name: var for var in arg_vars})
160
+ for obj, var in zip(args_objs, arg_vars, strict=True):
161
+ state.dfg[var] = obj._use_wire(func)
162
+
163
+ # Check call
164
+ arg_exprs: list[ast.expr] = [
165
+ with_loc(state.node, with_type(var.ty, PlaceNode(var))) for var in arg_vars
166
+ ]
167
+ call_node, ret_ty = func.synthesize_call(
168
+ arg_exprs, state.node, Context(state.globals, locals, {})
169
+ )
170
+
171
+ # Compile call
172
+ ret_wire = ExprCompiler(state.ctx).compile(call_node, state.dfg)
173
+
174
+ # Update inouts
175
+ # If the input types of the function aren't known, we can't check this.
176
+ # This is the case for functions with a custom checker and no type annotations.
177
+ if len(func.ty.inputs) != 0:
178
+ for inp, arg, var in zip(func.ty.inputs, args, arg_vars, strict=True):
179
+ if InputFlags.Inout in inp.flags:
180
+ inout_wire = state.dfg[var]
181
+ update_packed_value(
182
+ arg, GuppyObject(inp.ty, inout_wire), state.dfg.builder
183
+ )
184
+
185
+ ret_obj = GuppyObject(ret_ty, ret_wire)
186
+ return unpack_guppy_object(ret_obj, state.dfg.builder)