diff --git a/guppylang-internals/src/guppylang_internals/compiler/core.py b/guppylang-internals/src/guppylang_internals/compiler/core.py index dac0c7fd4..50e780143 100644 --- a/guppylang-internals/src/guppylang_internals/compiler/core.py +++ b/guppylang-internals/src/guppylang_internals/compiler/core.py @@ -16,6 +16,7 @@ from hugr.hugr.node_port import ToNode from hugr.std import PRELUDE from hugr.std.collections.array import EXTENSION as ARRAY_EXTENSION +from hugr.std.collections.borrow_array import EXTENSION as BORROW_ARRAY_EXTENSION from typing_extensions import assert_never from guppylang_internals.checker.core import ( @@ -247,7 +248,7 @@ def compile(self, defn: CheckedDef) -> CompiledDef: # Insert explicit drops for affine types # TODO: This is a quick workaround until we can properly insert these drops - # during linearity checking. See https://github.com/CQCL/guppylang/issues/1082 + # during linearity checking. See https://github.com/CQCL/guppylang/issues/1082 insert_drops(self.module.hugr) return entry_compiled @@ -651,6 +652,7 @@ def qualified_name(type_def: he.TypeDef) -> str: #: insertion of an explicit drop operation. AFFINE_EXTENSION_TYS: list[str] = [ qualified_name(ARRAY_EXTENSION.get_type("array")), + qualified_name(BORROW_ARRAY_EXTENSION.get_type("borrow_array")), ] @@ -698,7 +700,7 @@ def insert_drops(hugr: Hugr[OpVarCov]) -> None: # Iterating over `node.outputs()` doesn't work reliably since it sometimes # raises an `IncompleteOp` exception. Instead, we query the number of out ports # and look them up by index. However, this method is *also* broken when - # isnpecting `FuncDefn` nodes due to https://github.com/CQCL/hugr/issues/2438. + # inspecting `FuncDefn` nodes due to https://github.com/CQCL/hugr/issues/2438. if isinstance(data.op, ops.FuncDefn): continue for i in range(hugr.num_out_ports(node)): diff --git a/guppylang-internals/src/guppylang_internals/compiler/expr_compiler.py b/guppylang-internals/src/guppylang_internals/compiler/expr_compiler.py index 89352e608..1816e1357 100644 --- a/guppylang-internals/src/guppylang_internals/compiler/expr_compiler.py +++ b/guppylang-internals/src/guppylang_internals/compiler/expr_compiler.py @@ -63,14 +63,17 @@ from guppylang_internals.std._internal.compiler.arithmetic import ( UnsignedIntVal, convert_ifromusize, + convert_itousize, ) from guppylang_internals.std._internal.compiler.array import ( - array_convert_from_std_array, - array_convert_to_std_array, + array_clone, array_map, array_new, - array_repeat, + array_to_std_array, + barray_new_all_borrowed, + barray_return, standard_array_type, + std_array_to_array, unpack_array, ) from guppylang_internals.std._internal.compiler.list import ( @@ -79,7 +82,6 @@ from guppylang_internals.std._internal.compiler.prelude import ( build_error, build_panic, - build_unwrap, panic, ) from guppylang_internals.std._internal.compiler.tket_bool import ( @@ -91,6 +93,7 @@ ) from guppylang_internals.tys.arg import ConstArg from guppylang_internals.tys.builtin import ( + array_type, bool_type, get_element_type, int_type, @@ -556,15 +559,22 @@ def visit_ResultExpr(self, node: ResultExpr) -> Wire: op_name = f"result_array_{base_name}" size_arg = node.array_len.to_arg().to_hugr(self.ctx) extra_args = [size_arg, *extra_args] - # Remove the option wrapping in the array - unwrap = array_unwrap_elem(self.ctx) - unwrap = self.builder.load_function( - unwrap, - instantiation=ht.FunctionType([ht.Option(base_ty)], [base_ty]), - type_args=[ht.TypeTypeArg(base_ty)], + # As `borrow_array`s used by Guppy are linear, we need to clone it (knowing + # that all elements in it are copyable) to avoid linearity violations when + # both passing it to the result operation and returning it (as an inout + # argument). + value_wire, inout_wire = self.builder.add_op( + array_clone(base_ty, size_arg), value_wire ) - map_op = array_map(ht.Option(base_ty), size_arg, base_ty) - value_wire = self.builder.add_op(map_op, value_wire, unwrap) + func_ty = FunctionType( + [ + FuncInput( + array_type(node.base_ty, node.array_len), InputFlags.Inout + ), + ], + NoneType(), + ) + self._update_inout_ports(node.args, iter([inout_wire]), func_ty) if is_bool_type(node.base_ty): # We need to coerce a read on all the array elements if they are bools. array_read = array_read_bool(self.ctx) @@ -572,9 +582,9 @@ def visit_ResultExpr(self, node: ResultExpr) -> Wire: map_op = array_map(OpaqueBool, size_arg, ht.Bool) value_wire = self.builder.add_op(map_op, value_wire, array_read) base_ty = ht.Bool - # Turn `value_array` into regular linear `array` + # Turn `borrow_array` into regular `array` value_wire = self.builder.add_op( - array_convert_to_std_array(base_ty, size_arg), value_wire + array_to_std_array(base_ty, size_arg), value_wire ) hugr_ty: ht.Type = hugr.std.collections.array.Array(base_ty, size_arg) else: @@ -636,20 +646,20 @@ def visit_StateResultExpr(self, node: StateResultExpr) -> Wire: qubit_arr_in = self.builder.add_op( array_new(ht.Qubit, len(node.args) - 1), *qubits_in ) - # Turn into standard array from value array. + # Turn into standard array from borrow array. qubit_arr_in = self.builder.add_op( - array_convert_to_std_array(ht.Qubit, num_qubits_arg), qubit_arr_in + array_to_std_array(ht.Qubit, num_qubits_arg), qubit_arr_in ) qubit_arr_out = self.builder.add_op(op, qubit_arr_in) qubit_arr_out = self.builder.add_op( - array_convert_from_std_array(ht.Qubit, num_qubits_arg), qubit_arr_out + std_array_to_array(ht.Qubit, num_qubits_arg), qubit_arr_out ) qubits_out = unpack_array(self.builder, qubit_arr_out) else: - # If the input is an array of qubits, we need to unwrap the elements first, - # and then convert to a value array and back. + # If the input is an array of qubits, we need to convert to a standard + # array. qubits_in = [self.visit(node.args[1])] qubits_out = [ apply_array_op_with_conversions( @@ -681,17 +691,10 @@ def visit_DesugaredArrayComp(self, node: DesugaredArrayComp) -> Wire: assert isinstance(array_ty, OpaqueType) array_var = Variable(next(tmp_vars), array_ty, node) count_var = Variable(next(tmp_vars), int_type(), node) - # See https://github.com/CQCL/guppylang/issues/629 - hugr_elt_ty = ht.Option(node.elt_ty.to_hugr(self.ctx)) - # Initialise array with `None`s - make_none = array_comprehension_init_func(self.ctx) - make_none = self.builder.load_function( - make_none, - instantiation=ht.FunctionType([], [hugr_elt_ty]), - type_args=[ht.TypeTypeArg(node.elt_ty.to_hugr(self.ctx))], - ) + hugr_elt_ty = node.elt_ty.to_hugr(self.ctx) + # Initialise empty array. self.dfg[array_var] = self.builder.add_op( - array_repeat(hugr_elt_ty, node.length.to_arg().to_hugr(self.ctx)), make_none + barray_new_all_borrowed(hugr_elt_ty, node.length.to_arg().to_hugr(self.ctx)) ) self.dfg[count_var] = self.builder.load( hugr.std.int.IntVal(0, width=NumericType.INT_WIDTH) @@ -699,8 +702,12 @@ def visit_DesugaredArrayComp(self, node: DesugaredArrayComp) -> Wire: with self._build_generators([node.generator], [array_var, count_var]): elt = self.visit(node.elt) array, count = self.dfg[array_var], self.dfg[count_var] - [], [self.dfg[array_var]] = self._build_method_call( - array_ty, "__setitem__", node, [array, count, elt], array_ty.args + idx = self.builder.add_op(convert_itousize(), count) + self.dfg[array_var] = self.builder.add_op( + barray_return(hugr_elt_ty, node.length.to_arg().to_hugr(self.ctx)), + array, + idx, + elt, ) # Update `count += 1` one = self.builder.load(hugr.std.int.IntVal(1, width=NumericType.INT_WIDTH)) @@ -836,10 +843,6 @@ def python_value_to_hugr(v: Any, exp_ty: Type, ctx: CompilerContext) -> hv.Value return None -ARRAY_COMPREHENSION_INIT: Final[GlobalConstId] = GlobalConstId.fresh( - "array.__comprehension.init" -) - ARRAY_UNWRAP_ELEM: Final[GlobalConstId] = GlobalConstId.fresh("array.__unwrap_elem") ARRAY_WRAP_ELEM: Final[GlobalConstId] = GlobalConstId.fresh("array.__wrap_elem") @@ -849,54 +852,6 @@ def python_value_to_hugr(v: Any, exp_ty: Type, ctx: CompilerContext) -> hv.Value ) -def array_comprehension_init_func(ctx: CompilerContext) -> hf.Function: - """Returns the Hugr function that is used to initialise arrays elements before a - comprehension. - - Just returns the `None` variant of the optional element type. - - See https://github.com/CQCL/guppylang/issues/629 - """ - v = ht.Variable(0, ht.TypeBound(ht.TypeBound.Linear)) - sig = ht.PolyFuncType( - params=[ht.TypeTypeParam(ht.TypeBound.Linear)], - body=ht.FunctionType([], [ht.Option(v)]), - ) - func, already_defined = ctx.declare_global_func(ARRAY_COMPREHENSION_INIT, sig) - if not already_defined: - func.set_outputs(func.add_op(ops.Tag(0, ht.Option(v)))) - return func - - -def array_unwrap_elem(ctx: CompilerContext) -> hf.Function: - """Returns the Hugr function that is used to unwrap the elements in an option array - to turn it into a regular array.""" - v = ht.Variable(0, ht.TypeBound(ht.TypeBound.Linear)) - sig = ht.PolyFuncType( - params=[ht.TypeTypeParam(ht.TypeBound.Linear)], - body=ht.FunctionType([ht.Option(v)], [v]), - ) - func, already_defined = ctx.declare_global_func(ARRAY_UNWRAP_ELEM, sig) - if not already_defined: - msg = "Linear array element has already been used" - func.set_outputs(build_unwrap(func, func.inputs()[0], msg)) - return func - - -def array_wrap_elem(ctx: CompilerContext) -> hf.Function: - """Returns the Hugr function that is used to wrap the elements in an regular array - to turn it into a option array.""" - v = ht.Variable(0, ht.TypeBound(ht.TypeBound.Linear)) - sig = ht.PolyFuncType( - params=[ht.TypeTypeParam(ht.TypeBound.Linear)], - body=ht.FunctionType([v], [ht.Option(v)]), - ) - func, already_defined = ctx.declare_global_func(ARRAY_WRAP_ELEM, sig) - if not already_defined: - func.set_outputs(func.add_op(ops.Tag(1, ht.Option(v)), func.inputs()[0])) - return func - - def array_read_bool(ctx: CompilerContext) -> hf.Function: """Returns the Hugr function that is used to unwrap the elements in an option array to turn it into a regular array.""" @@ -945,35 +900,21 @@ def apply_array_op_with_conversions( output array. Transformations: - 1. Unwraps / wraps elements in options. - 3. (Optional) Converts from / to opaque bool to / from Hugr bool. - 2. Converts from / to value array to / from standard Hugr array. + 1. (Optional) Converts from / to opaque bool to / from Hugr bool. + 2. Converts from / to borrow array to / from standard Hugr array. """ - unwrap = array_unwrap_elem(ctx) - unwrap = builder.load_function( - unwrap, - instantiation=ht.FunctionType([ht.Option(elem_ty)], [elem_ty]), - type_args=[ht.TypeTypeArg(elem_ty)], - ) - map_op = array_map(ht.Option(elem_ty), size_arg, elem_ty) - unwrapped_array = builder.add_op(map_op, input_array, unwrap) - if convert_bool: array_read = array_read_bool(ctx) array_read = builder.load_function(array_read) map_op = array_map(OpaqueBool, size_arg, ht.Bool) - unwrapped_array = builder.add_op(map_op, unwrapped_array, array_read) + input_array = builder.add_op(map_op, input_array, array_read) elem_ty = ht.Bool - unwrapped_array = builder.add_op( - array_convert_to_std_array(elem_ty, size_arg), unwrapped_array - ) + input_array = builder.add_op(array_to_std_array(elem_ty, size_arg), input_array) - result_array = builder.add_op(op, unwrapped_array) + result_array = builder.add_op(op, input_array) - result_array = builder.add_op( - array_convert_from_std_array(elem_ty, size_arg), result_array - ) + result_array = builder.add_op(std_array_to_array(elem_ty, size_arg), result_array) if convert_bool: array_make_opaque = array_make_opaque_bool(ctx) @@ -982,11 +923,4 @@ def apply_array_op_with_conversions( result_array = builder.add_op(map_op, result_array, array_make_opaque) elem_ty = OpaqueBool - wrap = array_wrap_elem(ctx) - wrap = builder.load_function( - wrap, - instantiation=ht.FunctionType([elem_ty], [ht.Option(elem_ty)]), - type_args=[ht.TypeTypeArg(elem_ty)], - ) - map_op = array_map(elem_ty, size_arg, ht.Option(elem_ty)) - return builder.add_op(map_op, result_array, wrap) + return result_array diff --git a/guppylang-internals/src/guppylang_internals/compiler/stmt_compiler.py b/guppylang-internals/src/guppylang_internals/compiler/stmt_compiler.py index 2a5e9031c..f733dfad1 100644 --- a/guppylang-internals/src/guppylang_internals/compiler/stmt_compiler.py +++ b/guppylang-internals/src/guppylang_internals/compiler/stmt_compiler.py @@ -2,7 +2,6 @@ import functools from collections.abc import Sequence -import hugr.tys as ht from hugr import Wire, ops from hugr.build.dfg import DfBase @@ -120,8 +119,9 @@ def _assign_tuple(self, lhs: TupleUnpack, port: Wire) -> None: ports[len(left) : -len(right)] if right else ports[len(left) :] ) elt = get_element_type(array_ty).to_hugr(self.ctx) - opts = [self.builder.add_op(ops.Some(elt), p) for p in starred_ports] - array = self.builder.add_op(array_new(ht.Option(elt), len(opts)), *opts) + array = self.builder.add_op( + array_new(elt, len(starred_ports)), *starred_ports + ) self._assign(starred, array) @_assign.register @@ -130,7 +130,7 @@ def _assign_array(self, lhs: ArrayUnpack, port: Wire) -> None: # Given an assignment pattern `left, *starred, right`, pop from the left and # right, leaving us with the starred array in the middle length = lhs.length - opt_elt_ty = ht.Option(lhs.elt_type.to_hugr(self.ctx)) + elt_ty = lhs.elt_type.to_hugr(self.ctx) def pop( array: Wire, length: int, pats: list[ast.expr], from_left: bool @@ -141,10 +141,9 @@ def pop( elts = [] for i in range(num_pats): res = self.builder.add_op( - array_pop(opt_elt_ty, length - i, from_left), array + array_pop(elt_ty, length - i, from_left), array ) - [elt_opt, array] = build_unwrap(self.builder, res, err) - [elt] = build_unwrap(self.builder, elt_opt, err) + [elt, array] = build_unwrap(self.builder, res, err) elts.append(elt) # Assign elements to the given patterns for pat, elt in zip( @@ -164,7 +163,7 @@ def pop( self._assign(lhs.pattern.starred, array) else: assert length == 0 - self.builder.add_op(array_discard_empty(opt_elt_ty), array) + self.builder.add_op(array_discard_empty(elt_ty), array) @_assign.register def _assign_iterable(self, lhs: IterableUnpack, port: Wire) -> None: diff --git a/guppylang-internals/src/guppylang_internals/definition/custom.py b/guppylang-internals/src/guppylang_internals/definition/custom.py index e96bb3e06..7cdeb6ea4 100644 --- a/guppylang-internals/src/guppylang_internals/definition/custom.py +++ b/guppylang-internals/src/guppylang_internals/definition/custom.py @@ -7,6 +7,7 @@ from hugr import Wire, ops from hugr import tys as ht from hugr.build.dfg import DfBase +from hugr.std.collections.borrow_array import EXTENSION as BORROW_ARRAY_EXTENSION from guppylang_internals.ast_util import ( AstNode, @@ -23,6 +24,7 @@ DFContainer, GlobalConstId, partially_monomorphize_args, + qualified_name, ) from guppylang_internals.definition.common import ParsableDef from guppylang_internals.definition.value import CallReturnWires, CompiledCallableDef @@ -486,7 +488,39 @@ def compile(self, args: list[Wire]) -> list[Wire]: class CopyInoutCompiler(CustomInoutCallCompiler): - """Call compiler for functions that are noops but only want to borrow arguments.""" + """Call compiler for functions that borrow one argument to copy it.""" def compile_with_inouts(self, args: list[Wire]) -> CallReturnWires: + assert len(self.ty.input) == 1 + inp_ty = self.ty.input[0] + if inp_ty.type_bound() == ht.TypeBound.Linear: + (arg,) = args + copies = self._handle_affine_type(inp_ty, arg) + return CallReturnWires( + regular_returns=[copies[0]], inout_returns=[copies[1]] + ) return CallReturnWires(regular_returns=args, inout_returns=args) + + # Affine types in Guppy backed by a linear Hugr type need to be copied explicitly. + # TODO: Handle affine extension types more generally (borrow arrays are currently + # the only case). + def _handle_affine_type(self, ty: ht.Type, arg: Wire) -> list[Wire]: + match ty: + case ht.ExtType(type_def=type_def, args=type_args): + if qualified_name(type_def) == qualified_name( + BORROW_ARRAY_EXTENSION.get_type("borrow_array") + ): + assert len(type_args) == 2 + # Manually instantiate here to avoid circular import and use + # type args directly. + clone_op = BORROW_ARRAY_EXTENSION.get_op("clone").instantiate( + type_args, + ht.FunctionType(self.ty.input, self.ty.output), + ) + return list(self.builder.add_op(clone_op, arg)) + case _: + pass + raise InternalGuppyError( + f"Type `{ty}` needs an explicit handler in the `copy` compiler as " + "it is an affine Guppy type backed by a linear Hugr type." + ) diff --git a/guppylang-internals/src/guppylang_internals/definition/parameter.py b/guppylang-internals/src/guppylang_internals/definition/parameter.py index 7e264a6e6..cdf1a68f2 100644 --- a/guppylang-internals/src/guppylang_internals/definition/parameter.py +++ b/guppylang-internals/src/guppylang_internals/definition/parameter.py @@ -38,14 +38,21 @@ def to_param(self, idx: int) -> Parameter: class TypeVarDef(ParamDef, CompiledDef): """A type variable definition.""" - must_be_copyable: bool - must_be_droppable: bool + copyable: bool + droppable: bool description: str = field(default="type variable", init=False) def to_param(self, idx: int) -> TypeParam: """Creates a parameter from this definition.""" - return TypeParam(idx, self.name, self.must_be_copyable, self.must_be_droppable) + is_affine = not self.copyable and self.droppable + return TypeParam( + idx, + self.name, + must_be_copyable=self.copyable, + must_be_droppable=self.droppable, + is_affine=is_affine, + ) @dataclass(frozen=True) diff --git a/guppylang-internals/src/guppylang_internals/definition/pytket_circuits.py b/guppylang-internals/src/guppylang_internals/definition/pytket_circuits.py index 78722a9bc..f61f030f5 100644 --- a/guppylang-internals/src/guppylang_internals/definition/pytket_circuits.py +++ b/guppylang-internals/src/guppylang_internals/definition/pytket_circuits.py @@ -46,7 +46,6 @@ array_new, array_unpack, ) -from guppylang_internals.std._internal.compiler.prelude import build_unwrap from guppylang_internals.std._internal.compiler.tket_bool import OpaqueBool, make_opaque from guppylang_internals.tys.builtin import array_type, bool_type, float_type from guppylang_internals.tys.subst import Inst, Subst @@ -195,17 +194,9 @@ def compile_outer( # them into separate wires. for i, q_reg in enumerate(self.input_circuit.q_registers): reg_wire = outer_func.inputs()[i] - opt_elem_wires = outer_func.add_op( - array_unpack(ht.Option(ht.Qubit), q_reg.size), reg_wire + elem_wires = outer_func.add_op( + array_unpack(ht.Qubit, q_reg.size), reg_wire ) - elem_wires = [ - build_unwrap( - outer_func, - opt_elem, - "Internal error: unwrapping of array element failed", - ) - for opt_elem in opt_elem_wires - ] input_list.extend(elem_wires) else: @@ -231,26 +222,17 @@ def compile_outer( if has_params: lex_params: list[Wire] = list(outer_func.inputs()[offset:]) if self.use_arrays: - opt_param_wires = outer_func.add_op( + unpack_result = outer_func.add_op( array_unpack( - ht.Option(ht.Tuple(float_type().to_hugr(ctx))), - num_params, + ht.Tuple(float_type().to_hugr(ctx)), num_params ), lex_params[0], ) - lex_params = [ - build_unwrap( - outer_func, - opt_param, - "Internal error: unwrapping of array element failed", - ) - for opt_param in opt_param_wires - ] + lex_params = list(unpack_result) param_order = cast( list[str], hugr_func.metadata["TKET1.input_parameters"] ) lex_names = sorted(param_order) - assert len(lex_names) == len(lex_params) name_to_param = dict(zip(lex_names, lex_params, strict=True)) angle_wires = [name_to_param[name] for name in param_order] # Need to convert all angles to floats. @@ -281,34 +263,23 @@ def compile_outer( ] if self.use_arrays: - - def pack(elems: list[Wire], elem_ty: ht.Type, length: int) -> Wire: - elem_opts = [ - outer_func.add_op(ops.Some(elem_ty), elem) for elem in elems - ] - return outer_func.add_op( - array_new(ht.Option(elem_ty), length), *elem_opts - ) - array_wires: list[Wire] = [] wire_idx = 0 # First pack bool results into an array. for c_reg in self.input_circuit.c_registers: array_wires.append( - pack( - wires[wire_idx : wire_idx + c_reg.size], - OpaqueBool, - c_reg.size, + outer_func.add_op( + array_new(OpaqueBool, c_reg.size), + *wires[wire_idx : wire_idx + c_reg.size], ) ) wire_idx = wire_idx + c_reg.size # Then the borrowed qubits also need to be put back into arrays. for q_reg in self.input_circuit.q_registers: array_wires.append( - pack( - wires[wire_idx : wire_idx + q_reg.size], - ht.Qubit, - q_reg.size, + outer_func.add_op( + array_new(ht.Qubit, q_reg.size), + *wires[wire_idx : wire_idx + q_reg.size], ) ) wire_idx = wire_idx + q_reg.size diff --git a/guppylang-internals/src/guppylang_internals/std/_internal/compiler/array.py b/guppylang-internals/src/guppylang_internals/std/_internal/compiler/array.py index b0768f3a4..2bcde6ec6 100644 --- a/guppylang-internals/src/guppylang_internals/std/_internal/compiler/array.py +++ b/guppylang-internals/src/guppylang_internals/std/_internal/compiler/array.py @@ -2,31 +2,23 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Final, TypeVar +from typing import TYPE_CHECKING, TypeVar import hugr from hugr import Wire, ops from hugr import tys as ht -from hugr.std.collections.value_array import EXTENSION +from hugr.std.collections.borrow_array import EXTENSION -from guppylang_internals.compiler.core import ( - GlobalConstId, -) from guppylang_internals.definition.custom import CustomCallCompiler from guppylang_internals.definition.value import CallReturnWires from guppylang_internals.error import InternalGuppyError from guppylang_internals.std._internal.compiler.arithmetic import convert_itousize from guppylang_internals.std._internal.compiler.prelude import ( - build_expect_none, - build_unwrap, - build_unwrap_left, build_unwrap_right, ) from guppylang_internals.tys.arg import ConstArg, TypeArg -from guppylang_internals.tys.builtin import int_type if TYPE_CHECKING: - from hugr.build import function as hf from hugr.build.dfg import DfBase @@ -50,10 +42,10 @@ def _instantiate_array_op( def array_type(elem_ty: ht.Type, length: ht.TypeArg) -> ht.ExtType: """Returns the hugr type of a fixed length array. - This is the copyable `value_array` type used by Guppy. + This is the linear `borrow_array` type used by Guppy. """ elem_arg = ht.TypeTypeArg(elem_ty) - return EXTENSION.types["value_array"].instantiate([length, elem_arg]) + return EXTENSION.types["borrow_array"].instantiate([length, elem_arg]) def standard_array_type(elem_ty: ht.Type, length: ht.TypeArg) -> ht.ExtType: @@ -162,9 +154,9 @@ def array_repeat(elem_ty: ht.Type, length: ht.TypeArg) -> ops.ExtOp: ) -def array_convert_to_std_array(elem_ty: ht.Type, length: ht.TypeArg) -> ops.ExtOp: - """Returns an array operation to convert the `value_array` type used by Guppy into - the regular linear `array` in Hugr. +def array_to_std_array(elem_ty: ht.Type, length: ht.TypeArg) -> ops.ExtOp: + """Returns an array operation to convert a value of the `borrow_array` type + used by Guppy into a standard `array`. """ return EXTENSION.get_op("to_array").instantiate( [length, ht.TypeTypeArg(elem_ty)], @@ -174,9 +166,9 @@ def array_convert_to_std_array(elem_ty: ht.Type, length: ht.TypeArg) -> ops.ExtO ) -def array_convert_from_std_array(elem_ty: ht.Type, length: ht.TypeArg) -> ops.ExtOp: - """Returns an array operation to convert the `array` type used by Hugr into the - `value_array` type used by Guppy. +def std_array_to_array(elem_ty: ht.Type, length: ht.TypeArg) -> ops.ExtOp: + """Returns an array operation to convert the standard `array` type into the + `borrow_array` type used by Guppy. """ return EXTENSION.get_op("from_array").instantiate( [length, ht.TypeTypeArg(elem_ty)], @@ -186,6 +178,42 @@ def array_convert_from_std_array(elem_ty: ht.Type, length: ht.TypeArg) -> ops.Ex ) +def barray_borrow(elem_ty: ht.Type, length: ht.TypeArg) -> ops.ExtOp: + """Returns an array `borrow` operation.""" + arr_ty = array_type(elem_ty, length) + return _instantiate_array_op( + "borrow", elem_ty, length, [arr_ty, ht.USize()], [arr_ty, elem_ty] + ) + + +def barray_return(elem_ty: ht.Type, length: ht.TypeArg) -> ops.ExtOp: + """Returns an array `return` operation.""" + arr_ty = array_type(elem_ty, length) + return _instantiate_array_op( + "return", elem_ty, length, [arr_ty, ht.USize(), elem_ty], [arr_ty] + ) + + +def barray_discard_all_borrowed(elem_ty: ht.Type, length: ht.TypeArg) -> ops.ExtOp: + """Returns an array `discard_all_borrowed` operation.""" + arr_ty = array_type(elem_ty, length) + return _instantiate_array_op("discard_all_borrowed", elem_ty, length, [arr_ty], []) + + +def barray_new_all_borrowed(elem_ty: ht.Type, length: ht.TypeArg) -> ops.ExtOp: + """Returns an array `new_all_borrowed` operation.""" + arr_ty = array_type(elem_ty, length) + return _instantiate_array_op("new_all_borrowed", elem_ty, length, [], [arr_ty]) + + +def array_clone(elem_ty: ht.Type, length: ht.TypeArg) -> ops.ExtOp: + """Returns an array `clone` operation for arrays none of whose elements are + borrowed.""" + assert elem_ty.type_bound() == ht.TypeBound.Copyable + arr_ty = array_type(elem_ty, length) + return _instantiate_array_op("clone", elem_ty, length, [arr_ty], [arr_ty, arr_ty]) + + # ------------------------------------------------------ # --------- Custom compilers for non-native ops -------- # ------------------------------------------------------ @@ -238,12 +266,7 @@ def build_classical_array(self, elems: list[Wire]) -> Wire: def build_linear_array(self, elems: list[Wire]) -> Wire: """Lowers a call to `array.__new__` for linear arrays.""" - elem_opts = [ - self.builder.add_op(ops.Some(self.elem_ty), elem) for elem in elems - ] - return self.builder.add_op( - array_new(ht.Option(self.elem_ty), len(elems)), *elem_opts - ) + return self.builder.add_op(array_new(self.elem_ty, len(elems)), *elems) def compile(self, args: list[Wire]) -> list[Wire]: if self.elem_ty.type_bound() == ht.TypeBound.Linear: @@ -252,131 +275,35 @@ def compile(self, args: list[Wire]) -> list[Wire]: return [self.build_classical_array(args)] -ARRAY_GETITEM_CLASSICAL: Final[GlobalConstId] = GlobalConstId.fresh( - "array.__getitem__.classical" -) -ARRAY_GETITEM_LINEAR: Final[GlobalConstId] = GlobalConstId.fresh( - "array.__getitem__.linear" -) -ARRAY_SETITEM_CLASSICAL: Final[GlobalConstId] = GlobalConstId.fresh( - "array.__setitem__.classical" -) -ARRAY_SETITEM_LINEAR: Final[GlobalConstId] = GlobalConstId.fresh( - "array.__setitem__.linear" -) -ARRAY_ITER_ASSERT_ALL_USED_HELPER: Final[GlobalConstId] = GlobalConstId.fresh( - "ArrayIter._assert_all_used.helper" -) - - class ArrayGetitemCompiler(ArrayCompiler): """Compiler for the `array.__getitem__` function.""" - def _getitem_ty(self, bound: ht.TypeBound) -> ht.PolyFuncType: - """Constructs a polymorphic function type for `__getitem__`""" - # a(Option(T), N), int -> T, a(Option(T), N) - # Array element type parameter - elem_ty_param = ht.TypeTypeParam(bound) - # Array length parameter - length_param = ht.BoundedNatParam() - return ht.PolyFuncType( - params=[elem_ty_param, length_param], - body=ht.FunctionType( - input=[ - array_type( - ht.Option(ht.Variable(0, bound)), - ht.VariableArg(1, length_param), - ), - int_type().to_hugr(self.ctx), - ], - output=[ - ht.Variable(0, bound), - array_type( - ht.Option(ht.Variable(0, bound)), - ht.VariableArg(1, length_param), - ), - ], - ), - ) - - def _build_classical_getitem(self, func: hf.Function) -> None: - """Constructs a generic function for `__getitem__` for classical arrays.""" - elem_ty = ht.Variable(0, ht.TypeBound.Copyable) - length = ht.VariableArg(1, ht.BoundedNatParam()) + def _build_classical_getitem(self, array: Wire, idx: Wire) -> CallReturnWires: + """Constructs `__getitem__` for classical arrays.""" + idx = self.builder.add_op(convert_itousize(), idx) - # See https://github.com/CQCL/guppylang/issues/629 - elem_opt_ty = ht.Option(elem_ty) - none = func.add_op(ops.Tag(0, elem_opt_ty)) - idx = func.add_op(convert_itousize(), func.inputs()[1]) - # As copyable elements can be used multiple times, we need to swap the element - # back after initially swapping it out for `None` to get the value. - initial_result = func.add_op( - array_set(elem_opt_ty, length), - func.inputs()[0], - idx, - none, - ) - elem_opt, arr = build_unwrap_right( - func, initial_result, "Array index out of bounds" - ) - swapped_back = func.add_op( - array_set(elem_opt_ty, length), - arr, - idx, - elem_opt, - ) - _, arr = build_unwrap_right(func, swapped_back, "Array index out of bounds") - elem = build_unwrap(func, elem_opt, "array.__getitem__: Internal error") - - func.set_outputs(elem, arr) - - def _build_linear_getitem(self, func: hf.Function) -> None: - """Constructs function to call `array.__getitem__` for linear arrays.""" - elem_ty = ht.Variable(0, ht.TypeBound.Linear) - length = ht.VariableArg(1, ht.BoundedNatParam()) - - elem_opt_ty = ht.Option(elem_ty) - none = func.add_op(ops.Tag(0, elem_opt_ty)) - idx = func.add_op(convert_itousize(), func.inputs()[1]) - result = func.add_op( - array_set(elem_opt_ty, length), - func.inputs()[0], + opt_elem, arr = self.builder.add_op( + array_get(self.elem_ty, self.length), + array, idx, - none, ) - elem_opt, array = build_unwrap_right(func, result, "Array index out of bounds") - elem = build_unwrap( - func, elem_opt, "Linear array element has already been used" + elem = build_unwrap_right(self.builder, opt_elem, "Array index out of bounds") + return CallReturnWires( + regular_returns=[elem], + inout_returns=[arr], ) - func.set_outputs(elem, array) - - def _build_call_getitem( - self, - func: hf.Function, - array: Wire, - idx: Wire, - ) -> CallReturnWires: - """Inserts a call to `array.__getitem__`.""" - concrete_func_ty = ht.FunctionType( - input=[ - array_type(ht.Option(self.elem_ty), self.length), - int_type().to_hugr(self.ctx), - ], - output=[self.elem_ty, array_type(ht.Option(self.elem_ty), self.length)], - ) - type_args = [ht.TypeTypeArg(self.elem_ty), self.length] - func_call = self.builder.call( - func.parent_node, + def _build_linear_getitem(self, array: Wire, idx: Wire) -> CallReturnWires: + """Constructs `array.__getitem__` for linear arrays.""" + idx = self.builder.add_op(convert_itousize(), idx) + arr, elem = self.builder.add_op( + barray_borrow(self.elem_ty, self.length), array, idx, - instantiation=concrete_func_ty, - type_args=type_args, ) - outputs = list(func_call.outputs()) return CallReturnWires( - regular_returns=[outputs[0]], - inout_returns=[outputs[1]], + regular_returns=[elem], + inout_returns=[arr], ) def compile_with_inouts(self, args: list[Wire]) -> CallReturnWires: @@ -384,24 +311,9 @@ def compile_with_inouts(self, args: list[Wire]) -> CallReturnWires: [elem_ty_arg, _] = self.type_args assert isinstance(elem_ty_arg, TypeArg) if not elem_ty_arg.ty.copyable: - func_ty = self._getitem_ty(ht.TypeBound.Linear) - func, already_exists = self.ctx.declare_global_func( - ARRAY_GETITEM_LINEAR, func_ty - ) - if not already_exists: - self._build_linear_getitem(func) + return self._build_linear_getitem(array, idx) else: - func_ty = self._getitem_ty(ht.TypeBound.Copyable) - func, already_exists = self.ctx.declare_global_func( - ARRAY_GETITEM_CLASSICAL, func_ty - ) - if not already_exists: - self._build_classical_getitem(func) - return self._build_call_getitem( - func=func, - array=array, - idx=idx, - ) + return self._build_classical_getitem(array, idx) def compile(self, args: list[Wire]) -> list[Wire]: raise InternalGuppyError("Call compile_with_inouts instead") @@ -410,160 +322,60 @@ def compile(self, args: list[Wire]) -> list[Wire]: class ArraySetitemCompiler(ArrayCompiler): """Compiler for the `array.__setitem__` function.""" - def _setitem_ty(self, bound: ht.TypeBound) -> ht.PolyFuncType: - """Constructs a polymorphic function type for `__setitem__`""" - # a(Option(T), N), int, T -> a(Option(T), N) - elem_ty_param = ht.TypeTypeParam(bound) - length_param = ht.BoundedNatParam() - return ht.PolyFuncType( - params=[elem_ty_param, length_param], - body=ht.FunctionType( - input=[ - array_type( - ht.Option(ht.Variable(0, bound)), - ht.VariableArg(1, length_param), - ), - int_type().to_hugr(self.ctx), - ht.Variable(0, bound), - ], - output=[ - array_type( - ht.Option(ht.Variable(0, bound)), - ht.VariableArg(1, length_param), - ), - ], - ), - ) - - def _build_classical_setitem(self, func: hf.Function) -> None: - """Constructs a generic function for `__setitem__` for classical arrays.""" - elem_ty = ht.Variable(0, ht.TypeBound.Copyable) - length = ht.VariableArg(1, ht.BoundedNatParam()) - - elem_opt_ty = ht.Option(elem_ty) - idx = func.add_op(convert_itousize(), func.inputs()[1]) - elem_opt = func.add_op(ops.Some(elem_ty), func.inputs()[2]) - result = func.add_op( - array_set(elem_opt_ty, length), - func.inputs()[0], - idx, - elem_opt, - ) - _, array = build_unwrap_right(func, result, "Array index out of bounds") - - func.set_outputs(array) - - def _build_linear_setitem(self, func: hf.Function) -> None: - """Constructs function to call `array.__setitem__` for linear arrays.""" - elem_ty = ht.Variable(0, ht.TypeBound.Linear) - length = ht.VariableArg(1, ht.BoundedNatParam()) - - elem_opt_ty = ht.Option(elem_ty) - elem = func.add_op(ops.Some(elem_ty), func.inputs()[2]) - idx = func.add_op(convert_itousize(), func.inputs()[1]) - result = func.add_op( - array_set(elem_opt_ty, length), - func.inputs()[0], + def _build_classical_setitem( + self, array: Wire, idx: Wire, elem: Wire + ) -> CallReturnWires: + """Constructs `__setitem__` for classical arrays.""" + idx = self.builder.add_op(convert_itousize(), idx) + result = self.builder.add_op( + array_set(self.elem_ty, self.length), + array, idx, elem, ) - old_elem_opt, array = build_unwrap_right( - func, result, "Array index out of bounds" - ) - build_unwrap_left(func, old_elem_opt, "Linear array element has not been used") + _, arr = build_unwrap_right(self.builder, result, "Array index out of bounds") - func.set_outputs(array) + return CallReturnWires( + regular_returns=[], + inout_returns=[arr], + ) - def _build_call_setitem( - self, - func: hf.Function, - array: Wire, - idx: Wire, - elem: Wire, + def _build_linear_setitem( + self, array: Wire, idx: Wire, elem: Wire ) -> CallReturnWires: - """Inserts a call to `array.__setitem__`.""" - concrete_func_ty = ht.FunctionType( - input=[ - array_type(ht.Option(self.elem_ty), self.length), - int_type().to_hugr(self.ctx), - self.elem_ty, - ], - output=[array_type(ht.Option(self.elem_ty), self.length)], - ) - type_args = [ht.TypeTypeArg(self.elem_ty), self.length] - func_call = self.builder.call( - func.parent_node, + """Constructs `array.__setitem__` for linear arrays.""" + idx = self.builder.add_op(convert_itousize(), idx) + arr = self.builder.add_op( + barray_return(self.elem_ty, self.length), array, idx, elem, - instantiation=concrete_func_ty, - type_args=type_args, ) + return CallReturnWires( regular_returns=[], - inout_returns=list(func_call.outputs()), + inout_returns=[arr], ) def compile_with_inouts(self, args: list[Wire]) -> CallReturnWires: [array, idx, elem] = args if self.elem_ty.type_bound() == ht.TypeBound.Linear: - func_ty = self._setitem_ty(ht.TypeBound.Linear) - func, already_exists = self.ctx.declare_global_func( - ARRAY_SETITEM_LINEAR, func_ty - ) - if not already_exists: - self._build_linear_setitem(func) + return self._build_linear_setitem(array, idx, elem) else: - func_ty = self._setitem_ty(ht.TypeBound.Copyable) - func, already_exists = self.ctx.declare_global_func( - ARRAY_SETITEM_CLASSICAL, func_ty - ) - if not already_exists: - self._build_classical_setitem(func) - return self._build_call_setitem(func=func, array=array, idx=idx, elem=elem) + return self._build_classical_setitem(array, idx, elem) def compile(self, args: list[Wire]) -> list[Wire]: raise InternalGuppyError("Call compile_with_inouts instead") -class ArrayIterAsertAllUsedCompiler(ArrayCompiler): - """Compiler for the `ArrayIter._assert_all_used` method.""" +class ArrayDiscardAllUsedCompiler(ArrayCompiler): + """Compiler for the `_array_discard_all_used` method.""" def compile(self, args: list[Wire]) -> list[Wire]: - # For linear array iterators, map the array of optional elements to an - # `array[None, n]` that we can discard. if self.elem_ty.type_bound() == ht.TypeBound.Linear: - elem_opt_ty = ht.Option(self.elem_ty) - unit_ty = ht.UnitSum(1) - # Instantiate `unwrap_none` function - func = self.builder.load_function( - self.define_unwrap_none_helper(), - type_args=[ht.TypeTypeArg(self.elem_ty)], - instantiation=ht.FunctionType([elem_opt_ty], [unit_ty]), - ) - # Map it over the array so that the resulting array is no longer linear and - # can be discarded - [array_iter] = args - array, _ = self.builder.add_op(ops.UnpackTuple(), array_iter) + [arr] = args self.builder.add_op( - array_map(elem_opt_ty, self.length, unit_ty), array, func + barray_discard_all_borrowed(self.elem_ty, self.length), + arr, ) return [] - - def define_unwrap_none_helper(self) -> hf.Function: - """Define an `unwrap_none` function that checks that the passed element is - indeed `None`.""" - opt_ty = ht.Option(ht.Variable(0, ht.TypeBound.Linear)) - unit_ty = ht.UnitSum(1) - func_ty = ht.PolyFuncType( - params=[ht.TypeTypeParam(ht.TypeBound.Linear)], - body=ht.FunctionType([opt_ty], [unit_ty]), - ) - func, already_defined = self.ctx.declare_global_func( - ARRAY_ITER_ASSERT_ALL_USED_HELPER, func_ty - ) - if not already_defined: - err_msg = "ArrayIter._assert_all_used: array element has not been used" - build_expect_none(func, func.inputs()[0], err_msg) - func.set_outputs(func.add_op(ops.MakeTuple())) - return func diff --git a/guppylang-internals/src/guppylang_internals/tracing/unpacking.py b/guppylang-internals/src/guppylang_internals/tracing/unpacking.py index 4d65bcc32..29db773e8 100644 --- a/guppylang-internals/src/guppylang_internals/tracing/unpacking.py +++ b/guppylang-internals/src/guppylang_internals/tracing/unpacking.py @@ -1,7 +1,6 @@ from typing import Any, TypeVar from hugr import ops -from hugr import tys as ht from hugr.build.dfg import DfBase from guppylang_internals.ast_util import AstNode @@ -13,7 +12,6 @@ from guppylang_internals.compiler.expr_compiler import python_value_to_hugr from guppylang_internals.error import GuppyComptimeError, GuppyError from guppylang_internals.std._internal.compiler.array import array_new, unpack_array -from guppylang_internals.std._internal.compiler.prelude import build_unwrap from guppylang_internals.tracing.frozenlist import frozenlist from guppylang_internals.tracing.object import ( GuppyObject, @@ -71,9 +69,7 @@ def unpack_guppy_object( # them as Guppy objects here return obj elem_ty = get_element_type(ty) - opt_elems = unpack_array(builder, obj._use_wire(None)) - err = "Non-copyable array element has already been used" - elems = [build_unwrap(builder, opt_elem, err) for opt_elem in opt_elems] + elems = unpack_array(builder, obj._use_wire(None)) obj_list = [ unpack_guppy_object(GuppyObject(elem_ty, wire), builder, frozen) for wire in elems @@ -128,11 +124,8 @@ def guppy_object_from_py( f"Element at index {i + 1} does not match the type of " f"previous elements. Expected `{elem_ty}`, got `{obj._ty}`." ) - hugr_elem_ty = ht.Option(elem_ty.to_hugr(ctx)) - wires = [ - builder.add_op(ops.Tag(1, hugr_elem_ty), obj._use_wire(None)) - for obj in objs - ] + hugr_elem_ty = elem_ty.to_hugr(ctx) + wires = [obj._use_wire(None) for obj in objs] return GuppyObject( array_type(elem_ty, len(vs)), builder.add_op(array_new(hugr_elem_ty, len(vs)), *wires), @@ -172,26 +165,32 @@ def update_packed_value(v: Any, obj: "GuppyObject", builder: DfBase[P]) -> bool: assert isinstance(obj._ty, NoneType) case tuple(vs): assert isinstance(obj._ty, TupleType) - wires = builder.add_op(ops.UnpackTuple(), obj._use_wire(None)).outputs() - for v, ty, wire in zip(vs, obj._ty.element_types, wires, strict=True): - success = update_packed_value(v, GuppyObject(ty, wire), builder) + wire_iterator = builder.add_op( + ops.UnpackTuple(), obj._use_wire(None) + ).outputs() + for v, ty, out_wire in zip( + vs, obj._ty.element_types, wire_iterator, strict=True + ): + success = update_packed_value(v, GuppyObject(ty, out_wire), builder) if not success: return False case GuppyStructObject(_ty=ty, _field_values=values): assert obj._ty == ty - wires = builder.add_op(ops.UnpackTuple(), obj._use_wire(None)).outputs() - for field, wire in zip(ty.fields, wires, strict=True): + wire_iterator = builder.add_op( + ops.UnpackTuple(), obj._use_wire(None) + ).outputs() + for field, out_wire in zip(ty.fields, wire_iterator, strict=True): v = values[field.name] - success = update_packed_value(v, GuppyObject(field.ty, wire), builder) + success = update_packed_value( + v, GuppyObject(field.ty, out_wire), builder + ) if not success: values[field.name] = obj case list(vs) if len(vs) > 0: assert is_array_type(obj._ty) elem_ty = get_element_type(obj._ty) - opt_wires = unpack_array(builder, obj._use_wire(None)) - err = "Non-droppable array element has already been used" - for i, (v, opt_wire) in enumerate(zip(vs, opt_wires, strict=True)): - (wire,) = build_unwrap(builder, opt_wire, err).outputs() + wires = unpack_array(builder, obj._use_wire(None)) + for i, (v, wire) in enumerate(zip(vs, wires, strict=True)): success = update_packed_value(v, GuppyObject(elem_ty, wire), builder) if not success: vs[i] = obj diff --git a/guppylang-internals/src/guppylang_internals/tys/builtin.py b/guppylang-internals/src/guppylang_internals/tys/builtin.py index c1acc596a..54de2f144 100644 --- a/guppylang-internals/src/guppylang_internals/tys/builtin.py +++ b/guppylang-internals/src/guppylang_internals/tys/builtin.py @@ -177,13 +177,10 @@ def _array_to_hugr(args: Sequence[Argument], ctx: ToHugrContext) -> ht.Type: assert isinstance(ty_arg, TypeArg) assert isinstance(len_arg, ConstArg) - # Linear elements are turned into an optional to enable unsafe indexing. - # See `ArrayGetitemCompiler` for details. - # Same also for classical arrays, see https://github.com/CQCL/guppylang/issues/629 - elem_ty = ht.Option(ty_arg.ty.to_hugr(ctx)) + elem_ty = ty_arg.ty.to_hugr(ctx) hugr_arg = len_arg.to_hugr(ctx) - return hugr.std.collections.value_array.ValueArray(elem_ty, hugr_arg) + return hugr.std.collections.borrow_array.BorrowArray(elem_ty, hugr_arg) def _frozenarray_to_hugr(args: Sequence[Argument], ctx: ToHugrContext) -> ht.Type: diff --git a/guppylang-internals/src/guppylang_internals/tys/param.py b/guppylang-internals/src/guppylang_internals/tys/param.py index 3c7e3b624..3a65f05b2 100644 --- a/guppylang-internals/src/guppylang_internals/tys/param.py +++ b/guppylang-internals/src/guppylang_internals/tys/param.py @@ -83,8 +83,11 @@ def instantiate_bounds(self, inst: "PartialInst") -> Self: class TypeParam(ParameterBase): """A parameter of kind type. Used to define generic functions and types.""" + # TODO: `is_affine` here is not right, but suffices to hide + # https://github.com/CQCL/guppylang/issues/1306 in the current testsuite. must_be_copyable: bool must_be_droppable: bool + is_affine: bool | None = None @property def can_be_linear(self) -> bool: @@ -154,7 +157,9 @@ def instantiate_bounds(self, inst: "PartialInst") -> "TypeParam": def to_hugr(self, ctx: ToHugrContext) -> ht.TypeParam: """Computes the Hugr representation of the parameter.""" return ht.TypeTypeParam( - bound=ht.TypeBound.Linear if self.can_be_linear else ht.TypeBound.Copyable + bound=ht.TypeBound.Linear + if self.can_be_linear or self.is_affine + else ht.TypeBound.Copyable ) def __str__(self) -> str: diff --git a/guppylang/pyproject.toml b/guppylang/pyproject.toml index 755c28a25..a4e139cbe 100644 --- a/guppylang/pyproject.toml +++ b/guppylang/pyproject.toml @@ -35,7 +35,7 @@ classifiers = [ dependencies = [ "guppylang-internals ~= 0.24.0", "selene-sim~=0.2.3", - "selene-hugr-qis-compiler>=0.2.5", + "selene-hugr-qis-compiler~=0.2.8", "numpy~=2.0", "tqdm>=4.67.1", "types-tqdm>=4.67.0.20250809", diff --git a/guppylang/src/guppylang/std/array.py b/guppylang/src/guppylang/std/array.py index 1187b602b..b4fde115a 100644 --- a/guppylang/src/guppylang/std/array.py +++ b/guppylang/src/guppylang/std/array.py @@ -16,8 +16,8 @@ from guppylang_internals.definition.custom import CopyInoutCompiler from guppylang_internals.std._internal.checker import ArrayCopyChecker, NewArrayChecker from guppylang_internals.std._internal.compiler.array import ( + ArrayDiscardAllUsedCompiler, ArrayGetitemCompiler, - ArrayIterAsertAllUsedCompiler, ArraySetitemCompiler, NewArrayCompiler, ) @@ -103,11 +103,12 @@ def __next__( if self.i < int(n): elem = _array_unsafe_getitem(self.xs, self.i) return some((elem, ArrayIter(self.xs, self.i + 1))) - self._assert_all_used() + _array_discard_all_used(self.xs) return nothing() - @custom_function(ArrayIterAsertAllUsedCompiler()) - def _assert_all_used(self: ArrayIter[L, n] @ owned) -> None: ... + +@custom_function(ArrayDiscardAllUsedCompiler()) +def _array_discard_all_used(xs: array[L, n] @ owned) -> None: ... @custom_function(ArrayGetitemCompiler()) diff --git a/pyproject.toml b/pyproject.toml index 35ba67af2..4c296406c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,10 +17,11 @@ test = [ "pytest-xdist>=3.8.0", "ipykernel >=6.29.5,<7", "miette-py", - "selene-hugr-qis-compiler>=0.2.5, <0.3", ] pytket_integration = [{ include-group = "test" }, "guppylang[pytket]"] +[tool.uv] + [tool.uv.workspace] members = ["guppylang", "guppylang-internals", "miette-py"] @@ -31,7 +32,7 @@ miette-py = { workspace = true } # Uncomment these to test the latest dependency version during development # hugr = { git = "https://github.com/CQCL/hugr", subdirectory = "hugr-py", rev = "50a2bac" } -# tket = { git = "https://github.com/CQCL/tket2", subdirectory = "tket-py", rev = "aca944c" } +# tket = { git = "https://github.com/CQCL/tket2", subdirectory = "tket-py", rev = "e704c19" } [build-system] requires = ["hatchling"] diff --git a/tests/integration/test_array.py b/tests/integration/test_array.py index 863dc0efd..bedc270a4 100644 --- a/tests/integration/test_array.py +++ b/tests/integration/test_array.py @@ -347,6 +347,16 @@ def main() -> int: run_int_fn(main, expected=1) +def test_copy_inside(run_int_fn): + @guppy + def main() -> int: + xs = array(array(1), array(1)) + ys = xs[0].copy() + return ys[0] + + run_int_fn(main, expected=1) + + def test_copy_const(run_int_fn): @guppy def main() -> int: diff --git a/uv.lock b/uv.lock index cd90b59a1..08b052c88 100644 --- a/uv.lock +++ b/uv.lock @@ -30,7 +30,6 @@ dev = [ { name = "pytest-snapshot", specifier = ">=0.9.0,<1" }, { name = "pytest-xdist", specifier = ">=3.8.0" }, { name = "ruff", specifier = ">=0.6.2,<0.7" }, - { name = "selene-hugr-qis-compiler", specifier = ">=0.2.5,<0.3" }, ] docs = [ { name = "furo", specifier = ">=2024.8.6" }, @@ -55,7 +54,6 @@ pytket-integration = [ { name = "pytest-notebook", specifier = ">=0.10.0,<0.11" }, { name = "pytest-snapshot", specifier = ">=0.9.0,<1" }, { name = "pytest-xdist", specifier = ">=3.8.0" }, - { name = "selene-hugr-qis-compiler", specifier = ">=0.2.5,<0.3" }, ] test = [ { name = "ipykernel", specifier = ">=6.29.5,<7" }, @@ -66,7 +64,6 @@ test = [ { name = "pytest-notebook", specifier = ">=0.10.0,<0.11" }, { name = "pytest-snapshot", specifier = ">=0.9.0,<1" }, { name = "pytest-xdist", specifier = ">=3.8.0" }, - { name = "selene-hugr-qis-compiler", specifier = ">=0.2.5,<0.3" }, ] [[package]] @@ -971,7 +968,7 @@ requires-dist = [ { name = "guppylang-internals", editable = "guppylang-internals" }, { name = "numpy", specifier = "~=2.0" }, { name = "pytket", marker = "extra == 'pytket'", specifier = ">=1.34" }, - { name = "selene-hugr-qis-compiler", specifier = ">=0.2.5" }, + { name = "selene-hugr-qis-compiler", specifier = "~=0.2.8" }, { name = "selene-sim", specifier = "~=0.2.3" }, { name = "tket", marker = "extra == 'pytket'", specifier = ">=0.12.7" }, { name = "tqdm", specifier = ">=4.67.1" }, @@ -3213,14 +3210,13 @@ wheels = [ [[package]] name = "selene-hugr-qis-compiler" -version = "0.2.6" +version = "0.2.8" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/06/e936979e9ce8dad55e619dc5fe6d7c2aef573f520c091787a0b0169d86d9/selene_hugr_qis_compiler-0.2.6-cp310-abi3-macosx_13_0_arm64.whl", hash = "sha256:d85befade2422911d549c0f526dc3d3a1126a1dfb08c35ae44282491611f6065", size = 29956396, upload-time = "2025-09-23T09:25:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a7/5b614a150f01a9835a0357d4e9cae0920d2e680bcef0c5a2d6355031bfc6/selene_hugr_qis_compiler-0.2.6-cp310-abi3-macosx_13_0_x86_64.whl", hash = "sha256:4350b9b5bf142569d1d8cb1cddb9aa73e63d14d477e1e310dc92637f657a4077", size = 32664832, upload-time = "2025-09-23T09:25:45.238Z" }, - { url = "https://files.pythonhosted.org/packages/d8/11/e36b444dc74557af8adac321c89adbbc907047441b381c1c43c95e4bae10/selene_hugr_qis_compiler-0.2.6-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:860f7082afad87836faafdc8ecc8321d1722856de0465ac8c2d4ac9c86cae01b", size = 33385179, upload-time = "2025-09-23T09:25:47.903Z" }, - { url = "https://files.pythonhosted.org/packages/3a/d1/1e50f67dd02da764c93afb1a955eeb6926b77724328dd64e299feca55369/selene_hugr_qis_compiler-0.2.6-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:921d95ba298d3f1a1ea1bd76367cc82c3f89838ec6fb1cdbb3d7b6035a855be7", size = 34337440, upload-time = "2025-09-23T09:25:50.133Z" }, - { url = "https://files.pythonhosted.org/packages/bf/10/64578d17c6e758654d3948e8dd541fbf17b65b7b033d876d20bcb2c77cd9/selene_hugr_qis_compiler-0.2.6-cp310-abi3-win_amd64.whl", hash = "sha256:2b2c4938c668a54e229d98d270b98b3df3f5e87fa5658ecc8924096d6090780a", size = 29666299, upload-time = "2025-09-23T09:25:52.507Z" }, + { url = "https://files.pythonhosted.org/packages/61/cc/06f19a090915b093e9c4267d318acf4bc749df6c88f7a6192e5309ce845f/selene_hugr_qis_compiler-0.2.8-cp310-abi3-macosx_13_0_arm64.whl", hash = "sha256:7842b1046442056198c2ccf334c7ceb9e97c598d18cb95cba626441479ef9d10", size = 29796470, upload-time = "2025-10-21T13:06:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/de/09/616f9bf15ce335c3e3fa135cf4a0d5a16f4ce23b86cd55799369544ea590/selene_hugr_qis_compiler-0.2.8-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bc7b497577c901e50da362b96fedf3aadd61425981eaca5e7269fecb962aef78", size = 33168543, upload-time = "2025-10-21T13:06:46.642Z" }, + { url = "https://files.pythonhosted.org/packages/16/08/782e3ffa554fef4040d12e66d147b4ff66bf91daf5874d0f333903fa361b/selene_hugr_qis_compiler-0.2.8-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c0341a0ed4a53334dc328ab4346de41b9d579ce85d5ff7d655302d87291f1d64", size = 34276185, upload-time = "2025-10-21T13:06:49.568Z" }, + { url = "https://files.pythonhosted.org/packages/b2/3f/e52cd673e8d5095c440a75677d68734b8c91e736335edb901e40921c48ee/selene_hugr_qis_compiler-0.2.8-cp310-abi3-win_amd64.whl", hash = "sha256:e4e4a95a8a0a65ac9b1fef18c98995dee7befefac725a1dba1930b6905320dc9", size = 29488467, upload-time = "2025-10-21T13:06:52.911Z" }, ] [[package]]