diff --git a/src/codeedges.jl b/src/codeedges.jl index 395d248..9fc03c8 100644 --- a/src/codeedges.jl +++ b/src/codeedges.jl @@ -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] @@ -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 diff --git a/src/utils.jl b/src/utils.jl index 0e28493..7e6b652 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -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] diff --git a/test/codeedges.jl b/test/codeedges.jl index 1de2874..a507a09 100644 --- a/test/codeedges.jl +++ b/test/codeedges.jl @@ -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 @@ -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 @@ -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}