Odd behaviour with shadowing variables
from guppylang import guppy
from guppylang.std.builtins import array, py, result
from guppylang.std.quantum import discard_array, measure_array, qubit, h, rz, rx, x, cx
from guppylang.hresult import HShots
def build_fun() :
j = 1
@guppy
def fun2(i : int, arr : array[qubit, 3]) -> None:
x(arr[i])
@guppy
def main() -> None:
reg = array(qubit() for _ in range(3))
for i in range(50):
j = i + 1
if j == 51:
x(reg[1])
fun2(py(j), reg)
[c0, c1, c2] = measure_array(reg)
result("c[0]", c0)
result("c[1]", c1)
result("c[2]", c2)
return main.compile()
Running the above results in error:
Error: Python error (at /home/abhayat/repos/QTDA-Algs-Guppy/Guppy-code/src/test.py:25:12)
|
23 | x(reg[1])
24 |
25 | fun2(py(j), reg)
| ^ Error occurred while evaluating this expression
Traceback printed below:
Traceback (most recent call last):
File "<string>",
line 1, in <module>
NameError: name 'j' is not defined
Guppy compilation failed due to 1 previous error
If I change the name of j to anything else, the code runs fine. This seems a little odd / buggy since the local j is out of scope by the time the compile time one is called.
It seems to me that it should be possible to use py(j) even when the local j is in scope, because the py(...) function should be enough for the compiler to differentiate between the two?
the local j is out of scope by the time the compile time one is called.
Note that we're following Python's scoping semantics. For example the following code would be fine
@guppy
def foo() -> int:
while True:
j = 1
if condition():
break
# Use of j outside loop is legal since the compiler can
# prove that it is definitely assigned
return j
So the j in your example is at least potentially still in scope.
It seems to me that it should be possible to use py(j) even when the local j is in scope, because the py(...) function should be enough for the compiler to differentiate between the two?
This feels a bit dangerous as it might confuse users:
j = 0
@guppy
def foo() -> int:
j = 1
return py(j) # Making this evaluate to 0 seems counter intuitive to me
I'd prefer to force users to be explicit and give them an error pointing to the fact that they're using a Guppy variable:
Error: Not compile-time evaluatable (at test.py:26:14)
|
24 | def foo() -> int:
25 | j = 1
26 | return py(j) # Making this evaluate to 0 seems counter intuitive to me
| ^ Guppy variable `j` cannot be accessed in a compile-time
| `py(...)` expression
But the error you're getting in your example is clearly worse! We should be outputting the same message as above.
The problem is that given a decorated function f, we use the f.__closure__ and f.__code__.co_freevars attributes of the CPython implementation to figure out which external variables are used by the function and make those available to the execution of the py(...) expression. However, Python doesn't treat j as a global variable since it is assigned in the function. Therefore, we don't catch it that case. But maybe there is a way around that...
j = 0
@guppy
def foo() -> int:
j = 1
return py(j) # Making this evaluate to 0 seems counter intuitive to me
I actually don't find this counter intuitive, since my mind reads py(j) as python_j, but perhaps that is just me.