f(x) = @generated g() = x
julia> f(1)()
ERROR: type DataType has no field x
in g(...) at ./REPL[1]:1
As performed in 0.5.0 and 0.6.0-dev.826.
:?
julia> @code_lowered f(1)()
CodeInfo(:(begin
nothing
return (Core.getfield)(#self#,:x)
end))
This issue seems to apply to inner generated functions generally:
julia> function f(x)
@generated g() = x
g
end;
julia> f(1)()
ERROR: type DataType has no field x
in g(...) at ./REPL[1]:2
This may be similar to #15602 and #18674, in that it is no longer permitted but doesn't have an appropriate error message.
Oh, that's nice to know. I just tried something like this and it worked:
function f(x)
@generated function()
x
end
end
julia> f(1)()
1
So I assumed it was a macro thing. I will update the issue, then.
I'm not sure how easily this can be fixed, but the correct answer should have been Int in all of the above examples (since the function generator capture types, not values).
@vtjnash yeah, I later realized that the above gave a 1 because @generated function() ... end doesn't create a generated function at all, it just returns the function itself. That's a separate issue I guess.
f = @generated function() end
g = @generated function h() end
julia> methods(f).ms[1].isstaged
false
julia> methods(g).ms[1].isstaged
true
@vtjnash hey wait, you sure? The "@generated function() end doesn't work" issue is true, but the "generated closure captures types" isn't consistent with other outside variables:
x = 1
@generated f() = x
julia> f()
1
julia> methods(f).ms[1].isstaged
true
Yes, I think it would be reasonable for generated functions to still capture variables by value. Arguably @generated only causes the generator's arguments to be bound to types, and nothing else.
To give a reason, functions are not specialized on the types of captured variables, _unless_ those are reflected in the type of the function itself, which is already an argument. So generated functions do not need their captured variables to become types.
True, this is perhaps one way we could allow inner generated functions to capture variables, by requiring that the generator also reference that variable? The implementation of such sounds quite annoying.
However, the following would need to hold in order for the cached code to have only depended on the type #g#f{typeof(x)}, which is why in the above example, the correct answer would be Int:
function g(x)
@generated f()
return :(x :: $x)
end
return f
end
In your code example,
x = 1
@generated f() = x
you are missing a const declaration before the x for this to be a valid usage of generated functions. This is not capturing x, but creating a new const reference to it.
Most helpful comment
Yes, I think it would be reasonable for generated functions to still capture variables by value. Arguably
@generatedonly causes the generator's arguments to be bound to types, and nothing else.To give a reason, functions are not specialized on the types of captured variables, _unless_ those are reflected in the type of the function itself, which is already an argument. So generated functions do not need their captured variables to become types.