Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
28 changes: 22 additions & 6 deletions src/codeedges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1007,13 +1007,29 @@ function find_typedefs(src::CodeInfo)
if istypedef(stmt) && !isanonymous_typedef(stmt::Expr)
stmt = stmt::Expr
r = typedef_range(src, i)
push!(typedef_blocks, r)
name = stmt.head === :call ? stmt.args[3] : stmt.args[1]
if isa(name, QuoteNode)
name = name.value
if is_resolve_typegroup_call(stmt)
# A typegroup defines one type per `declare_const` in the block;
# record each name against the same statement range.
for j in r
s = src.code[j]
is_declare_const(s) || continue
name = (s::Expr).args[3]
if isa(name, QuoteNode)
name = name.value
end
isa(name, Symbol) || continue
push!(typedef_blocks, r)
push!(typedef_names, name)
end
else
push!(typedef_blocks, r)
name = stmt.head === :call ? stmt.args[3] : stmt.args[1]
if isa(name, QuoteNode)
name = name.value
end
isa(name, Symbol) || @show src i r stmt
push!(typedef_names, name::Symbol)
end
isa(name, Symbol) || @show src i r stmt
push!(typedef_names, name::Symbol)
i = last(r)+1
else
i += 1
Expand Down
40 changes: 40 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,20 @@ function isanonymous_typedef(@nospecialize stmt)
return false
end

# Recognize the `Core.resolve_typegroup` call that creates the types of a type
# group. On Julia versions where `struct` definitions lower through the
# typegroup mechanism, ordinary structs also produce this form (with a
# single-element group); `typegroup` blocks produce multi-element groups.
function is_resolve_typegroup_call(@nospecialize(stmt))
isexpr(stmt, :call) || return false
f = (stmt::Expr).args[1]
is_global_ref(f, Core, :resolve_typegroup) && return true
@static if isdefined(Core, :resolve_typegroup)
is_quotenode_egal(f, Core.resolve_typegroup) && return true
end
return false
end

function istypedef(stmt)
isa(stmt, Expr) || return false
stmt = getrhs(stmt)
Expand All @@ -215,6 +229,7 @@ function istypedef(stmt)
end
end
end
is_resolve_typegroup_call(stmt) && return true
isanonymous_typedef(stmt) && return true
return false
end
Expand Down Expand Up @@ -256,6 +271,31 @@ function typedef_range(src::CodeInfo, idx)
is_declare_global(s) && break
istart -= 1
end
if is_resolve_typegroup_call(getrhs(stmt))
# Typegroup form: `TypeVar` bindings and struct-info svecs, then
# `resolve_typegroup(mod, typevars, infos, olds)`, then `getfield`
# extractions and one `declare_const` per type, closed by `latestworld`.
# Ordinary struct definitions open with a `global` marker; `typegroup`
# blocks do not, so if none was found fall back to extending the range
# backwards to the previous statement that cannot be part of the group.
if istart < 1
istart = idx
for j = idx-1:-1:1
s = src.code[j]
(isexpr(s, :latestworld) || isexpr(s, :method) || isexpr(s, :thunk) ||
is_return(s)) && break
istart = j
end
end
iend, n = idx, length(src.code)
while iend <= n
s = src.code[iend]
(isexpr(s, :latestworld) || isexpr(s, :global) || is_return(s)) && break
iend += 1
end
iend <= n || error("no final latestworld found for typegroup")
return istart:iend-1
end
istart >= 1 || error("no initial :global or declare_global found")
iend, n = idx, length(src.code)
have_typebody = have_equivtypedef = false
Expand Down
29 changes: 29 additions & 0 deletions test/codeedges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -584,4 +584,33 @@ end
end
end

# Typegroup blocks (Julia versions where `Core.resolve_typegroup` exists).
# Ordinary structs also lower through the typegroup mechanism on Julia ≥ 1.14
# and are covered by the cases above; this checks a multi-type group, which has
# no leading `global` marker and defines several names from one statement range.
@static if isdefined(Core, :resolve_typegroup)
@testset "typegroup blocks" begin
m = Module(:TypegroupMock)
ex = Expr(:typegroup, Expr(:block,
:(struct TGA; b::Union{TGB,Nothing}; end),
:(struct TGB; a::TGA; end)))
lwr = Meta.lower(m, ex)
if lwr isa Expr && lwr.head === :thunk &&
any(LoweredCodeUtils.is_resolve_typegroup_call, (first(lwr.args)::Core.CodeInfo).code)
src = first(lwr.args)::Core.CodeInfo
edges = CodeEdges(m, src)
idx = findfirst(LoweredCodeUtils.is_resolve_typegroup_call, src.code)
@test istypedef(src.code[idx])
blocks, names = LoweredCodeUtils.find_typedefs(src)
@test names == [:TGA, :TGB]
@test length(blocks) == 2 && blocks[1] == blocks[2]
isrq = lines_required!(istypedef.(src.code), src, edges)
selective_eval_fromstart!(Frame(m, src), isrq, #=istoplevel=#true)
@test @invokelatest(isdefined(m, :TGA))
@test @invokelatest(isdefined(m, :TGB))
@test @invokelatest(fieldtype(m.TGB, :a)) === @invokelatest(m.TGA)
end
end
end

end # module codeedges
Loading