I got it, and I think this addresses @Tamas_Papp’s type inference issues:
module WithMacro
export @with
macro with(nt,body)
local b = QuoteNode(body)
quote
let f = genfunc($(esc(nt)), $b)
f(; $(esc(nt))...)
end
end
end
function genfunc(nt, body)
local props = propertynames(nt)
f = :( (; $(props...) )-> $body )
g = Meta.eval(f)
# Base.invokelatest(g; nt...)
end
end
Usage:
julia> using .WithMacro
julia> subtotal = (price=12.99, shipping=3.99)
(price = 12.99, shipping = 3.99)
julia> @with subtotal begin
price + shipping
end
16.98
julia> person = (first="Mark", last="Kittisopikul")
(first = "Mark", last = "Kittisopikul")
julia> @with person begin
"$first $last"
end
"Mark Kittisopikul"
Analysis:
julia> f = WithMacro.genfunc(subtotal,quote
price + shipping
end)
#11 (generic function with 1 method)
julia> @code_warntype f(; subtotal...)
Variables
#unused#::Core.Compiler.Const(Base.Meta.var"#11#13##kw"(), false)
@_2::NamedTuple{(:price, :shipping),Tuple{Float64,Float64}}
@_3::Core.Compiler.Const(Base.Meta.var"#11#13"(), false)
price::Float64
shipping::Float64
@_6::Float64
@_7::Float64
Body::Float64
1 ─ %1 = Base.haskey(@_2, :price)::Core.Compiler.Const(true, false)
│ %1
│ (@_6 = Base.getindex(@_2, :price))
└── goto #3
2 ─ Core.Compiler.Const(:(Core.UndefKeywordError(:price)), false)
└── Core.Compiler.Const(:(@_6 = Core.throw(%5)), false)
3 ┄ (price = @_6)
│ %8 = Base.haskey(@_2, :shipping)::Core.Compiler.Const(true, false)
│ %8
│ (@_7 = Base.getindex(@_2, :shipping))
└── goto #5
4 ─ Core.Compiler.Const(:(Core.UndefKeywordError(:shipping)), false)
└── Core.Compiler.Const(:(@_7 = Core.throw(%12)), false)
5 ┄ (shipping = @_7)
│ %15 = (:price, :shipping)::Core.Compiler.Const((:price, :shipping), false)
│ %16 = Core.apply_type(Core.NamedTuple, %15)::Core.Compiler.Const(NamedTuple{(:price, :shipping),T} where T<:Tuple, false)
│ %17 = Base.structdiff(@_2, %16)::Core.Compiler.Const(NamedTuple(), false)
│ %18 = Base.pairs(%17)::Core.Compiler.Const(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(), false)
│ %19 = Base.isempty(%18)::Core.Compiler.Const(true, false)
│ %19
└── goto #7
6 ─ Core.Compiler.Const(:(Base.kwerr(@_2, @_3)), false)
7 ┄ %23 = Base.Meta.:(var"#11#12")(price, shipping, @_3)::Float64
└── return %23
julia> f = WithMacro.genfunc(person,quote
"$first $last"
end)
#14 (generic function with 1 method)
julia> @code_warntype f(;person...)
Variables
#unused#::Core.Compiler.Const(Base.Meta.var"#14#16##kw"(), false)
@_2::NamedTuple{(:first, :last),Tuple{String,String}}
@_3::Core.Compiler.Const(Base.Meta.var"#14#16"(), false)
first::String
last::String
@_6::String
@_7::String
Body::String
1 ─ %1 = Base.haskey(@_2, :first)::Core.Compiler.Const(true, false)
│ %1
│ (@_6 = Base.getindex(@_2, :first))
└── goto #3
2 ─ Core.Compiler.Const(:(Core.UndefKeywordError(:first)), false)
└── Core.Compiler.Const(:(@_6 = Core.throw(%5)), false)
3 ┄ (first = @_6)
│ %8 = Base.haskey(@_2, :last)::Core.Compiler.Const(true, false)
│ %8
│ (@_7 = Base.getindex(@_2, :last))
└── goto #5
4 ─ Core.Compiler.Const(:(Core.UndefKeywordError(:last)), false)
└── Core.Compiler.Const(:(@_7 = Core.throw(%12)), false)
5 ┄ (last = @_7)
│ %15 = (:first, :last)::Core.Compiler.Const((:first, :last), false)
│ %16 = Core.apply_type(Core.NamedTuple, %15)::Core.Compiler.Const(NamedTuple{(:first, :last),T} where T<:Tuple, false)
│ %17 = Base.structdiff(@_2, %16)::Core.Compiler.Const(NamedTuple(), false)
│ %18 = Base.pairs(%17)::Core.Compiler.Const(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}(), false)
│ %19 = Base.isempty(%18)::Core.Compiler.Const(true, false)
│ %19
└── goto #7
6 ─ Core.Compiler.Const(:(Base.kwerr(@_2, @_3)), false)
7 ┄ %23 = Base.Meta.:(var"#14#15")(first, last, @_3)::String
└── return %23