diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 5e4387267ff4f..639e8aaef4566 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2189,12 +2189,16 @@ function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes:: head = e.head if head === :static_parameter n = e.args[1]::Int + nothrow = false if 1 <= n <= length(sv.sptypes) rt = sv.sptypes[n] + if is_maybeundefsp(rt) + rt = unwrap_maybeundefsp(rt) + else + nothrow = true + end end - if !isa(rt, Const) - merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow=false)) - end + merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow)) return rt elseif head === :boundscheck if isa(sv, InferenceState) @@ -2456,8 +2460,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp elseif isexpr(sym, :static_parameter) n = sym.args[1]::Int if 1 <= n <= length(sv.sptypes) - spty = sv.sptypes[n] - if isa(spty, Const) + if !is_maybeundefsp(sv.sptypes, n) t = Const(true) end end diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 0ae7989c82c76..a1e5096772231 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -348,15 +348,91 @@ function InferenceState(result::InferenceResult, cache::Symbol, interp::Abstract return InferenceState(result, src, cache, interp) end +""" + constrains_param(var::TypeVar, sig, covariant::Bool) + +Check if `var` will be constrained to have a definite value +in any concrete leaftype subtype of `sig`. + +It is used as a helper to determine whether type intersection is guaranteed to be able to +find a value for a particular type parameter. +A necessary condition for type intersection to not assign a parameter is that it only +appears in a `Union[All]` and during subtyping some other union component (that does not +constrain the type parameter) is selected. +""" +function constrains_param(var::TypeVar, @nospecialize(typ), covariant::Bool) + typ === var && return true + while typ isa UnionAll + covariant && constrains_param(var, typ.var.ub, covariant) && return true + # typ.var.lb doesn't constrain var + typ = typ.body + end + if typ isa Union + # for unions, verify that both options would constrain var + ba = constrains_param(var, typ.a, covariant) + bb = constrains_param(var, typ.b, covariant) + (ba && bb) && return true + elseif typ isa DataType + # return true if any param constrains var + fc = length(typ.parameters) + if fc > 0 + if typ.name === Tuple.name + # vararg tuple needs special handling + for i in 1:(fc - 1) + p = typ.parameters[i] + constrains_param(var, p, covariant) && return true + end + lastp = typ.parameters[fc] + vararg = unwrap_unionall(lastp) + if vararg isa Core.TypeofVararg && isdefined(vararg, :N) + constrains_param(var, vararg.N, covariant) && return true + # T = vararg.parameters[1] doesn't constrain var + else + constrains_param(var, lastp, covariant) && return true + end + else + for i in 1:fc + p = typ.parameters[i] + constrains_param(var, p, false) && return true + end + end + end + end + return false +end + +""" + MaybeUndefSP(typ) + is_maybeundefsp(typ) -> Bool + unwrap_maybeundefsp(typ) -> Any + +A special wrapper that represents a static parameter that could be undefined at runtime. +This does not participate in the native type system nor the inference lattice, +and it thus should be always unwrapped when performing any type or lattice operations on it. +""" +struct MaybeUndefSP + typ + MaybeUndefSP(@nospecialize typ) = new(typ) +end +is_maybeundefsp(@nospecialize typ) = isa(typ, MaybeUndefSP) +unwrap_maybeundefsp(@nospecialize typ) = isa(typ, MaybeUndefSP) ? typ.typ : typ +is_maybeundefsp(sptypes::Vector{Any}, idx::Int) = is_maybeundefsp(sptypes[idx]) +unwrap_maybeundefsp(sptypes::Vector{Any}, idx::Int) = unwrap_maybeundefsp(sptypes[idx]) + +const EMPTY_SPTYPES = Any[] + function sptypes_from_meth_instance(linfo::MethodInstance) - toplevel = !isa(linfo.def, Method) - if !toplevel && isempty(linfo.sparam_vals) && isa(linfo.def.sig, UnionAll) + def = linfo.def + isa(def, Method) || return EMPTY_SPTYPES # toplevel + sig = def.sig + if isempty(linfo.sparam_vals) + isa(sig, UnionAll) || return EMPTY_SPTYPES # linfo is unspecialized sp = Any[] - sig = linfo.def.sig - while isa(sig, UnionAll) - push!(sp, sig.var) - sig = sig.body + sig′ = sig + while isa(sig′, UnionAll) + push!(sp, sig′.var) + sig′ = sig′.body end else sp = collect(Any, linfo.sparam_vals) @@ -364,7 +440,8 @@ function sptypes_from_meth_instance(linfo::MethodInstance) for i = 1:length(sp) v = sp[i] if v isa TypeVar - temp = linfo.def.sig + maybe_undef = !constrains_param(v, linfo.specTypes, true) + temp = sig for j = 1:i-1 temp = temp.body end @@ -402,12 +479,13 @@ function sptypes_from_meth_instance(linfo::MethodInstance) tv = TypeVar(v.name, lb, ub) ty = UnionAll(tv, Type{tv}) end + @label ty_computed + maybe_undef && (ty = MaybeUndefSP(ty)) elseif isvarargtype(v) ty = Int else ty = Const(v) end - @label ty_computed sp[i] = ty end return sp diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 2c26848ac1ca1..473ee3899b9da 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -267,9 +267,9 @@ function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospe if isa(stmt, Expr) (; head, args) = stmt if head === :static_parameter - etyp = (isa(src, IRCode) ? src.sptypes : src.ir.sptypes)[args[1]::Int] # if we aren't certain enough about the type, it might be an UndefVarError at runtime - nothrow = isa(etyp, Const) + sptypes = isa(src, IRCode) ? src.sptypes : src.ir.sptypes + nothrow = !is_maybeundefsp(sptypes, args[1]::Int) return (true, nothrow, nothrow) end if head === :call @@ -377,7 +377,7 @@ function argextype( sptypes::Vector{Any}, slottypes::Vector{Any}) if isa(x, Expr) if x.head === :static_parameter - return sptypes[x.args[1]::Int] + return unwrap_maybeundefsp(sptypes, x.args[1]::Int) elseif x.head === :boundscheck return Bool elseif x.head === :copyast diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index 289cea14dc01d..60d04ff1bf601 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -216,7 +216,7 @@ end function typ_for_val(@nospecialize(x), ci::CodeInfo, sptypes::Vector{Any}, idx::Int, slottypes::Vector{Any}) if isa(x, Expr) if x.head === :static_parameter - return sptypes[x.args[1]::Int] + return unwrap_maybeundefsp(sptypes, x.args[1]::Int) elseif x.head === :boundscheck return Bool elseif x.head === :copyast diff --git a/base/compiler/ssair/verify.jl b/base/compiler/ssair/verify.jl index 7dc268f648bcc..b6c90c4528f23 100644 --- a/base/compiler/ssair/verify.jl +++ b/base/compiler/ssair/verify.jl @@ -268,7 +268,7 @@ function verify_ir(ir::IRCode, print::Bool=true, elseif stmt.head === :foreigncall isforeigncall = true elseif stmt.head === :isdefined && length(stmt.args) == 1 && - (stmt.args[1] isa GlobalRef || (stmt.args[1] isa Expr && stmt.args[1].head === :static_parameter)) + (stmt.args[1] isa GlobalRef || isexpr(stmt.args[1], :static_parameter)) # a GlobalRef or static_parameter isdefined check does not evaluate its argument continue elseif stmt.head === :call diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index f1216371d0b27..0b8128e77eed7 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -1970,54 +1970,11 @@ function detect_unbound_args(mods...; return collect(ambs) end -# find if var will be constrained to have a definite value -# in any concrete leaftype subtype of typ -function constrains_param(var::TypeVar, @nospecialize(typ), covariant::Bool) - typ === var && return true - while typ isa UnionAll - covariant && constrains_param(var, typ.var.ub, covariant) && return true - # typ.var.lb doesn't constrain var - typ = typ.body - end - if typ isa Union - # for unions, verify that both options would constrain var - ba = constrains_param(var, typ.a, covariant) - bb = constrains_param(var, typ.b, covariant) - (ba && bb) && return true - elseif typ isa DataType - # return true if any param constrains var - fc = length(typ.parameters) - if fc > 0 - if typ.name === Tuple.name - # vararg tuple needs special handling - for i in 1:(fc - 1) - p = typ.parameters[i] - constrains_param(var, p, covariant) && return true - end - lastp = typ.parameters[fc] - vararg = Base.unwrap_unionall(lastp) - if vararg isa Core.TypeofVararg && isdefined(vararg, :N) - constrains_param(var, vararg.N, covariant) && return true - # T = vararg.parameters[1] doesn't constrain var - else - constrains_param(var, lastp, covariant) && return true - end - else - for i in 1:fc - p = typ.parameters[i] - constrains_param(var, p, false) && return true - end - end - end - end - return false -end - function has_unbound_vars(@nospecialize sig) while sig isa UnionAll var = sig.var sig = sig.body - if !constrains_param(var, sig, true) + if !Core.Compiler.constrains_param(var, sig, true) return true end end diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index c8e2a1e3f5105..7423ee228e378 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -748,8 +748,13 @@ end # unknown :static_parameter should taint :nothrow # https://github.com/JuliaLang/julia/issues/46771 unknown_sparam_throw(::Union{Nothing, Type{T}}) where T = (T; nothing) +unknown_sparam_nothrow1(x::Ref{T}) where T = (T; nothing) +unknown_sparam_nothrow2(x::Ref{Ref{T}}) where T = (T; nothing) @test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type{Int},))) +@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type{<:Integer},))) +@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type,))) @test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Nothing,))) - -unknown_sparam_nothrow(x::Ref{T}) where {T} = (T; nothing) -@test_broken Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_nothrow, (Ref,))) +@test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Union{Type{Int},Nothing},))) +@test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Any,))) +@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_nothrow1, (Ref,))) +@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_nothrow2, (Ref{Ref{T}} where T,))) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index d35d0e4a68117..23f1f5da2f9f3 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4735,3 +4735,16 @@ g_no_bail_effects_any(x::Any) = f_no_bail_effects_any(x) # issue #48374 @test (() -> Union{<:Nothing})() == Nothing + +# :static_parameter accuracy +unknown_sparam_throw(::Union{Nothing, Type{T}}) where T = @isdefined(T) ? T::Type : nothing +unknown_sparam_nothrow1(x::Ref{T}) where T = @isdefined(T) ? T::Type : nothing +unknown_sparam_nothrow2(x::Ref{Ref{T}}) where T = @isdefined(T) ? T::Type : nothing +@test only(Base.return_types(unknown_sparam_throw, (Type{Int},))) == Type{Int} +@test only(Base.return_types(unknown_sparam_throw, (Type{<:Integer},))) == Type{<:Integer} +@test only(Base.return_types(unknown_sparam_throw, (Type,))) == Type +@test_broken only(Base.return_types(unknown_sparam_throw, (Nothing,))) === Nothing +@test_broken only(Base.return_types(unknown_sparam_throw, (Union{Type{Int},Nothing},))) === Union{Nothing,Type{Int}} +@test only(Base.return_types(unknown_sparam_throw, (Any,))) === Union{Nothing,Type} +@test only(Base.return_types(unknown_sparam_nothrow1, (Ref,))) === Type +@test only(Base.return_types(unknown_sparam_nothrow2, (Ref{Ref{T}} where T,))) === Type