Skip to content

Commit 807b378

Browse files
committed
Actually step into generators for generated functions
1 parent 8d7cce6 commit 807b378

File tree

2 files changed

+41
-16
lines changed

2 files changed

+41
-16
lines changed

src/construct.jl

+20-10
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,13 @@ end
4747

4848
get_source(meth::Method) = Base.uncompressed_ast(meth)
4949

50-
function get_source(g::GeneratedFunctionStub, env)
51-
b = g(env..., g.argnames...)
52-
b isa CodeInfo && return b
53-
return eval(b)
54-
end
50+
# The following creates the CodeInfo holding the generated expression. May be
51+
# useful if we decide to revert to that behavior.
52+
# function get_source(g::GeneratedFunctionStub, env)
53+
# b = @which g(env..., g.argnames...)
54+
# b isa CodeInfo && return b
55+
# return eval(b)
56+
# end
5557

5658
function copy_codeinfo(code::CodeInfo)
5759
@static if VERSION < v"1.1.0-DEV.762"
@@ -129,6 +131,7 @@ function prepare_framecode(method::Method, @nospecialize(argtypes); enter_genera
129131
framecode = get(framedict, method, nothing)
130132
end
131133
if framecode === nothing
134+
method0 = method
132135
if is_generated(method) && !enter_generated
133136
# If we're stepping into a staged function, we need to use
134137
# the specialization, rather than stepping through the
@@ -138,16 +141,20 @@ function prepare_framecode(method::Method, @nospecialize(argtypes); enter_genera
138141
generator = false
139142
else
140143
if is_generated(method)
141-
code = get_source(method.generator, lenv)
144+
g = method.generator
145+
methsg = collect(methods(g.gen))
146+
@assert length(methsg) == 1
147+
method = first(methsg)
148+
code = get_source(method)
142149
generator = true
143150
else
144151
code = get_source(method)
145152
generator = false
146153
end
147154
end
148155
framecode = FrameCode(method, code; generator=generator)
149-
if is_generated(method) && !enter_generated
150-
genframedict[(method, argtypes)] = framecode
156+
if is_generated(method0) && !generator
157+
genframedict[(method0, argtypes)] = framecode
151158
else
152159
framedict[method] = framecode
153160
end
@@ -225,8 +232,10 @@ function prepare_call(@nospecialize(f), allargs; enter_generated = false)
225232
isa(ret, Compiled) && return ret
226233
# Typical return
227234
framecode, lenv = ret
228-
if is_generated(method) && enter_generated
235+
if framecode.generator
229236
args = Any[_Typeof(a) for a in args]
237+
selfarg = Base.unwrap_unionall(scopeof(framecode).sig).parameters[1] # #self#
238+
args = Any[selfarg, lenv..., args...]
230239
end
231240
return framecode, args, lenv, argtypes
232241
end
@@ -294,8 +303,9 @@ static parameters `lenv`. See [`JuliaInterpreter.prepare_call`](@ref) for inform
294303
"""
295304
function prepare_frame(framecode::FrameCode, args::Vector{Any}, lenv::SimpleVector; enter_generated=false)
296305
s = scopeof(framecode)
297-
if isa(s, Method) && is_generated(s) && enter_generated
306+
if framecode.generator
298307
args = Any[_Typeof(a) for a in args]
308+
args = Any[Base.unwrap_unionall(s.sig).parameters[1], lenv..., args...] # first is #self#
299309
end
300310
framedata = prepare_framedata(framecode, args)
301311
resize!(framedata.sparams, length(lenv))

test/debug.jl

+21-6
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,14 @@ step_through(f, args...; kwargs...) = step_through_frame(() -> enter_call(f, arg
2929
step_through(expr::Expr) = step_through_frame(() -> enter_call_expr(expr))
3030

3131
@generated function generatedfoo(x)
32-
:(return x)
32+
# A deliberately-complicated way to perform this operation
33+
ex = Expr(:block)
34+
push!(ex.args, :(return x))
35+
return ex
3336
end
3437
callgenerated() = generatedfoo(1)
3538
@generated function generatedparams(a::Array{T,N}) where {T,N}
39+
zz = 1
3640
:(return ($T,$N))
3741
end
3842
callgeneratedparams() = generatedparams([1 2; 3 4])
@@ -99,26 +103,37 @@ struct B{T} end
99103
frame = enter_call_expr(:($(callgenerated)()))
100104
f, pc = debug_command(frame, :sg)
101105
@test isa(pc, BreakpointRef)
102-
@test JuliaInterpreter.scopeof(f).name == :generatedfoo
103106
stmt = JuliaInterpreter.pc_expr(f)
104-
@test stmt.head == :return
107+
if stmt === nothing
108+
f, pc = debug_command(f, :se)
109+
stmt = JuliaInterpreter.pc_expr(f)
110+
end
111+
@test stmt.head == :(=)
105112
f2, pc = debug_command(f, :finish)
106113
@test JuliaInterpreter.scopeof(f2).name == :callgenerated
107114
# Now finish the regular function
108115
@test debug_command(frame, :finish) === nothing
109116
@test frame.callee === nothing
110-
@test get_return(frame) === Int
117+
excmp = quote return x end
118+
deleteat!(excmp.args, 1) # delete LineNumberNode
119+
@test get_return(frame) == excmp
111120

112121
# Parametric generated function (see #157)
113122
frame = fr = JuliaInterpreter.enter_call(callgeneratedparams)
114123
while fr.pc < JuliaInterpreter.nstatements(fr.framecode) - 1
115124
fr, pc = debug_command(fr, :se)
116125
end
117126
fr, pc = debug_command(fr, :sg)
118-
@test JuliaInterpreter.scopeof(fr).name == :generatedparams
127+
JuliaInterpreter.finish!(fr)
128+
vzz = filter(v -> v.name == :zz, JuliaInterpreter.locals(fr))[1]
129+
@test vzz.value == 1
119130
fr, pc = debug_command(fr, :finish)
120131
@test debug_command(fr, :finish) === nothing
121-
@test JuliaInterpreter.get_return(fr) == (Int, 2)
132+
ex = get_return(fr)
133+
isa(ex.args[1], LineNumberNode) && deleteat!(ex.args, 1)
134+
excmp = quote return ($Int, 2) end
135+
deleteat!(excmp.args, 1)
136+
@test ex == excmp
122137
end
123138

124139
@testset "Optional arguments" begin

0 commit comments

Comments
 (0)