Skip to content

Commit 57c50f1

Browse files
committed
Fixed a bug in replace returns (#352)
On master we have the following behavior for a test-case in Turing.jl: ```julia julia> @macroexpand @model empty_model() = begin x = 1; end quote function empty_model(__model__::DynamicPPL.Model, __varinfo__::DynamicPPL.AbstractVarInfo, __context__::DynamicPPL.AbstractContext; ) #= REPL[5]:1 =# begin #= REPL[5]:1 =# #= REPL[5]:1 =# return (x = 1, __varinfo__) end end begin $(Expr(:meta, :doc)) function empty_model(; ) #= REPL[5]:1 =# return (DynamicPPL.Model)(:empty_model, empty_model, NamedTuple(), NamedTuple()) end end end ``` Notice the `return` statement: it converted the statement `x = 1` which returns `1` into an attempt at a `NamedTuple{(:x, :__varinfo__)}`. On Julia 1.6 we don't really notice much of difference, because `first` and `last` will have the same behavior, but on Julia 1.3 the tests would fail in TuringLang/Turing.jl#1726 since "implicit" names in construction of `NamedTuple` isn't supported. This PR addresses this issue by simply capturing the return-value in separate variable, which is then combined with `__varinfo__` in a `Tuple` at the end. This should both fail and succeed whenever standard Julia code would.
1 parent 28c6004 commit 57c50f1

File tree

3 files changed

+18
-10
lines changed

3 files changed

+18
-10
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "DynamicPPL"
22
uuid = "366bfd00-2699-11ea-058f-f148b4cae6d8"
3-
version = "0.17.0"
3+
version = "0.17.1"
44

55
[deps]
66
AbstractMCMC = "80f14c24-f653-4e6a-9b94-39d6b0f70001"

src/compiler.jl

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -518,16 +518,17 @@ function replace_returns(e::Expr)
518518
end
519519

520520
if Meta.isexpr(e, :return)
521-
# NOTE: `return` always has an argument. In the case of
522-
# an empty `return`, the lowered expression will be `return nothing`.
523-
# Hence we don't need any special handling for empty returns.
524-
retval_expr = if length(e.args) > 1
525-
Expr(:tuple, e.args...)
526-
else
527-
e.args[1]
521+
# We capture the original return-value in `retval` and return
522+
# a `Tuple{typeof(retval),typeof(__varinfo__)}`.
523+
# If we don't capture the return-value separately, cases such as
524+
# `return x = 1` will result in `(x = 1, __varinfo__)` which will
525+
# mistakenly attempt to construct a `NamedTuple` (which fails on Julia 1.3
526+
# and is not our intent).
527+
@gensym retval
528+
return quote
529+
$retval = $(e.args...)
530+
return $retval, __varinfo__
528531
end
529-
530-
return :(return ($retval_expr, __varinfo__))
531532
end
532533

533534
return Expr(e.head, map(replace_returns, e.args)...)

test/compiler.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,13 @@ end
546546
end
547547

548548
@testset "return value" begin
549+
# Make sure that a return-value of `x = 1` isn't combined into
550+
# an attempt at a `NamedTuple` of the form `(x = 1, __varinfo__)`.
551+
@model empty_model() = return x = 1
552+
empty_vi = VarInfo()
553+
retval_and_vi = DynamicPPL.evaluate!!(empty_model(), empty_vi, SamplingContext())
554+
@test retval_and_vi isa Tuple{Int,typeof(empty_vi)}
555+
549556
# Even if the return-value is `AbstractVarInfo`, we should return
550557
# a `Tuple` with `AbstractVarInfo` in the second component too.
551558
@model demo() = return __varinfo__

0 commit comments

Comments
 (0)