Skip to content

Commit 8233847

Browse files
author
Ian Atol
authored
Add narrowing for isdefined calls on Unions (#43009)
1 parent 1867b42 commit 8233847

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,28 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs
12321232
end
12331233
return Conditional(aty.var, ifty, elty)
12341234
end
1235+
elseif f === isdefined
1236+
uty = argtypes[2]
1237+
a = ssa_def_slot(fargs[2], sv)
1238+
if isa(uty, Union) && isa(a, SlotNumber)
1239+
fld = argtypes[3]
1240+
vtype = Union{}
1241+
elsetype = Union{}
1242+
for ty in uniontypes(uty)
1243+
cnd = isdefined_tfunc(ty, fld)
1244+
if isa(cnd, Const)
1245+
if cnd.val::Bool
1246+
vtype = tmerge(vtype, ty)
1247+
else
1248+
elsetype = tmerge(elsetype, ty)
1249+
end
1250+
else
1251+
vtype = tmerge(vtype, ty)
1252+
elsetype = tmerge(elsetype, ty)
1253+
end
1254+
end
1255+
return Conditional(a, vtype, elsetype)
1256+
end
12351257
end
12361258
end
12371259
@assert !isa(rt, TypeVar) "unhandled TypeVar"

test/compiler/inference.jl

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3755,3 +3755,63 @@ end |> only == Tuple{Int,Int}
37553755
s2 = Some{Any}(s1)
37563756
s2.value.value
37573757
end |> only == Int
3758+
3759+
# issue #42986
3760+
@testset "narrow down `Union` using `isdefined` checks" begin
3761+
# basic functionality
3762+
@test Base.return_types((Union{Nothing,Core.CodeInstance},)) do x
3763+
if isdefined(x, :inferred)
3764+
return x
3765+
else
3766+
throw("invalid")
3767+
end
3768+
end |> only === Core.CodeInstance
3769+
3770+
@test Base.return_types((Union{Nothing,Core.CodeInstance},)) do x
3771+
if isdefined(x, :not_exist)
3772+
return x
3773+
else
3774+
throw("invalid")
3775+
end
3776+
end |> only === Union{}
3777+
3778+
# even when isdefined is malformed, we can filter out types with no fields
3779+
@test Base.return_types((Union{Nothing, Core.CodeInstance},)) do x
3780+
if isdefined(x, 5)
3781+
return x
3782+
else
3783+
throw("invalid")
3784+
end
3785+
end |> only === Core.CodeInstance
3786+
3787+
struct UnionNarrowingByIsdefinedA; x; end
3788+
struct UnionNarrowingByIsdefinedB; x; end
3789+
struct UnionNarrowingByIsdefinedC; x; end
3790+
3791+
# > 2 types in the union
3792+
@test Base.return_types((Union{UnionNarrowingByIsdefinedA, UnionNarrowingByIsdefinedB, UnionNarrowingByIsdefinedC},)) do x
3793+
if isdefined(x, :x)
3794+
return x
3795+
else
3796+
throw("invalid")
3797+
end
3798+
end |> only === Union{UnionNarrowingByIsdefinedA, UnionNarrowingByIsdefinedB, UnionNarrowingByIsdefinedC}
3799+
3800+
# > 2 types in the union and some aren't defined
3801+
@test Base.return_types((Union{UnionNarrowingByIsdefinedA, Core.CodeInstance, UnionNarrowingByIsdefinedC},)) do x
3802+
if isdefined(x, :x)
3803+
return x
3804+
else
3805+
throw("invalid")
3806+
end
3807+
end |> only === Union{UnionNarrowingByIsdefinedA, UnionNarrowingByIsdefinedC}
3808+
3809+
# should respect `Const` information still
3810+
@test Base.return_types((Union{UnionNarrowingByIsdefinedA, UnionNarrowingByIsdefinedB},)) do x
3811+
if isdefined(x, :x)
3812+
return x
3813+
else
3814+
return nothing # dead branch
3815+
end
3816+
end |> only === Union{UnionNarrowingByIsdefinedA, UnionNarrowingByIsdefinedB}
3817+
end

0 commit comments

Comments
 (0)