Not sure if something like this is already in a package somewhere, but here’s one way to write such a macro:
function rewrite_assignment(@nospecialize(ex))
if Meta.isexpr(ex, :(=))
l, r = ex.args
l isa Symbol || error("Destructuring not supported")
tmp = gensym(l)
return esc(:(let $tmp = $r; global $l::typeof($tmp) = $tmp end))
elseif Meta.isexpr(ex, :block)
return Expr(:block, Base.mapany(rewrite_assignment, ex.args)...)
else
return ex
end
end
macro typed(ex)
return rewrite_assignment(ex)
end
julia> @typed begin
a = 2
b = 2.8
end
2.8
julia> code_typed() do
a, b
end
1-element Vector{Any}:
CodeInfo(
1 ─ %1 = Main.a::Int64
│ %2 = Main.b::Float64
│ %3 = Core.tuple(%1, %2)::Tuple{Int64, Float64}
└── return %3
) => Tuple{Int64, Float64}
That’s fine if the right-hand side is a literal or symbol, but you might also have an expression with side effects, for example something like @typed a = pop!(v). Or even one that returns different results each time like @typed a = rand((1, 2.0)). In those cases you want to avoid evaluating r twice.
Ah, found what I was originally thinking of: it’s the “@stable” macro by @giordano in the Seven Lines of Julia thread:
Simeon’s is a bit robuster indeed, with the $tmp.
@simeonschaub I was also wondering, why the Base.mapany (and not just map)?
↓
EDIT: I found why here (in the SnoopCompile docs) (maybe):
mapany avoids trying to narrow the type of f(v[i]) and just assumes it will be Any, thereby avoiding invalidations of many convert methods.
So, it’s used to spare the compiler some type inference work and/or to avoid convert invalidations, I think
The final thing I wonder about is the @nospecialize:
all Exprs are the same type, so there is no “too many specializations” problem right?
Like, why not say rewrite_assignment(ex::Expr).
And why the Meta.isexpr(ex, …) instead of just ex.head == …?
↓
Ah, these are to handle LineNumberNodes, I assume.