Skip to content

Commit 06ede99

Browse files
committed
apply_type_tfunc: add heuristic complexity limit
In #48421 we removed all limits from apply_type, but that can lead to significant expression complexity which may be hard for subtyping to deal with. Try adding a heuristic size limiting constraint. Also do a bunch of code rearrangement and slight cleanup so that this change does not make the logic look complicated here. Solves #49127
1 parent 822d9b8 commit 06ede99

File tree

3 files changed

+54
-15
lines changed

3 files changed

+54
-15
lines changed

base/compiler/tfuncs.jl

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,31 +1779,55 @@ const _tvarnames = Symbol[:_A, :_B, :_C, :_D, :_E, :_F, :_G, :_H, :_I, :_J, :_K,
17791779
uncertain = true
17801780
unw = unwrap_unionall(ai)
17811781
isT = isType(unw)
1782+
# compute our desired upper bound value
17821783
if isT
1783-
tai = ai
1784-
while isa(tai, UnionAll)
1785-
if contains_is(outervars, tai.var)
1786-
ai = rename_unionall(ai)
1787-
unw = unwrap_unionall(ai)
1788-
break
1784+
ub = rewrap_unionall(unw.parameters[1], ai)
1785+
else
1786+
ub = Any
1787+
end
1788+
if !istuple && unionall_depth(ai) > 3
1789+
# Heuristic: if we are adding more than N unknown parameters here to the
1790+
# outer type, use the wrapper type, instead of letting it nest more
1791+
# complexity here. This is not monotonic, but seems to work out pretty well.
1792+
if isT
1793+
ub = unwrap_unionall(unw.parameters[1])
1794+
if ub isa DataType
1795+
ub = ub.name.wrapper
1796+
unw = Type{unwrap_unionall(ub)}
1797+
ai = rewrap_unionall(unw, ub)
1798+
else
1799+
isT = false
1800+
ai = unw = ub = Any
17891801
end
1790-
tai = tai.body
1802+
else
1803+
isT = false
1804+
ai = unw = ub = Any
17911805
end
1806+
elseif !isT
1807+
# if we didn't have isType to compute ub directly, try to use instanceof_tfunc to refine this guess
1808+
ai_w = widenconst(ai)
1809+
ub = ai_w isa Type && ai_w <: Type ? instanceof_tfunc(ai)[1] : Any
17921810
end
1793-
ai_w = widenconst(ai)
1794-
ub = ai_w isa Type && ai_w <: Type ? instanceof_tfunc(ai)[1] : Any
17951811
if istuple
17961812
# in the last parameter of a Tuple type, if the upper bound is Any
17971813
# then this could be a Vararg type.
17981814
if i == largs && ub === Any
1799-
push!(tparams, Vararg)
1800-
elseif isT
1801-
push!(tparams, rewrap_unionall((unw::DataType).parameters[1], ai))
1802-
else
1803-
push!(tparams, Any)
1815+
ub = Vararg
18041816
end
1817+
push!(tparams, ub)
18051818
elseif isT
1806-
push!(tparams, (unw::DataType).parameters[1])
1819+
tai = ai
1820+
while isa(tai, UnionAll)
1821+
# make sure vars introduced here are unique
1822+
if contains_is(outervars, tai.var)
1823+
ai = rename_unionall(ai)
1824+
unw = unwrap_unionall(ai)::DataType
1825+
# ub = rewrap_unionall(unw, ai)
1826+
break
1827+
end
1828+
tai = tai.body
1829+
end
1830+
push!(tparams, unw.parameters[1])
18071831
while isa(ai, UnionAll)
18081832
push!(outervars, ai.var)
18091833
ai = ai.body

base/compiler/typeutils.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,15 @@ function _unioncomplexity(@nospecialize x)
308308
end
309309
end
310310

311+
function unionall_depth(@nospecialize ua) # aka subtype_env_size
312+
depth = 0
313+
while ua isa UnionAll
314+
depth += 1
315+
ua = ua.body
316+
end
317+
return depth
318+
end
319+
311320
# convert a Union of Tuple types to a Tuple of Unions
312321
function unswitchtupleunion(u::Union)
313322
ts = uniontypes(u)

test/compiler/inference.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2658,10 +2658,16 @@ let 𝕃 = Core.Compiler.fallback_lattice
26582658
@test apply_type_tfunc(𝕃, Const(Issue47089), Const(String)) === Union{}
26592659
@test apply_type_tfunc(𝕃, Const(Issue47089), Const(AbstractString)) === Union{}
26602660
@test apply_type_tfunc(𝕃, Const(Issue47089), Type{Ptr}, Type{Ptr{T}} where T) === Base.rewrap_unionall(Type{Issue47089.body.body}, Issue47089)
2661+
# check complexity size limiting
2662+
@test apply_type_tfunc(𝕃, Const(Val), Type{Pair{Pair{Pair{Pair{A,B},C},D},E}} where {A,B,C,D,E}) == Type{Val{Pair{A, B}}} where {A, B}
2663+
@test apply_type_tfunc(𝕃, Const(Pair), Base.rewrap_unionall(Type{Pair.body.body},Pair), Type{Pair{Pair{Pair{Pair{A,B},C},D},E}} where {A,B,C,D,E}) == Type{Pair{Pair{A, B}, Pair{C, D}}} where {A, B, C, D}
2664+
@test apply_type_tfunc(𝕃, Const(Val), Type{Union{Int,Pair{Pair{Pair{Pair{A,B},C},D},E}}} where {A,B,C,D,E}) == Type{Val{_A}} where _A
26612665
end
26622666
@test only(Base.return_types(keys, (Dict{String},))) == Base.KeySet{String, T} where T<:(Dict{String})
26632667
@test only(Base.return_types((r)->similar(Array{typeof(r[])}, 1), (Base.RefValue{Array{Int}},))) == Vector{<:Array{Int}}
26642668
@test only(Base.return_types((r)->similar(Array{typeof(r[])}, 1), (Base.RefValue{Array{<:Real}},))) == Vector{<:Array{<:Real}}
2669+
# test complexity limit on apply_type on a function capturing functions returning functions
2670+
@test only(Base.return_types(Base.afoldl, (typeof((m, n) -> () -> Returns(nothing)(m, n)), Function, Function, Vararg{Function}))) === Function
26652671

26662672
let A = Tuple{A,B,C,D,E,F,G,H} where {A,B,C,D,E,F,G,H}
26672673
B = Core.Compiler.rename_unionall(A)

0 commit comments

Comments
 (0)