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
11 changes: 11 additions & 0 deletions src/codeedges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,11 @@ end
function add_typedefs!(isrequired, src::CodeInfo, edges::CodeEdges, (typedef_blocks, typedef_names), norequire)
changed = false
stmts = src.code
defaultctors = Tuple{Int,BitSet}[]
for (i, stmt) in pairs(stmts)
is_defaultctors_call(stmt) || continue
push!(defaultctors, (i, terminal_preds(i, edges)))
end
idx = 1
while idx < length(stmts)
stmt = stmts[idx]
Expand All @@ -1049,6 +1054,12 @@ function add_typedefs!(isrequired, src::CodeInfo, edges::CodeEdges, (typedef_blo
end
end
end
for (ctor, preds) in defaultctors
ctor ∈ norequire && continue
any(p -> p ∈ typedefr, preds) || continue
changed |= !isrequired[ctor]
isrequired[ctor] = true
end
idx = last(typedefr) + 1
continue
end
Expand Down
15 changes: 15 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ function callee_matches(f, mod, sym)
return false
end

# Recognize the default-constructor call emitted when lowering a struct definition.
function is_defaultctors_call(@nospecialize(stmt))
isexpr(stmt, :call) || return false
f = stmt.args[1]
is_global_ref(f, Core, :_defaultctors) && return true
is_global_ref(f, Base, :_defaultctors) && return true
@static if isdefined(Core, :_defaultctors)
is_quotenode_egal(f, Core._defaultctors) && return true
end
@static if isdefined(Base, :_defaultctors)
is_quotenode_egal(f, Base._defaultctors) && return true
end
return false
end

function getrhs(@nospecialize(stmt))
lhs_rhs = get_lhs_rhs(stmt)
return lhs_rhs === nothing ? stmt : lhs_rhs[2]
Expand Down
33 changes: 26 additions & 7 deletions test/codeedges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module codeedges
using LoweredCodeUtils
using LoweredCodeUtils.JuliaInterpreter
using LoweredCodeUtils: CC
using LoweredCodeUtils: callee_matches, istypedef, exclude_named_typedefs
using LoweredCodeUtils: callee_matches, istypedef, exclude_named_typedefs, is_defaultctors_call
using JuliaInterpreter: is_global_ref, is_quotenode
using Test

Expand Down Expand Up @@ -41,12 +41,6 @@ function minimal_evaluation(predicate, src::Core.CodeInfo, edges::CodeEdges; kwa
return isrequired
end

# Recognize the default-constructor call emitted when lowering a struct definition.
# `_defaultctors` lived in `Core` through 1.12 but moved to `Base` (JuliaLang/julia, see
# base/essentials.jl), so accept either home.
is_defaultctors_call(@nospecialize stmt) = Meta.isexpr(stmt, :call) &&
(is_global_ref(stmt.args[1], Base, :_defaultctors) || is_global_ref(stmt.args[1], Core, :_defaultctors))

function allmissing(mod::Module, names)
for name in names
isdefined(mod, name) && return false
Expand Down Expand Up @@ -305,6 +299,31 @@ module ModSelective end
@test isa(NoParam(), NoParam)
end

# Requiring a type definition must include its generated default constructors.
@static if VERSION ≥ v"1.12-"
let mod = Module(:ModRequiredDefaultConstructors)
src = Meta.lower(mod, quote
struct WithDefaultConstructor
value
end
struct UnrelatedDefaultConstructor
value
end
end).args[1]
edges = CodeEdges(mod, src)
isrequired = lines_required(findfirst(istypedef, src.code), src, edges)
ctorpcs = findall(is_defaultctors_call, src.code)
@test length(ctorpcs) == 2
@test isrequired[first(ctorpcs)]
@test !isrequired[last(ctorpcs)]
selective_eval_fromstart!(Frame(mod, src), isrequired, true)
T = @invokelatest mod.WithDefaultConstructor
value = Base.invokelatest(T, 7)
@test value.value == 7
@test !isdefined(mod, :UnrelatedDefaultConstructor)
end
end

# Parametric
ex = quote
struct Struct{T} <: StructParent{T,1}
Expand Down
Loading