Skip to content

Commit

Permalink
subtype: more conservative intersection of triangular variables
Browse files Browse the repository at this point in the history
When a variable is used triangularly, we need to be careful not to
propagate the lower bound implied by the other side to the upper bound
implied by the invariant usage of the value--that is only legal when we
are intersecting a variable that is used diagonally.

Fix #49578
  • Loading branch information
vtjnash committed May 2, 2023
1 parent 6d39d6d commit b7db974
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 43 deletions.
28 changes: 19 additions & 9 deletions src/subtype.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ typedef struct jl_varbinding_t {
// let ub = var.ub ∩ type
// 0 - var.ub <: type ? var : ub
// 1 - var.ub = ub; return var
// 2 - either (var.ub = ub; return var), or return ub
// 2 - var.lb = lb; return ub
int8_t constraintkind;
int8_t intvalued; // intvalued: must be integer-valued; i.e. occurs as N in Vararg{_,N}
int8_t limited;
Expand Down Expand Up @@ -2646,14 +2646,24 @@ static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int
return ub;
}
assert(bb->constraintkind == 2);
if (!jl_is_typevar(a)) {
if (ub == a && bb->lb != jl_bottom_type)
return ub;
else if (jl_egal(bb->ub, bb->lb))
return ub;
set_bound(&bb->ub, ub, b, e);
}
return (jl_value_t*)b;
if (ub == a && bb->lb != jl_bottom_type)
return ub;
if (jl_egal(bb->ub, bb->lb))
return ub;
if (is_leaf_bound(ub))
set_bound(&bb->lb, ub, b, e);
// TODO: can we improve this bound by pushing a new variable into the environment
// and adding that to the lower bound of our variable?
//jl_value_t *ntv = NULL;
//JL_GC_PUSH2(&ntv, &ub);
//if (bb->innervars == NULL)
// bb->innervars = jl_alloc_array_1d(jl_array_any_type, 0);
//ntv = (jl_value_t*)jl_new_typevar(b->name, bb->lb, ub);
//jl_array_ptr_1d_push(bb->innervars, ntv);
//jl_value_t *lb = simple_join(b->lb, ntv);
//JL_GC_POP();
//bb->lb = lb;
return ub;
}

// test whether `var` occurs inside constructors. `want_inv` tests only inside
Expand Down
80 changes: 46 additions & 34 deletions test/subtype.jl
Original file line number Diff line number Diff line change
Expand Up @@ -759,8 +759,11 @@ function test_intersection()
@testintersect((@UnionAll T Tuple{T, AbstractArray{T}}), Tuple{Int, Array{Number,1}},
Tuple{Int, Array{Number,1}})

# TODO: improve this result
#@testintersect((@UnionAll S Tuple{S,Vector{S}}), (@UnionAll T<:Real Tuple{T,AbstractVector{T}}),
# (@UnionAll S<:Real Tuple{S,Vector{S}}))
@testintersect((@UnionAll S Tuple{S,Vector{S}}), (@UnionAll T<:Real Tuple{T,AbstractVector{T}}),
(@UnionAll S<:Real Tuple{S,Vector{S}}))
(@UnionAll S<:Real Tuple{Real,Vector{S}}))

# typevar corresponding to a type it will end up being neither greater than nor
# less than
Expand Down Expand Up @@ -819,9 +822,9 @@ function test_intersection()
Tuple{Tuple{Vararg{Integer}}, Tuple{Integer,Integer}},
Tuple{Tuple{Integer,Integer}, Tuple{Integer,Integer}})

#@test isequal_type(typeintersect((@UnionAll N Tuple{NTuple{N,Any},Array{Int,N}}),
# Tuple{Tuple{Int,Vararg{Int}},Array}),
# Tuple{Tuple{Int,Vararg{Int}},Array{Int,N}})
@test isequal_type(typeintersect((@UnionAll N Tuple{NTuple{N,Any},Array{Int,N}}),
Tuple{Tuple{Int,Vararg{Int}},Array}),
@UnionAll N Tuple{Tuple{Int,Vararg{Int}},Array{Int,N}})

@testintersect((@UnionAll N Tuple{NTuple{N,Any},Array{Int,N}}),
Tuple{Tuple{Int,Vararg{Int}},Array{Int,2}},
Expand Down Expand Up @@ -930,9 +933,10 @@ function test_intersection()
# since this T is inside the invariant ctor Type{}, we allow T == Any here
@testintersect((Type{Tuple{Vararg{T}}} where T), Type{Tuple}, Type{Tuple})

# TODO: improve this
@testintersect(Tuple{Type{S}, Tuple{Any, Vararg{Any}}} where S<:Tuple{Any, Vararg{Any}},
Tuple{Type{T}, T} where T,
Tuple{Type{S},S} where S<:Tuple{Any,Vararg{Any}})
Tuple{Type{S}, Tuple{Any, Vararg{Any}}} where S<:Tuple{Any, Vararg{Any}})

# part of issue #20450
@testintersect(Tuple{Array{Ref{T}, 1}, Array{Pair{M, V}, 1}} where V where T where M,
Expand Down Expand Up @@ -1079,8 +1083,7 @@ function test_intersection_properties()
I2 = _type_intersect(S,T)
@test isequal_type(I, I2)
if i > length(easy_menagerie) || j > length(easy_menagerie)
# TODO: these cases give a conservative answer
@test issub(I, T) || issub(I, S)
# @test issub(I, T) || issub(I, S)
else
@test issub(I, T) && issub(I, S)
end
Expand Down Expand Up @@ -1601,7 +1604,7 @@ end
Tuple{Type{A29955{T,TV,TM}},
TM} where {T,TV<:AbstractVector{T},TM<:M29955{T,TV}},
Tuple{Type{A29955{Float64,Array{Float64,1},TM}},
TM} where TM<:M29955{Float64,Array{Float64,1}})
M29955{Float64,Vector{Float64}}} where TM<:M29955{Float64,Array{Float64,1}})
let M = M29955{T,Vector{Float64}} where T
@test M == (M29955{T,Vector{Float64}} where T)
@test M{Float64} == M29955{Float64,Vector{Float64}}
Expand All @@ -1619,9 +1622,9 @@ end
Tuple{LT,R,I} where LT<:Union{I, R} where R<:Rational{I} where I<:Integer,
Tuple{LT,Rational{Int},Int} where LT<:Union{Rational{Int},Int})

#@testintersect(Tuple{Any,Tuple{Int},Int},
# Tuple{LT,R,I} where LT<:Union{I, R} where R<:Tuple{I} where I<:Integer,
# Tuple{LT,Tuple{Int},Int} where LT<:Union{Tuple{Int},Int})
@testintersect(Tuple{Any,Tuple{Int},Int},
Tuple{LT,R,I} where LT<:Union{I, R} where R<:Tuple{I} where I<:Integer,
Tuple{LT,Tuple{Int},Int} where LT<:Union{Tuple{Int},Int})
# fails due to this:
let U = Tuple{Union{LT, LT1},Union{R, R1},Int} where LT1<:R1 where R1<:Tuple{Int} where LT<:Int where R<:Tuple{Int},
U2 = Union{Tuple{LT,R,Int} where LT<:Int where R<:Tuple{Int}, Tuple{LT,R,Int} where LT<:R where R<:Tuple{Int}},
Expand All @@ -1638,9 +1641,10 @@ end
# issue #31082 and #30741
@test typeintersect(Tuple{T, Ref{T}, T} where T,
Tuple{Ref{S}, S, S} where S) != Union{}
# TODO: improve this bound
@testintersect(Tuple{Pair{B,C},Union{C,Pair{B,C}},Union{B,Real}} where {B,C},
Tuple{Pair{B,C},C,C} where {B,C},
Tuple{Pair{B,C},C,C} where C<:Union{Real, B} where B)
Tuple{Pair{B,C}, Union{Pair{B,C},C},Union{Real,B}} where {B,C})
f31082(::Pair{B, C}, ::Union{C, Pair{B, C}}, ::Union{B, Real}) where {B, C} = 0
f31082(::Pair{B, C}, ::C, ::C) where {B, C} = 1
@test f31082(""=>1, 2, 3) == 1
Expand Down Expand Up @@ -1904,12 +1908,14 @@ end

# issue #22787
@testintersect(Tuple{Type{Q}, Q, Ref{Q}} where Q<:Ref,
Tuple{Type{S}, Union{Ref{S}, Ref{R}}, R} where R where S,
!Union{})
Tuple{Type{S}, Union{Ref{S}, Ref{R}}, R} where R where S,
Tuple{Type{Q}, Union{Ref{Q}, Ref{R}}, Ref{Q}} where {Q<:Ref, R}) # likely suboptimal

t = typeintersect(Tuple{Type{T}, T, Ref{T}} where T,
let t = typeintersect(Tuple{Type{T}, T, Ref{T}} where T,
Tuple{Type{S}, Ref{S}, S} where S)
@test_broken t != Union{} # optimal solution: Tuple{Type{T}, Ref{T}, Ref{T}} where T>:Ref
@test_broken t == Tuple{Type{T}, Ref{T}, Ref{T}} where T>:Ref
@test t == Tuple{Type{T}, Ref{T}, Ref{T}} where T
end

# issue #38279
t = typeintersect(Tuple{<:Array{T, N}, Val{T}} where {T<:Real, N},
Expand Down Expand Up @@ -1954,9 +1960,11 @@ let A = Tuple{Type{T} where T<:Ref, Ref, Union{T, Union{Ref{T}, T}} where T<:Ref
B = Tuple{Type{T}, Ref{T}, Union{Int, Ref{T}, T}} where T
# this was a case where <: disagreed with === (due to a badly-normalized type)
I = _type_intersect(B, A)
@test I == _type_intersect(B, A) == Union{Tuple{Type{T}, Ref{T}, Ref{T}} where T<:Ref, Tuple{Type{T}, Ref{T}, T} where T<:Ref}
@test_broken I == Union{Tuple{Type{T}, Ref{T}, Ref{T}} where T<:Ref, Tuple{Type{T}, Ref{T}, T} where T<:Ref}
@test I == _type_intersect(B, A) == Tuple{Type{T}, Ref{T}, Ref} where T<:Ref
I = typeintersect(B, A)
@test I == typeintersect(B, A) == Tuple{Type{T}, Ref{T}, Union{Ref{T}, T}} where T<:Ref
@test_broken I == Tuple{Type{T}, Ref{T}, Union{Ref{T}, T}} where T<:Ref
@test I == typeintersect(B, A) <: Tuple{Type{T}, Ref{T}, Ref} where T<:Ref

I = _type_intersect(A, B)
@test !Base.has_free_typevars(I)
Expand Down Expand Up @@ -2026,17 +2034,17 @@ let A = Tuple{Ref{T}, Vararg{T}} where T,
I = typeintersect(A, B)
Ts = (Tuple{Ref{Int}, Int, Int}, Tuple{Ref{Ref{Int}}, Ref{Int}, Ref{Int}})
@test I != Union{}
@test I <: A
@test_broken I <: B
@test_broken I <: A
@test I <: B
for T in Ts
if T <: A && T <: B
@test T <: I
end
end
J = typeintersect(A, C)
@test J != Union{}
@test J <: A
@test_broken J <: C
@test_broken J <: A
@test J <: C
for T in Ts
if T <: A && T <: C
@test T <: J
Expand All @@ -2045,9 +2053,13 @@ let A = Tuple{Ref{T}, Vararg{T}} where T,
end

let A = Tuple{Dict{I,T}, I, T} where T where I,
B = Tuple{AbstractDict{I,T}, T, I} where T where I
# TODO: we should probably have I == T here
@test typeintersect(A, B) == Tuple{Dict{I,T}, I, T} where {I, T}
B = Tuple{AbstractDict{I,T}, T, I} where T where I,
I = typeintersect(A, B)
# TODO: we should probably have something approaching I == T here,
# though note something more complex is needed since the intersection must also include types such as;
# Tuple{Dict{Integer,Any}, Integer, Int}
@test_broken I <: A && I <: B
@test I == typeintersect(B, A) == Tuple{Dict{I, T}, Any, Any} where {I, T}
end

let A = Tuple{UnionAll, Vector{Any}},
Expand Down Expand Up @@ -2378,7 +2390,7 @@ let S = Tuple{Type{T1}, T1, Val{T1}} where T1<:(Val{S1} where S1<:Val),
@test I1 !== Union{} && I2 !== Union{}
@test_broken I1 <: S
@test_broken I2 <: T
@test I2 <: S
@test_broken I2 <: S
@test_broken I2 <: T
end

Expand Down Expand Up @@ -2512,26 +2524,26 @@ end

let A = Tuple{Type{T}, T, Val{T}} where T,
B = Tuple{Type{S}, Val{S}, Val{S}} where S
@test_broken typeintersect(A, B) != Union{}
# optimal = Tuple{Type{T}, Val{T}, Val{T}} where T>:Val
@test_broken typeintersect(A, B) == Tuple{Type{T}, Val{T}, Val{T}} where T>:Val
@test typeintersect(A, B) <: Tuple{Type{T}, Val{T}, Val{T}} where T
end
let A = Tuple{Type{T}, T, Val{T}} where T<:Val,
B = Tuple{Type{S}, Val{S}, Val{S}} where S<:Val
@test_broken typeintersect(A, B) != Union{}
# optimal = Tuple{Type{Val}, Val{Val}, Val{Val}}
@test_broken typeintersect(A, B) == Tuple{Type{Val}, Val{Val}, Val{Val}}
@test typeintersect(A, B) <: Tuple{Type{T}, Val{T}, Val{T}} where T<:Val
end
let A = Tuple{Type{T}, T, Val{T}} where T<:Val,
B = Tuple{Type{S}, Val{S}, Val{S}} where S<:Val{A} where A
@test typeintersect(A, B) == Union{}
end
let A = Tuple{Type{T}, T, Val{T}} where T<:Val{<:Val},
B = Tuple{Type{S}, Val{S}, Val{S}} where S<:Val
@test_broken typeintersect(A, B) != Union{}
# optimal = Tuple{Type{Val{<:Val}}, Val{Val{<:Val}}, Val{Val{<:Val}}}
@test_broken typeintersect(A, B) == Tuple{Type{Val{<:Val}}, Val{Val{<:Val}}, Val{Val{<:Val}}}
@test typeintersect(A, B) <: Tuple{Type{T}, Val{T}, Val{T}} where T<:(Val{<:Val})
end
let T = Tuple{Union{Type{T}, Type{S}}, Union{Val{T}, Val{S}}, Union{Val{T}, S}} where T<:Val{A} where A where S<:Val,
S = Tuple{Type{T}, T, Val{T}} where T<:(Val{S} where S<:Val)
# optimal = Union{}?
@test typeintersect(T, S) == Tuple{Type{T}, T, Val{T}} where T<:(Val{S} where S<:Val)
@test typeintersect(S, T) == Tuple{Union{Type{T}, Type{T1}}, Union{Val{T1}, Val{S1}, T}, Union{S, S1}} where {T<:(Val{S} where S<:Val), S<:Val{T}, T1<:Val, S1<:Val{T1}}
@test typeintersect(T, S) == Tuple{Type{A}, Union{Val{A}, Val{S} where S<:Union{Val, A}, Val{x} where x<:Val, Val{x} where x<:Union{Val, A}}, Val{A}} where A<:(Val{S} where S<:Val)
@test typeintersect(S, T) == Tuple{Type{T}, Union{Val{T}, Val{S}}, Val{T}} where {T<:Val, S<:(Union{Val{A}, Val} where A)}
end

0 comments on commit b7db974

Please sign in to comment.