From 8233847d0dff2dff5af7ef977dc56f7d72f53323 Mon Sep 17 00:00:00 2001 From: Ian Atol Date: Wed, 10 Nov 2021 08:30:35 -0800 Subject: [PATCH] Add narrowing for isdefined calls on Unions (#43009) --- base/compiler/abstractinterpretation.jl | 22 +++++++++ test/compiler/inference.jl | 60 +++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 260a98eb72eca..2b97fa9a64cf7 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1232,6 +1232,28 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs end return Conditional(aty.var, ifty, elty) end + elseif f === isdefined + uty = argtypes[2] + a = ssa_def_slot(fargs[2], sv) + if isa(uty, Union) && isa(a, SlotNumber) + fld = argtypes[3] + vtype = Union{} + elsetype = Union{} + for ty in uniontypes(uty) + cnd = isdefined_tfunc(ty, fld) + if isa(cnd, Const) + if cnd.val::Bool + vtype = tmerge(vtype, ty) + else + elsetype = tmerge(elsetype, ty) + end + else + vtype = tmerge(vtype, ty) + elsetype = tmerge(elsetype, ty) + end + end + return Conditional(a, vtype, elsetype) + end end end @assert !isa(rt, TypeVar) "unhandled TypeVar" diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 9e26ad18e8b84..da89b8aba672b 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -3755,3 +3755,63 @@ end |> only == Tuple{Int,Int} s2 = Some{Any}(s1) s2.value.value end |> only == Int + +# issue #42986 +@testset "narrow down `Union` using `isdefined` checks" begin + # basic functionality + @test Base.return_types((Union{Nothing,Core.CodeInstance},)) do x + if isdefined(x, :inferred) + return x + else + throw("invalid") + end + end |> only === Core.CodeInstance + + @test Base.return_types((Union{Nothing,Core.CodeInstance},)) do x + if isdefined(x, :not_exist) + return x + else + throw("invalid") + end + end |> only === Union{} + + # even when isdefined is malformed, we can filter out types with no fields + @test Base.return_types((Union{Nothing, Core.CodeInstance},)) do x + if isdefined(x, 5) + return x + else + throw("invalid") + end + end |> only === Core.CodeInstance + + struct UnionNarrowingByIsdefinedA; x; end + struct UnionNarrowingByIsdefinedB; x; end + struct UnionNarrowingByIsdefinedC; x; end + + # > 2 types in the union + @test Base.return_types((Union{UnionNarrowingByIsdefinedA, UnionNarrowingByIsdefinedB, UnionNarrowingByIsdefinedC},)) do x + if isdefined(x, :x) + return x + else + throw("invalid") + end + end |> only === Union{UnionNarrowingByIsdefinedA, UnionNarrowingByIsdefinedB, UnionNarrowingByIsdefinedC} + + # > 2 types in the union and some aren't defined + @test Base.return_types((Union{UnionNarrowingByIsdefinedA, Core.CodeInstance, UnionNarrowingByIsdefinedC},)) do x + if isdefined(x, :x) + return x + else + throw("invalid") + end + end |> only === Union{UnionNarrowingByIsdefinedA, UnionNarrowingByIsdefinedC} + + # should respect `Const` information still + @test Base.return_types((Union{UnionNarrowingByIsdefinedA, UnionNarrowingByIsdefinedB},)) do x + if isdefined(x, :x) + return x + else + return nothing # dead branch + end + end |> only === Union{UnionNarrowingByIsdefinedA, UnionNarrowingByIsdefinedB} +end