Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Make linear types @inout by default; add @owned annotation #486

Merged
merged 28 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
40e9701
Drive-by: change name of parse_docstring
croyzor Sep 10, 2024
ec3072e
Drive-by: Say which identifiers are unknown
croyzor Sep 10, 2024
091802e
checkpoint: integration tests
croyzor Sep 11, 2024
1edb8f0
Update comprehension error tests
croyzor Sep 11, 2024
24e7d04
Update inout error files
croyzor Sep 11, 2024
d5db2e7
Update linear error files
croyzor Sep 11, 2024
e64155f
Update misc error tests
croyzor Sep 12, 2024
8a0976d
Only print the missing module for attributes
croyzor Sep 12, 2024
b982bc4
Update more error files
croyzor Sep 12, 2024
ea04238
Merge remote-tracking branch 'origin/main' into feat/owned
croyzor Sep 12, 2024
217d471
Fix printing of flags
croyzor Sep 12, 2024
718c2c7
Review comments
croyzor Sep 12, 2024
f07175a
Add new test case
croyzor Sep 12, 2024
6fa60fd
Update error files
croyzor Sep 12, 2024
8f9b7de
Review comment: update used_twice
croyzor Sep 12, 2024
8c911ee
revert pyproject change
croyzor Sep 12, 2024
45be26d
Shut up mypy
croyzor Sep 12, 2024
11d9642
Update references to @inout
croyzor Sep 12, 2024
fe69a36
Merge branch 'main' into feat/owned
croyzor Sep 12, 2024
df989bb
Take ownership of linear args to struct constructors
croyzor Sep 13, 2024
fc75c81
Add tests for linear struct initialisation
croyzor Sep 13, 2024
b8fe162
Make check an assert
croyzor Sep 13, 2024
b13e547
chore: Unskip now-passing tests (#475)
aborgna-q Sep 12, 2024
f32cd61
Merge remote-tracking branch 'origin/main' into feat/owned
croyzor Sep 13, 2024
a34d824
Update guppylang/tys/ty.py
croyzor Sep 13, 2024
46d32a1
Update guppylang/checker/linearity_checker.py
croyzor Sep 13, 2024
7c7e2d0
Update guppylang/tys/parsing.py
croyzor Sep 13, 2024
fdf10f2
Update tests; mypy annotations
croyzor Sep 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion guppylang/cfg/cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def analyze(
inout_vars: list[str],
) -> dict[BB, VariableStats[str]]:
stats = {bb: bb.compute_variable_stats() for bb in self.bbs}
# Mark all @inout variables as implicitly used in the exit BB
# Mark all borrowed variables as implicitly used in the exit BB
stats[self.exit_bb].used |= {x: InoutReturnSentinel(var=x) for x in inout_vars}
self.live_before = LivenessAnalysis(stats).run(self.bbs)
self.ass_before, self.maybe_ass_before = AssignmentAnalysis(
Expand Down
4 changes: 2 additions & 2 deletions guppylang/checker/expr_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ def type_check_args(


def check_inout_arg_place(place: Place, ctx: Context, node: PlaceNode) -> Place:
"""Performs additional checks for place arguments in @inout position.
"""Performs additional checks for borrowed place arguments.

In particular, we need to check that places involving `place[item]` subscripts
implement the corresponding `__setitem__` method.
Expand Down Expand Up @@ -845,7 +845,7 @@ def check_inout_arg_place(place: Place, ctx: Context, node: PlaceNode) -> Place:
setitem_args[0],
setitem_args[1:],
"__setitem__",
"not allowed in a subscripted `@inout` position",
"unable to have subscripted elements borrowed",
exp_sig,
True,
)
Expand Down
2 changes: 1 addition & 1 deletion guppylang/checker/func_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def parse_function_with_docstring(


def inout_var_names(func_ty: FunctionType) -> list[str]:
"""Returns the names of all `@inout` arguments of a function type."""
"""Returns the names of all borrowed arguments in a function type."""
assert func_ty.input_names is not None
return [
x
Expand Down
37 changes: 19 additions & 18 deletions guppylang/checker/linearity_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,13 @@ def new_scope(self) -> Generator[Scope, None, None]:
self.scope = scope

def visit_PlaceNode(self, node: PlaceNode, /, is_inout_arg: bool = False) -> None:
# Usage of @inout variables is generally forbidden. The only exception is using
# them in another @inout position of a function call. In that case, our
# Usage of borrowed variables is generally forbidden. The only exception is
# letting them be borrowed by another function call. In that case, our
# `_visit_call_args` helper will set `is_inout_arg=True`.
if is_inout_var(node.place) and not is_inout_arg:
raise GuppyError(
f"{node.place.describe} may only be used in an `@inout` position since "
"it is annotated as `@inout`. Consider removing the annotation to get "
f"{node.place.describe} may not be used in an `@owned` position since "
"it lacks an `@owned` annotation. Consider adding `@owned` to get "
croyzor marked this conversation as resolved.
Show resolved Hide resolved
"ownership of the value.",
node,
)
Expand All @@ -150,7 +150,7 @@ def visit_PlaceNode(self, node: PlaceNode, /, is_inout_arg: bool = False) -> Non
if not is_inout_arg and subscript.parent.ty.linear:
raise GuppyError(
"Subscripting on expression with linear type "
f"`{subscript.parent.ty}` is only allowed in `@inout` position",
f"`{subscript.parent.ty}` is not allowed in `@owned` position",
node,
)
self.scope.assign(subscript.item)
Expand Down Expand Up @@ -187,23 +187,24 @@ def _visit_call_args(self, func_ty: FunctionType, args: list[ast.expr]) -> None:
self.visit(arg)

def _reassign_inout_args(self, func_ty: FunctionType, args: list[ast.expr]) -> None:
"""Helper function to reassign the @inout arguments after a function call."""
"""Helper function to reassign the borrowed arguments after a function call."""
for inp, arg in zip(func_ty.inputs, args, strict=True):
if InputFlags.Inout in inp.flags:
match arg:
case PlaceNode(place=place):
self._reassign_single_inout_arg(place, arg)
case arg if inp.ty.linear:
raise GuppyError(
f"Inout argument with linear type `{inp.ty}` would be "
f"Borrowed argument with linear type `{inp.ty}` would be "
"dropped after this function call. Consider assigning the "
"expression to a local variable before passing it to the "
"function.",
arg,
)

def _reassign_single_inout_arg(self, place: Place, node: ast.expr) -> None:
"""Helper function to reassign a single inout argument after a function call."""
"""Helper function to reassign a single borrowed argument after a function
call."""
# Places involving subscripts are given back by visiting the `__setitem__` call
if subscript := contains_subscript(place):
assert subscript.setitem_call is not None
Expand Down Expand Up @@ -311,11 +312,11 @@ def _check_assign_targets(self, targets: list[ast.expr]) -> None:
[target] = targets
for tgt in find_nodes(lambda n: isinstance(n, PlaceNode), target):
assert isinstance(tgt, PlaceNode)
# Special error message for shadowing of @inout vars
# Special error message for shadowing of borrowed vars
x = tgt.place.id
if x in self.scope.vars and is_inout_var(self.scope[x]):
raise GuppyError(
f"Assignment shadows argument `{tgt.place}` annotated as `@inout`. "
f"Assignment shadows borrowed argument `{tgt.place}`. "
"Consider assigning to a different name.",
tgt,
)
Expand Down Expand Up @@ -432,7 +433,7 @@ def contains_subscript(place: Place) -> SubscriptAccess | None:


def is_inout_var(place: Place) -> TypeGuard[Variable]:
"""Checks whether a place is an @inout variable."""
"""Checks whether a place is a borrowed variable."""
return isinstance(place, Variable) and InputFlags.Inout in place.flags


Expand All @@ -452,7 +453,7 @@ def check_cfg_linearity(
for bb in cfg.bbs
}

# Check that @inout vars are not being shadowed. This would also be caught by
# Check that borrowed vars are not being shadowed. This would also be caught by
# the dataflow analysis below, however we can give nicer error messages here.
for bb, scope in scopes.items():
if bb == cfg.entry_bb:
Expand All @@ -466,12 +467,12 @@ def check_cfg_linearity(
entry_place = entry_scope[x]
if is_inout_var(entry_place):
raise GuppyError(
f"Assignment shadows argument `{entry_place}` annotated as "
"`@inout`. Consider assigning to a different name.",
f"Assignment shadows borrowed argument `{entry_place}`. "
"Consider assigning to a different name.",
place.defined_at,
)

# Mark the @inout variables as implicitly used in the exit BB
# Mark the borrowed variables as implicitly used in the exit BB
exit_scope = scopes[cfg.exit_bb]
for var in cfg.entry_bb.sig.input_row:
if InputFlags.Inout in var.flags:
Expand All @@ -498,13 +499,13 @@ def check_cfg_linearity(
if place.ty.linear and (prev_use := scope.used(x)):
use = use_scope.used_parent[x]
# Special case if this is a use arising from the implicit returning
# of an @inout argument
# of a borrowed argument
if isinstance(use, InoutReturnSentinel):
assert isinstance(use.var, Variable)
assert InputFlags.Inout in use.var.flags
raise GuppyError(
f"Argument `{use.var}` annotated as `@inout` cannot be "
f"returned to the caller since `{place}` is used at {{0}}. "
f"Borrowed argument `{use.var}` cannot be returned "
f"to the caller since `{place}` is used at {{0}}. "
f"Consider writing a value back into `{place}` before "
"returning.",
use.var.defined_at,
Expand Down
6 changes: 3 additions & 3 deletions guppylang/compiler/expr_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,18 +243,18 @@ def _update_inout_ports(
inout_ports: Iterator[Wire],
func_ty: FunctionType,
) -> None:
"""Helper method that updates the ports for @inout arguments after a call."""
"""Helper method that updates the ports for borrowed arguments after a call."""
for inp, arg in zip(func_ty.inputs, args, strict=True):
if InputFlags.Inout in inp.flags:
# Linearity checker ensures that inout arguments that are not places
# Linearity checker ensures that borrowed arguments that are not places
# can be safely dropped after the call returns
if not isinstance(arg, PlaceNode):
next(inout_ports)
continue
self.dfg[arg.place] = next(inout_ports)
# Places involving subscripts need to generate code for the appropriate
# `__setitem__` call. Nested subscripts are handled automatically since
# `arg.place.parent` occurs as an inout arg of this call, so will also
# `arg.place.parent` occurs as an arg of this call, so will also
# be recursively reassigned.
if subscript := contains_subscript(arg.place):
self.visit(subscript.setitem_call)
Expand Down
6 changes: 3 additions & 3 deletions guppylang/definition/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ def synthesize(self, args: list[ast.expr]) -> tuple[ast.expr, Type]:


class CustomInoutCallCompiler(ABC):
"""Abstract base class for custom function call compilers with @inout args.
"""Abstract base class for custom function call compilers with borrowed args.

Args:
builder: The function builder where the function should be defined.
Expand Down Expand Up @@ -303,13 +303,13 @@ def _setup(
def compile_with_inouts(self, args: list[Wire]) -> CallReturnWires:
"""Compiles a custom function call.

Returns the outputs of the call together with any @inout arguments that are
Returns the outputs of the call together with any borrowed arguments that are
passed through the function.
"""


class CustomCallCompiler(CustomInoutCallCompiler, ABC):
"""Abstract base class for custom function call compilers without @inout args."""
"""Abstract base class for custom function call compilers with only owned args."""

@abstractmethod
def compile(self, args: list[Wire]) -> list[Wire]:
Expand Down
4 changes: 2 additions & 2 deletions guppylang/definition/value.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def compile_call(
) -> "CallReturnWires":
"""Compiles a call to the function.

Returns the outputs of the call together with any @inout arguments that are
Returns the outputs of the call together with any borrowed arguments that are
passed through the function.
"""

Expand All @@ -101,7 +101,7 @@ def load(
class CallReturnWires(NamedTuple):
"""Output wires that are given back from a call.

Contains the regular function returns together with any @inout arguments that are
Contains the regular function returns together with any borrowed arguments that are
passed through the function.
"""

Expand Down
4 changes: 2 additions & 2 deletions guppylang/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,8 @@ class ResultExpr(ast.expr):


class InoutReturnSentinel(ast.expr):
"""An invisible expression corresponding to an implicit use of @inout vars whenever
a function returns."""
"""An invisible expression corresponding to an implicit use of borrowed vars
whenever a function returns."""

var: "Place | str"

Expand Down
7 changes: 0 additions & 7 deletions guppylang/prelude/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,6 @@ def py(*_args: Any) -> Any:
raise GuppyError("`py` can only by used in a Guppy context")


class _Inout:
"""Dummy class to support `@inout` annotations."""

def __rmatmul__(self, other: Any) -> Any:
return other


class _Owned:
"""Dummy class to support `@owned` annotations."""

Expand Down
6 changes: 3 additions & 3 deletions guppylang/tys/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,16 +196,16 @@ def parse_function_io_types(
) -> tuple[list[FuncInput], Type]:
"""Parses the inputs and output types of a function type.

This function takes care of parsing `@inout` annotations and any related checks.
This function takes care of parsing annotations and any related checks.

Returns the parsed input and output types.
"""
inputs = []
for inp in input_nodes:
ty, flags = type_with_flags_from_ast(inp, globals, param_var_mapping)
if InputFlags.Inout in flags and not ty.linear:
if InputFlags.Owned in flags and not ty.linear:
raise GuppyError(
f"Non-linear type `{ty}` cannot be annotated as `@inout`", loc
f"Non-linear type `{ty}` cannot be annotated as `@owned`", loc
)
if ty.linear and InputFlags.Owned not in flags:
flags |= InputFlags.Inout
Expand Down
5 changes: 2 additions & 3 deletions guppylang/tys/ty.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,7 @@ def transform(self, transformer: Transformer) -> "Type":
class InputFlags(Flag):
"""Flags that can be set on inputs of function types.

Currently, the only supported flag is `Inout`. In the future, we could add
additional flags like `Frozen`, `Owned`, etc.
In the future, we could add additional flags like `Frozen`, `Owned`, etc.
croyzor marked this conversation as resolved.
Show resolved Hide resolved
"""

NoFlags = 0
Expand Down Expand Up @@ -381,7 +380,7 @@ def _to_hugr_function_type(self) -> ht.FunctionType:
ins = [inp.ty.to_hugr() for inp in self.inputs]
outs = [
*(t.to_hugr() for t in type_to_row(self.output)),
# We might have additional @inout args that will be also outputted
# We might have additional borrowed args that will be also outputted
*(inp.ty.to_hugr() for inp in self.inputs if InputFlags.Inout in inp.flags),
]
return ht.FunctionType(input=ins, output=outs)
Expand Down
2 changes: 1 addition & 1 deletion tests/error/array_errors/subscript_non_inout.err
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Guppy compilation failed. Error in file $FILE:14
13: def main(qs: array[qubit, 42]) -> tuple[qubit, array[qubit, 42]]:
14: q = qs[0]
^^^^^
GuppyError: Subscripting on expression with linear type `array[qubit, 42]` is only allowed in `@inout` position
GuppyError: Subscripting on expression with linear type `array[qubit, 42]` is not allowed in `@owned` position
2 changes: 1 addition & 1 deletion tests/error/inout_errors/drop_after_call.err
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Guppy compilation failed. Error in file $FILE:15
14: def test() -> None:
15: foo(qubit())
^^^^^^^
GuppyError: Inout argument with linear type `qubit` would be dropped after this function call. Consider assigning the expression to a local variable before passing it to the function.
GuppyError: Borrowed argument with linear type `qubit` would be dropped after this function call. Consider assigning the expression to a local variable before passing it to the function.
2 changes: 1 addition & 1 deletion tests/error/inout_errors/moved.err
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Guppy compilation failed. Error in file $FILE:21
20: foo(q)
21: use(q)
^
GuppyError: Variable `q` may only be used in an `@inout` position since it is annotated as `@inout`. Consider removing the annotation to get ownership of the value.
GuppyError: Variable `q` may not be used in an `@owned` position since it lacks an `@owned` annotation. Consider adding `@owned` to get ownership of the value.
2 changes: 1 addition & 1 deletion tests/error/inout_errors/moved_assign.err
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Guppy compilation failed. Error in file $FILE:11
10: def test(q: qubit) -> qubit:
11: r = q
^
GuppyError: Variable `q` may only be used in an `@inout` position since it is annotated as `@inout`. Consider removing the annotation to get ownership of the value.
GuppyError: Variable `q` may not be used in an `@owned` position since it lacks an `@owned` annotation. Consider adding `@owned` to get ownership of the value.
2 changes: 1 addition & 1 deletion tests/error/inout_errors/moved_if.err
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Guppy compilation failed. Error in file $FILE:17
16: if b:
17: use(q)
^
GuppyError: Variable `q` may only be used in an `@inout` position since it is annotated as `@inout`. Consider removing the annotation to get ownership of the value.
GuppyError: Variable `q` may not be used in an `@owned` position since it lacks an `@owned` annotation. Consider adding `@owned` to get ownership of the value.
2 changes: 1 addition & 1 deletion tests/error/inout_errors/moved_out.err
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ Guppy compilation failed. Error in file $FILE:20
18: @guppy(module)
19: def test(s: MyStruct) -> None:
^^^^^^^^^^^
GuppyError: Argument `s` annotated as `@inout` cannot be returned to the caller since `s.q` is used at 21:8. Consider writing a value back into `s.q` before returning.
GuppyError: Borrowed argument `s` cannot be returned to the caller since `s.q` is used at 21:8. Consider writing a value back into `s.q` before returning.
2 changes: 1 addition & 1 deletion tests/error/inout_errors/moved_out_if.err
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ Guppy compilation failed. Error in file $FILE:20
18: @guppy(module)
19: def test(s: MyStruct, b: bool) -> None:
^^^^^^^^^^^
GuppyError: Argument `s` annotated as `@inout` cannot be returned to the caller since `s.q` is used at 22:12. Consider writing a value back into `s.q` before returning.
GuppyError: Borrowed argument `s` cannot be returned to the caller since `s.q` is used at 22:12. Consider writing a value back into `s.q` before returning.
2 changes: 1 addition & 1 deletion tests/error/inout_errors/shadow.err
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Guppy compilation failed. Error in file $FILE:12
11: def test(q: qubit) -> None:
12: q = qubit()
^
GuppyError: Assignment shadows argument `q` annotated as `@inout`. Consider assigning to a different name.
GuppyError: Assignment shadows borrowed argument `q`. Consider assigning to a different name.
2 changes: 1 addition & 1 deletion tests/error/inout_errors/shadow_if.err
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Guppy compilation failed. Error in file $FILE:13
12: if b:
13: q = qubit()
^
GuppyError: Assignment shadows argument `q` annotated as `@inout`. Consider assigning to a different name.
GuppyError: Assignment shadows borrowed argument `q`. Consider assigning to a different name.
2 changes: 1 addition & 1 deletion tests/error/inout_errors/subscript_not_setable.err
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Guppy compilation failed. Error in file $FILE:24
23: def test(c: MyImmutableContainer) -> MyImmutableContainer:
24: foo(c[0])
^^^^
GuppyTypeError: Expression of type `MyImmutableContainer` is not allowed in a subscripted `@inout` position since it does not implement the `__setitem__` method
GuppyTypeError: Expression of type `MyImmutableContainer` is unable to have subscripted elements borrowed since it does not implement the `__setitem__` method