Skip to content

Commit 86b9df2

Browse files
aviateskKenostaticfloat
authored andcommitted
effects: taint :nothrow effect on unknown :static_parameter (#46791)
* effects: taint `:nothrow` effect on unknown `:static_parameter` (conservatively) With this commit, we taint `:nothrow` effect property correctly on access to unknown `:static_parameter`, e.g.: ```julia unknown_sparam_throw(::Union{Nothing, Type{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, ((Nothing,)))) ``` This commit implements a very conservative analysis, and thus there is a room for improvement still, e.g.: ```julia unknown_sparam_nothrow(x::Ref{T}) where {T} = (T; nothing) @test_broken Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_nothrow, (Ref,))) ``` * inference: improve `:nothrow` modeling for `:static_parameter` (#46820) * Fix test with free type params * Test: Ignore ::Type{T} in detect_unbounded These are only technically unbounded because of the existence of ill-formed types. However, this function is supposed to be an API sanity check and ordinary users should never have ill-formed types, so for the purpose we want here, allow unboundedness in Type{T}. --------- Co-authored-by: Keno Fischer <[email protected]> Co-authored-by: Elliot Saba <[email protected]> (cherry picked from commit b5d17ea)
1 parent 5453936 commit 86b9df2

File tree

8 files changed

+135
-60
lines changed

8 files changed

+135
-60
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,9 +2048,17 @@ function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes::
20482048
head = e.head
20492049
if head === :static_parameter
20502050
n = e.args[1]::Int
2051+
nothrow = false
20512052
if 1 <= n <= length(sv.sptypes)
20522053
rt = sv.sptypes[n]
2054+
if is_maybeundefsp(rt)
2055+
rt = unwrap_maybeundefsp(rt)
2056+
else
2057+
nothrow = true
2058+
end
20532059
end
2060+
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow))
2061+
return rt
20542062
elseif head === :boundscheck
20552063
if isa(sv, InferenceState)
20562064
stmt = sv.src.code[sv.currpc]
@@ -2295,8 +2303,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp
22952303
elseif isexpr(sym, :static_parameter)
22962304
n = sym.args[1]::Int
22972305
if 1 <= n <= length(sv.sptypes)
2298-
spty = sv.sptypes[n]
2299-
if isa(spty, Const)
2306+
if !is_maybeundefsp(sv.sptypes, n)
23002307
t = Const(true)
23012308
end
23022309
end

base/compiler/inferencestate.jl

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -348,15 +348,100 @@ function InferenceState(result::InferenceResult, cache::Symbol, interp::Abstract
348348
return InferenceState(result, src, cache, interp)
349349
end
350350

351+
"""
352+
constrains_param(var::TypeVar, sig, covariant::Bool, type_constrains::Bool)
353+
354+
Check if `var` will be constrained to have a definite value
355+
in any concrete leaftype subtype of `sig`.
356+
357+
It is used as a helper to determine whether type intersection is guaranteed to be able to
358+
find a value for a particular type parameter.
359+
A necessary condition for type intersection to not assign a parameter is that it only
360+
appears in a `Union[All]` and during subtyping some other union component (that does not
361+
constrain the type parameter) is selected.
362+
363+
The `type_constrains` flag determines whether Type{T} is considered to be constraining
364+
`T`. This is not true in general, because of the existence of types with free type
365+
parameters, however, some callers would like to ignore this corner case.
366+
"""
367+
function constrains_param(var::TypeVar, @nospecialize(typ), covariant::Bool, type_constrains::Bool=false)
368+
typ === var && return true
369+
while typ isa UnionAll
370+
covariant && constrains_param(var, typ.var.ub, covariant, type_constrains) && return true
371+
# typ.var.lb doesn't constrain var
372+
typ = typ.body
373+
end
374+
if typ isa Union
375+
# for unions, verify that both options would constrain var
376+
ba = constrains_param(var, typ.a, covariant, type_constrains)
377+
bb = constrains_param(var, typ.b, covariant, type_constrains)
378+
(ba && bb) && return true
379+
elseif typ isa DataType
380+
# return true if any param constrains var
381+
fc = length(typ.parameters)
382+
if fc > 0
383+
if typ.name === Tuple.name
384+
# vararg tuple needs special handling
385+
for i in 1:(fc - 1)
386+
p = typ.parameters[i]
387+
constrains_param(var, p, covariant, type_constrains) && return true
388+
end
389+
lastp = typ.parameters[fc]
390+
vararg = unwrap_unionall(lastp)
391+
if vararg isa Core.TypeofVararg && isdefined(vararg, :N)
392+
constrains_param(var, vararg.N, covariant, type_constrains) && return true
393+
# T = vararg.parameters[1] doesn't constrain var
394+
else
395+
constrains_param(var, lastp, covariant, type_constrains) && return true
396+
end
397+
else
398+
if typ.name === typename(Type) && typ.parameters[1] === var && var.ub === Any
399+
# Types with free type parameters are <: Type cause the typevar
400+
# to be unconstrained because Type{T} with free typevars is illegal
401+
return type_constrains
402+
end
403+
for i in 1:fc
404+
p = typ.parameters[i]
405+
constrains_param(var, p, false, type_constrains) && return true
406+
end
407+
end
408+
end
409+
end
410+
return false
411+
end
412+
413+
"""
414+
MaybeUndefSP(typ)
415+
is_maybeundefsp(typ) -> Bool
416+
unwrap_maybeundefsp(typ) -> Any
417+
418+
A special wrapper that represents a static parameter that could be undefined at runtime.
419+
This does not participate in the native type system nor the inference lattice,
420+
and it thus should be always unwrapped when performing any type or lattice operations on it.
421+
"""
422+
struct MaybeUndefSP
423+
typ
424+
MaybeUndefSP(@nospecialize typ) = new(typ)
425+
end
426+
is_maybeundefsp(@nospecialize typ) = isa(typ, MaybeUndefSP)
427+
unwrap_maybeundefsp(@nospecialize typ) = isa(typ, MaybeUndefSP) ? typ.typ : typ
428+
is_maybeundefsp(sptypes::Vector{Any}, idx::Int) = is_maybeundefsp(sptypes[idx])
429+
unwrap_maybeundefsp(sptypes::Vector{Any}, idx::Int) = unwrap_maybeundefsp(sptypes[idx])
430+
431+
const EMPTY_SPTYPES = Any[]
432+
351433
function sptypes_from_meth_instance(linfo::MethodInstance)
352-
toplevel = !isa(linfo.def, Method)
353-
if !toplevel && isempty(linfo.sparam_vals) && isa(linfo.def.sig, UnionAll)
434+
def = linfo.def
435+
isa(def, Method) || return EMPTY_SPTYPES # toplevel
436+
sig = def.sig
437+
if isempty(linfo.sparam_vals)
438+
isa(sig, UnionAll) || return EMPTY_SPTYPES
354439
# linfo is unspecialized
355440
sp = Any[]
356-
sig = linfo.def.sig
357-
while isa(sig, UnionAll)
358-
push!(sp, sig.var)
359-
sig = sig.body
441+
sig = sig
442+
while isa(sig, UnionAll)
443+
push!(sp, sig.var)
444+
sig = sig.body
360445
end
361446
else
362447
sp = collect(Any, linfo.sparam_vals)
@@ -365,9 +450,7 @@ function sptypes_from_meth_instance(linfo::MethodInstance)
365450
v = sp[i]
366451
if v isa TypeVar
367452
fromArg = 0
368-
# if this parameter came from arg::Type{T}, then `arg` is more precise than
369-
# Type{T} where lb<:T<:ub
370-
sig = linfo.def.sig
453+
maybe_undef = !constrains_param(v, linfo.specTypes, #=covariant=#true)
371454
temp = sig
372455
for j = 1:i-1
373456
temp = temp.body
@@ -408,6 +491,8 @@ function sptypes_from_meth_instance(linfo::MethodInstance)
408491
ty = UnionAll(tv, Type{tv})
409492
end
410493
end
494+
@label ty_computed
495+
maybe_undef && (ty = MaybeUndefSP(ty))
411496
elseif isvarargtype(v)
412497
ty = Int
413498
else

base/compiler/optimize.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,9 @@ function stmt_effect_flags(lattice::AbstractLattice, @nospecialize(stmt), @nospe
235235
if isa(stmt, Expr)
236236
(; head, args) = stmt
237237
if head === :static_parameter
238-
etyp = (isa(src, IRCode) ? src.sptypes : src.ir.sptypes)[args[1]::Int]
239238
# if we aren't certain enough about the type, it might be an UndefVarError at runtime
240-
nothrow = isa(etyp, Const)
239+
sptypes = isa(src, IRCode) ? src.sptypes : src.ir.sptypes
240+
nothrow = !is_maybeundefsp(sptypes, args[1]::Int)
241241
return (true, nothrow, nothrow)
242242
end
243243
if head === :call
@@ -343,7 +343,7 @@ function argextype(
343343
sptypes::Vector{Any}, slottypes::Vector{Any})
344344
if isa(x, Expr)
345345
if x.head === :static_parameter
346-
return sptypes[x.args[1]::Int]
346+
return unwrap_maybeundefsp(sptypes, x.args[1]::Int)
347347
elseif x.head === :boundscheck
348348
return Bool
349349
elseif x.head === :copyast

base/compiler/ssair/slot2ssa.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ end
215215
function typ_for_val(@nospecialize(x), ci::CodeInfo, sptypes::Vector{Any}, idx::Int, slottypes::Vector{Any})
216216
if isa(x, Expr)
217217
if x.head === :static_parameter
218-
return sptypes[x.args[1]::Int]
218+
return unwrap_maybeundefsp(sptypes, x.args[1]::Int)
219219
elseif x.head === :boundscheck
220220
return Bool
221221
elseif x.head === :copyast

base/compiler/ssair/verify.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ function verify_ir(ir::IRCode, print::Bool=true, allow_frontend_forms::Bool=fals
267267
elseif stmt.head === :foreigncall
268268
isforeigncall = true
269269
elseif stmt.head === :isdefined && length(stmt.args) == 1 &&
270-
(stmt.args[1] isa GlobalRef || (stmt.args[1] isa Expr && stmt.args[1].head === :static_parameter))
270+
(stmt.args[1] isa GlobalRef || isexpr(stmt.args[1], :static_parameter))
271271
# a GlobalRef or static_parameter isdefined check does not evaluate its argument
272272
continue
273273
elseif stmt.head === :call

stdlib/Test/src/Test.jl

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,54 +1964,11 @@ function detect_unbound_args(mods...;
19641964
return collect(ambs)
19651965
end
19661966

1967-
# find if var will be constrained to have a definite value
1968-
# in any concrete leaftype subtype of typ
1969-
function constrains_param(var::TypeVar, @nospecialize(typ), covariant::Bool)
1970-
typ === var && return true
1971-
while typ isa UnionAll
1972-
covariant && constrains_param(var, typ.var.ub, covariant) && return true
1973-
# typ.var.lb doesn't constrain var
1974-
typ = typ.body
1975-
end
1976-
if typ isa Union
1977-
# for unions, verify that both options would constrain var
1978-
ba = constrains_param(var, typ.a, covariant)
1979-
bb = constrains_param(var, typ.b, covariant)
1980-
(ba && bb) && return true
1981-
elseif typ isa DataType
1982-
# return true if any param constrains var
1983-
fc = length(typ.parameters)
1984-
if fc > 0
1985-
if typ.name === Tuple.name
1986-
# vararg tuple needs special handling
1987-
for i in 1:(fc - 1)
1988-
p = typ.parameters[i]
1989-
constrains_param(var, p, covariant) && return true
1990-
end
1991-
lastp = typ.parameters[fc]
1992-
vararg = Base.unwrap_unionall(lastp)
1993-
if vararg isa Core.TypeofVararg && isdefined(vararg, :N)
1994-
constrains_param(var, vararg.N, covariant) && return true
1995-
# T = vararg.parameters[1] doesn't constrain var
1996-
else
1997-
constrains_param(var, lastp, covariant) && return true
1998-
end
1999-
else
2000-
for i in 1:fc
2001-
p = typ.parameters[i]
2002-
constrains_param(var, p, false) && return true
2003-
end
2004-
end
2005-
end
2006-
end
2007-
return false
2008-
end
2009-
20101967
function has_unbound_vars(@nospecialize sig)
20111968
while sig isa UnionAll
20121969
var = sig.var
20131970
sig = sig.body
2014-
if !constrains_param(var, sig, true)
1971+
if !Core.Compiler.constrains_param(var, sig, #=covariant=#true, #=type_constrains=#true)
20151972
return true
20161973
end
20171974
end

test/compiler/effects.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,3 +696,16 @@ end |> !Core.Compiler.is_consistent
696696
@inbounds x[1]
697697
end |> Core.Compiler.is_total
698698

699+
# unknown :static_parameter should taint :nothrow
700+
# https://github.com/JuliaLang/julia/issues/46771
701+
unknown_sparam_throw(::Union{Nothing, Type{T}}) where T = (T; nothing)
702+
unknown_sparam_nothrow1(x::Ref{T}) where T = (T; nothing)
703+
unknown_sparam_nothrow2(x::Ref{Ref{T}}) where T = (T; nothing)
704+
@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type{Int},)))
705+
@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type{<:Integer},)))
706+
@test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type,)))
707+
@test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Nothing,)))
708+
@test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Union{Type{Int},Nothing},)))
709+
@test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Any,)))
710+
@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_nothrow1, (Ref,)))
711+
@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_nothrow2, (Ref{Ref{T}} where T,)))

test/compiler/inference.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4280,3 +4280,16 @@ end
42804280

42814281
# issue #48374
42824282
@test (() -> Union{<:Nothing})() == Nothing
4283+
4284+
# :static_parameter accuracy
4285+
unknown_sparam_throw(::Union{Nothing, Type{T}}) where T = @isdefined(T) ? T::Type : nothing
4286+
unknown_sparam_nothrow1(x::Ref{T}) where T = @isdefined(T) ? T::Type : nothing
4287+
unknown_sparam_nothrow2(x::Ref{Ref{T}}) where T = @isdefined(T) ? T::Type : nothing
4288+
@test only(Base.return_types(unknown_sparam_throw, (Type{Int},))) == Type{Int}
4289+
@test only(Base.return_types(unknown_sparam_throw, (Type{<:Integer},))) == Type{<:Integer}
4290+
@test only(Base.return_types(unknown_sparam_throw, (Type,))) == Union{Nothing, Type}
4291+
@test_broken only(Base.return_types(unknown_sparam_throw, (Nothing,))) === Nothing
4292+
@test_broken only(Base.return_types(unknown_sparam_throw, (Union{Type{Int},Nothing},))) === Union{Nothing,Type{Int}}
4293+
@test only(Base.return_types(unknown_sparam_throw, (Any,))) === Union{Nothing,Type}
4294+
@test only(Base.return_types(unknown_sparam_nothrow1, (Ref,))) === Type
4295+
@test only(Base.return_types(unknown_sparam_nothrow2, (Ref{Ref{T}} where T,))) === Type

0 commit comments

Comments
 (0)