-
Notifications
You must be signed in to change notification settings - Fork 3
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 arrays iterable #632
Conversation
guppylang/checker/expr_checker.py
Outdated
result, result_ty = func.synthesize_call([node, *args], node, self.ctx) | ||
return with_type(result_ty, result), result_ty |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drive-by: Annotate the AST node with the inferred type to avoid repeated checking
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense for synthesize_call
to do this itself?
# Return an actual Python `TypeVar` so it can be used as an actual type in code | ||
# that is executed by interpreter before handing it to Guppy. | ||
return TypeVar(name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise, we couldn't write Generic[T, n]
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #632 +/- ##
=======================================
Coverage 92.48% 92.48%
=======================================
Files 66 66
Lines 7507 7549 +42
=======================================
+ Hits 6943 6982 +39
- Misses 564 567 +3 ☔ View full report in Codecov by Sentry. |
build_expect_none( | ||
cast(DfBase[ops.DfParentOp], func), func.inputs()[0], err_msg | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Annoying that we have this cast
here.
func
is a Function
which inherits DfBase[ops.FuncDefn]
and ops.FuncDefn
inherits ops.DfParentOp
. The problem is that DfBase
is not covariant, so this cast is technically not safe. But I also don't want to define build_expect_none
only for DfBase[ops.FuncDefn]
. Any ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I presume you can't just make DfBase
covariant...(that'd be the easy fix but still not the nice one).
How about build_expect_none[P](func: DfBase[P], ...)
where P
is a typing.TypeVar
bounded by ops.DfParentOp
, can you do that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly also doesn't work, mypy still complains :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #676
case BoundConstVar(idx=idx): | ||
case BoundConstVar(idx=idx, ty=NumericType(kind=NumericType.Kind.Nat)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drive-by: This case should only trigger for nat
type args
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, a few suggestions, possibly some answers, and a few questions...
guppylang/checker/expr_checker.py
Outdated
result, result_ty = func.synthesize_call([node, *args], node, self.ctx) | ||
return with_type(result_ty, result), result_ty |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense for synthesize_call
to do this itself?
name: str, elem_ty: ht.Type, length: int, inp: list[ht.Type], out: list[ht.Type] | ||
name: str, | ||
elem_ty: ht.Type, | ||
length: ht.TypeArg, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably all these length: int
s are changing to length: ht.TypeArg
to support them being compile-time-nat "type" variables....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly 👍
"""The length for the array op that is being compiled.""" | ||
match self.type_args: | ||
case [_, ConstArg(ConstValue(value=int(length)))]: | ||
return length | ||
case [_, ConstArg(const=const)]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
case [_, ConstArg(const=const)]: | |
case [_, ConstArg(const)]: |
would presumably be equivalent. But (if my interpretation of the int
--> ht.TypeArg
change above is correct), do we also need to handle this not being a ConstArg
but a variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConstArg
here means "compile-time constant", so including literals and variables. Terminology borrowed from rustc: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/consts/type.ConstKind.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth a little comment # Inc. literals and variables
- it's not super-obvious that a Const could include a variable!!
build_expect_none( | ||
cast(DfBase[ops.DfParentOp], func), func.inputs()[0], err_msg | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I presume you can't just make DfBase
covariant...(that'd be the easy fix but still not the nice one).
How about build_expect_none[P](func: DfBase[P], ...)
where P
is a typing.TypeVar
bounded by ops.DfParentOp
, can you do that?
def compile(self, args: list[Wire]) -> list[Wire]: | ||
# For linear array iterators, we need to make sure that all array elements have | ||
# been taken out (i.e. replaced by `None`) | ||
if self.elem_ty.type_bound() == ht.TypeBound.Any: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't there something like self.elem_ty.is_linear()
you can use here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that exists: https://cqcl.github.io/hugr/generated/hugr.tys.Type.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh this is not guppy-specific anymore, right
) | ||
func.set_outputs(func.add_op(ops.Tag(0, none_ty))) | ||
func = self.builder.load_function(func) | ||
# Map it over the array so that the resulting array is no longer linear and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok sorry, you have got this comment - just put this at the top!! :)
|
||
|
||
def build_expect_none( | ||
builder: DfBase[ops.DfParentOp], result: Wire, error_msg: str, error_signal: int = 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest rename result
-> input
(or input_option if you like)
guppylang/prelude/builtins.py
Outdated
@guppy | ||
@no_type_check | ||
def __hasnext__(self: "ArrayIter[L, n]" @ owned) -> tuple[bool, "ArrayIter[L, n]"]: | ||
return self.i < int(n), self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's pretty cool :). Is the op for typearg-to-runtime-value in this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was added in #618
guppylang/prelude/builtins.py
Outdated
|
||
|
||
@guppy.custom(ArrayGetitemCompiler()) | ||
def _array_unsafe_getitem(xs: array[L, n], idx: int) -> L: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this just be normal array_get followed by unwrap?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not on guppy level since we don't have an option type yet
for q in qs: | ||
discard(q) | ||
|
||
validate(module.compile()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am just wondering here if you could check that the module does include the expect_none stuff, and then have a counterexample
qs = array(qubit(), qubit(), qubit())
[discard(q) for q in qs]
that does not need expect none (because the array returned by the comprehension is copyable)....I think??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(because the array returned by the comprehension is copyable)....I think??
Yes, indeed! Unfortunately we can't add this test since #613 isn't merged yet
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for dropping the ball on this one! Looks good to me, modulo I'm hoping #676 fixes that cast
issue
🤖 I have created a release *beep* *boop* --- ## [0.14.0](v0.13.1...v0.14.0) (2024-12-19) ### ⚠ BREAKING CHANGES * Lists in `py(...)` expressions are now turned into Guppy arrays instead of lists. * `dirty_qubit` function removed * measure_return renamed to `project_z` ### Features * add `maybe_qubit` stdlib function ([#705](#705)) ([a49f70e](a49f70e)), closes [#627](#627) * add measure_array and discard_array quantum function ([#710](#710)) ([3ad49ff](3ad49ff)) * Add method to load pytket circuit without function stub ([#712](#712)) ([ee1e3de](ee1e3de)) * Add Option type to standard library ([#696](#696)) ([45ea6b7](45ea6b7)) * Allow generic nat args in statically sized ranges ([#706](#706)) ([f441bb8](f441bb8)), closes [#663](#663) * Array comprehension ([#613](#613)) ([fdc0526](fdc0526)), closes [#614](#614) [#616](#616) [#612](#612) * Implicit coercion of numeric types ([#702](#702)) ([df4745b](df4745b)), closes [#701](#701) * Load `pytket` circuit as a function definition ([#672](#672)) ([b21b7e1](b21b7e1)) * Make arrays iterable ([#632](#632)) ([07b9871](07b9871)) * qsystem std functions with updated primitives ([#679](#679)) ([b0f041f](b0f041f)) * remove dirty_qubit ([#698](#698)) ([78e366b](78e366b)) * Turn py expression lists into arrays ([#697](#697)) ([d52a00a](d52a00a)) * Unpacking assignment of iterable types with static size ([#688](#688)) ([602e243](602e243)) * update to hugr 0.10 and tket2 0.6 ([#725](#725)) ([63ea7a7](63ea7a7)) ### Bug Fixes * Accept non-negative int literals and py expressions as nats ([#708](#708)) ([a93d4fe](a93d4fe)), closes [#704](#704) * Allow borrowing inside comprehensions ([#723](#723)) ([02b6ab0](02b6ab0)), closes [#719](#719) * Detect unsupported default arguments ([#659](#659)) ([94ac7e3](94ac7e3)), closes [#658](#658) * docs build command ([#729](#729)) ([471b74c](471b74c)) * Ensure `int`s can be treated as booleans ([#709](#709)) ([6ef6d60](6ef6d60)), closes [#681](#681) * Fix array execution bugs ([#731](#731)) ([0f6ceaa](0f6ceaa)) * Fix implicit modules in IPython shells ([#662](#662)) ([4ecb5f2](4ecb5f2)), closes [#661](#661) * Properly report error for unsupported constants ([#724](#724)) ([d0c2da4](d0c2da4)), closes [#721](#721) * Properly report errors for unsupported expressions ([#692](#692)) ([7f24264](7f24264)), closes [#691](#691) * remove use of deprecated Ellipsis ([#699](#699)) ([b819a84](b819a84)) ### Documentation * Fix docs build ([#700](#700)) ([684f485](684f485)), closes [#680](#680) * fix README.md and quickstart.md ([#654](#654)) ([abb0221](abb0221)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: Seyon Sivarajah <[email protected]>
ArrayIter
type to the standard libraryarray.__iter__
methodDrive by: Fix array method compilers to handle generic array lengths
Note: The
ArrayIter.__end__
method depends on a newarray_map
Hugr op that doesn't exist yet to discard linear arraysCloses #617