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,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)
|