Skip to content

Hacky way of supporting AdvancedPS.jl #483

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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 119 additions & 1 deletion src/compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ function build_output(modeldef, linenumbernode)

## Build the anonymous evaluator from the user-provided model definition.
evaluatordef = deepcopy(modeldef)
evaluatordef[:kwargs] = []

# Add the internal arguments to the user-specified arguments (positional + keywords).
evaluatordef[:args] = vcat(
Expand All @@ -559,6 +560,9 @@ function build_output(modeldef, linenumbernode)
$(replace_returns(make_returns_explicit!(modeldef[:body])))
end

# Add the kwargs as the first argument to the evaluator.
evaluatordef_combined = make_inlined_kwcall(evaluatordef)

## Build the model function.

# Obtain or generate the name of the model to support functors:
Expand Down Expand Up @@ -586,11 +590,125 @@ function build_output(modeldef, linenumbernode)
end

return MacroTools.@q begin
$(MacroTools.combinedef(evaluatordef))
$(evaluatordef_combined)
$(Base).@__doc__ $(MacroTools.combinedef(modeldef))
end
end

@doc raw"""
make_inlined_kwcall(modeldef)

Transforms the function definition `modeldef` into a function definition that
takes a `NamedTuple` of keyword arguments as the first argument and unwraps
the keyword arguments into the function body.

This is similar to how `Base.kwcall` on Julia 1.9+, but with inlining of the
extraction of the keyword arguments into the corresponding variables, and
without any checking of the validity of the keyword arguments.

# Examples

This method effectively want to transform the following:
```julia
function f(args...)
...
end
```

into

```julia
@generated function f(__kwargs__::NamedTuple{__names__}, args...) where {__names__}
kwargs_unwrapped = [:($n = getproperty(__kwargs__, $n)) for n in __names__]
return quote
$(kwargs_unwrapped...)
...
end
end
```

For example:
```julia
julia> using MacroTools

julia> expr = :(f(x) = x + y) # `y` isn't defined!
:(f(x) = begin
#= REPL[40]:1 =#
x + y
end)

julia> modeldef = MacroTools.splitdef(expr);

julia> # This is pretty ugly, but what can we do.
expr_result = DynamicPPL.make_inlined_kwcall(MacroTools.splitdef(expr))
:(#= /home/tor/Projects/public/DynamicPPL.jl/src/compiler.jl:667 =# @generated function f(var"##kwargs#467"::NamedTuple{var"##names#468"}, x; ) where var"##names#468"
kwargs_unwrapped = Expr(:block)
for n = var"##names#468"
push!(kwargs_unwrapped.args, Expr(:(=), n, Expr(:call, :getproperty, Symbol("##kwargs#467"), QuoteNode(n))))
end
return Expr(:block, kwargs_unwrapped, $(Expr(:quote, quote
#= REPL[40]:1 =#
x + y
end)))
end)

julia> # Let's evaluate it and check that it indeed does what we expect.
eval(expr_result)
f (generic function with 1 method)

julia> f((y=100,), 1) # (✓) Works!
101

julia> f(NamedTuple(), 1) # (×) Fails!
ERROR: UndefVarError: `y` not defined
Stacktrace:
[1] macro expansion
@ ./REPL[40]:1 [inlined]
[2] macro expansion
@ ./none:0 [inlined]
[3] f(kwargs#473::NamedTuple{(), Tuple{}}, x::Int64)
@ Main ./none:0
[4] top-level scope
@ REPL[51]:1
```
"""
function make_inlined_kwcall(modeldef)
modeldef = deepcopy(modeldef)
# Generate some unique symbols to avoid name clashes.
kwargs = gensym(:kwargs)
names = gensym(:names)
# Insert the kwargs at the beginning of the argument list.
modeldef[:args] = vcat(
[:($kwargs::NamedTuple{$names})],
modeldef[:args],
)
modeldef[:whereparams] = vcat(
[:($names)],
[modeldef[:whereparams]...],
)

# Create body with the inlined kwargs.
modeldef[:body] = MacroTools.@q begin
kwargs_unwrapped = Expr(:block)
for n in $names
push!(
kwargs_unwrapped.args,
Expr(:(=), n, Expr(:call, :getproperty, $(QuoteNode(kwargs)), QuoteNode(n)))
)
end

return Expr(
:block,
kwargs_unwrapped,
$(Meta.quot(modeldef[:body]))
)
end

return :(@generated $(MacroTools.combinedef(modeldef)))
end



function warn_empty(body)
if all(l -> isa(l, LineNumberNode), body.args)
@warn("Model definition seems empty, still continue.")
Expand Down
2 changes: 1 addition & 1 deletion src/model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ Evaluate the `model` with the arguments matching the given `context` and `varinf
"""
function _evaluate!!(model::Model, varinfo::AbstractVarInfo, context::AbstractContext)
args, kwargs = make_evaluate_args_and_kwargs(model, varinfo, context)
return model.f(args...; kwargs...)
return model.f(NamedTuple(kwargs), args...)
end

"""
Expand Down