Skip to content

Commit 8653d6f

Browse files
committed
fix some cases of diagonal subtyping when a var also appears invariantly
fix #26108, fix #26716
1 parent 1878343 commit 8653d6f

File tree

3 files changed

+100
-17
lines changed

3 files changed

+100
-17
lines changed

doc/src/devdocs/types.md

+26-5
Original file line numberDiff line numberDiff line change
@@ -403,17 +403,38 @@ f(nothing, 2.0)
403403
These examples are telling us something: when `x` is `nothing::Nothing`, there are no
404404
extra constraints on `y`.
405405
It is as if the method signature had `y::Any`.
406-
This means that whether a variable is diagonal is not a static property based on
407-
where it appears in a type.
408-
Rather, it depends on where a variable appears when the subtyping algorithm *uses* it.
409-
When `x` has type `Nothing`, we don't need to use the `T` in `Union{Nothing,T}`, so `T`
410-
does not "occur".
411406
Indeed, we have the following type equivalence:
412407

413408
```julia
414409
(Tuple{Union{Nothing,T},T} where T) == Union{Tuple{Nothing,Any}, Tuple{T,T} where T}
415410
```
416411

412+
The general rule is: a concrete variable in covariant position acts like it's
413+
not concrete if the subtyping algorithm only *uses* it once.
414+
When `x` has type `Nothing`, we don't need to use the `T` in `Union{Nothing,T}`;
415+
we only use it in the second slot.
416+
This arises naturally from the observation that in `Tuple{T} where T` restricting
417+
`T` to concrete types makes no difference; the type is equal to `Tuple{Any}` either way.
418+
419+
However, appearing in *invariant* position disqualifies a variable from being concrete
420+
whether that appearance of the variable is used or not.
421+
Otherwise types become incoherent, behaving differently depending on which other types
422+
they are compared to. For example, consider
423+
424+
Tuple{Int,Int8,Vector{Integer}} <: Tuple{T,T,Vector{Union{Integer,T}}} where T
425+
426+
If the `T` inside the Union is ignored, then `T` is concrete and the answer is "false"
427+
since the first two types aren't the same.
428+
But consider instead
429+
430+
Tuple{Int,Int8,Vector{Any}} <: Tuple{T,T,Vector{Union{Integer,T}}} where T
431+
432+
Now we cannot ignore the `T` in the Union (we must have T == Any), so `T` is not
433+
concrete and the answer is "true".
434+
That would make the concreteness of `T` depend on the other type, which is not
435+
acceptable since a type must have a clear meaning on its own.
436+
Therefore the appearance of `T` inside `Vector` is considered in both cases.
437+
417438
## Subtyping diagonal variables
418439

419440
The subtyping algorithm for diagonal variables has two components:

src/subtype.c

+40-9
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ static int subtype_ccheck(jl_value_t *x, jl_value_t *y, jl_stenv_t *e)
516516
return sub;
517517
}
518518

519-
static int subtype_left_var(jl_value_t *x, jl_value_t *y, jl_stenv_t *e)
519+
static int subtype_left_var(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param)
520520
{
521521
if (x == y)
522522
return 1;
@@ -528,7 +528,7 @@ static int subtype_left_var(jl_value_t *x, jl_value_t *y, jl_stenv_t *e)
528528
return 1;
529529
if (x == (jl_value_t*)jl_any_type && jl_is_datatype(y))
530530
return 0;
531-
return subtype(x, y, e, 0);
531+
return subtype(x, y, e, param);
532532
}
533533

534534
// use the current context to record where a variable occurred, for the purpose
@@ -566,10 +566,10 @@ static int var_lt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param)
566566
{
567567
jl_varbinding_t *bb = lookup(e, b);
568568
if (bb == NULL)
569-
return e->ignore_free || subtype_left_var(b->ub, a, e);
569+
return e->ignore_free || subtype_left_var(b->ub, a, e, param);
570570
record_var_occurrence(bb, e, param);
571571
if (!bb->right) // check ∀b . b<:a
572-
return subtype_left_var(bb->ub, a, e);
572+
return subtype_left_var(bb->ub, a, e, param);
573573
if (bb->ub == a)
574574
return 1;
575575
if (!((bb->lb == jl_bottom_type && !jl_is_type(a) && !jl_is_typevar(a)) || subtype_ccheck(bb->lb, a, e)))
@@ -590,7 +590,7 @@ static int var_lt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param)
590590
if (aa && !aa->right && in_union(bb->lb, a) && bb->depth0 != aa->depth0 && var_outside(e, b, (jl_tvar_t*)a)) {
591591
// an "exists" var cannot equal a "forall" var inside it unless the forall
592592
// var has equal bounds.
593-
return subtype_left_var(aa->ub, aa->lb, e);
593+
return subtype_left_var(aa->ub, aa->lb, e, param);
594594
}
595595
}
596596
return 1;
@@ -601,10 +601,10 @@ static int var_gt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param)
601601
{
602602
jl_varbinding_t *bb = lookup(e, b);
603603
if (bb == NULL)
604-
return e->ignore_free || subtype_left_var(a, b->lb, e);
604+
return e->ignore_free || subtype_left_var(a, b->lb, e, param);
605605
record_var_occurrence(bb, e, param);
606606
if (!bb->right) // check ∀b . b>:a
607-
return subtype_left_var(a, bb->lb, e);
607+
return subtype_left_var(a, bb->lb, e, param);
608608
if (bb->lb == bb->ub) {
609609
if (jl_is_typevar(bb->lb) && !jl_is_type(a) && !jl_is_typevar(a))
610610
return var_gt((jl_tvar_t*)bb->lb, a, e, param);
@@ -618,7 +618,7 @@ static int var_gt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param)
618618
if (jl_is_typevar(a)) {
619619
jl_varbinding_t *aa = lookup(e, (jl_tvar_t*)a);
620620
if (aa && !aa->right && bb->depth0 != aa->depth0 && param == 2 && var_outside(e, b, (jl_tvar_t*)a))
621-
return subtype_left_var(aa->ub, aa->lb, e);
621+
return subtype_left_var(aa->ub, aa->lb, e, param);
622622
}
623623
return 1;
624624
}
@@ -690,12 +690,42 @@ static int var_occurs_inside(jl_value_t *v, jl_tvar_t *var, int inside, int want
690690

691691
typedef int (*tvar_callback)(void*, int8_t, jl_stenv_t *, int);
692692

693+
static int var_occurs_invariant(jl_value_t *v, jl_tvar_t *var, int inv) JL_NOTSAFEPOINT
694+
{
695+
if (v == (jl_value_t*)var) {
696+
return inv;
697+
}
698+
else if (jl_is_uniontype(v)) {
699+
return var_occurs_invariant(((jl_uniontype_t*)v)->a, var, inv) ||
700+
var_occurs_invariant(((jl_uniontype_t*)v)->b, var, inv);
701+
}
702+
else if (jl_is_unionall(v)) {
703+
jl_unionall_t *ua = (jl_unionall_t*)v;
704+
if (ua->var == var)
705+
return 0;
706+
if (var_occurs_invariant(ua->var->lb, var, inv) || var_occurs_invariant(ua->var->ub, var, inv))
707+
return 1;
708+
return var_occurs_invariant(ua->body, var, inv);
709+
}
710+
else if (jl_is_datatype(v)) {
711+
size_t i;
712+
int ins = !jl_is_tuple_type(v);
713+
int va = jl_is_vararg_type(v);
714+
for (i=0; i < jl_nparams(v); i++) {
715+
if (var_occurs_invariant(jl_tparam(v,i), var, va && i == 0 ? 0 : ins))
716+
return 1;
717+
}
718+
}
719+
return 0;
720+
}
721+
693722
static int with_tvar(tvar_callback callback, void *context, jl_unionall_t *u, int8_t R, jl_stenv_t *e, int param)
694723
{
695724
jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, NULL, 0, 0, 0, 0,
696725
R ? e->Rinvdepth : e->invdepth, 0, NULL, 0, e->vars };
697726
JL_GC_PUSH4(&u, &vb.lb, &vb.ub, &vb.innervars);
698727
e->vars = &vb;
728+
vb.occurs_inv = var_occurs_invariant(u->body, u->var, 0);
699729
int ans;
700730
if (R) {
701731
e->envidx++;
@@ -2494,7 +2524,8 @@ static jl_value_t *intersect_unionall_(jl_value_t *t, jl_unionall_t *u, jl_stenv
24942524
else {
24952525
res = intersect(u->body, t, e, param);
24962526
}
2497-
vb->concrete |= (!vb->occurs_inv && vb->occurs_cov > 1 && is_leaf_typevar(u->var));
2527+
int static_occurs_inv = var_occurs_invariant(u->body, u->var, 0);
2528+
vb->concrete |= (!static_occurs_inv && vb->occurs_cov > 1 && is_leaf_typevar(u->var));
24982529

24992530
// handle the "diagonal dispatch" rule, which says that a type var occurring more
25002531
// than once, and only in covariant position, is constrained to concrete types. E.g.

test/subtype.jl

+34-3
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,30 @@ function test_diagonal()
136136
@test issub(Tuple{Tuple{T, T}} where T>:Int, Tuple{Tuple{T, T} where T>:Int})
137137
@test issub(Vector{Tuple{T, T} where Number<:T<:Number},
138138
Vector{Tuple{Number, Number}})
139+
140+
@test !issub(Type{Tuple{T,Any} where T}, Type{Tuple{T,T}} where T)
141+
@test !issub(Type{Tuple{T,Any,T} where T}, Type{Tuple{T,T,T}} where T)
142+
@test_broken issub(Type{Tuple{T} where T}, Type{Tuple{T}} where T)
143+
@test_broken issub(Ref{Tuple{T} where T}, Ref{Tuple{T}} where T)
144+
@test !issub(Type{Tuple{T,T} where T}, Type{Tuple{T,T}} where T)
145+
@test !issub(Type{Tuple{T,T,T} where T}, Type{Tuple{T,T,T}} where T)
146+
@test isequal_type(Ref{Tuple{T, T} where Int<:T<:Int},
147+
Ref{Tuple{S, S}} where Int<:S<:Int)
148+
149+
let A = Tuple{Int,Int8,Vector{Integer}},
150+
B = Tuple{T,T,Vector{T}} where T>:Integer,
151+
C = Tuple{T,T,Vector{Union{Integer,T}}} where T
152+
@test A <: B
153+
@test B == C
154+
@test A <: C
155+
@test Tuple{Int,Int8,Vector{Any}} <: C
156+
end
157+
158+
# #26108
159+
@test !issub((Tuple{T, T, Array{T, 1}} where T), Tuple{T, T, Any} where T)
160+
161+
# #26716
162+
@test !issub((Union{Tuple{Int,Bool}, Tuple{P,Bool}} where P), Tuple{Union{T,Int}, T} where T)
139163
end
140164

141165
# level 3: UnionAll
@@ -1475,12 +1499,19 @@ let U = Tuple{Union{LT, LT1},Union{R, R1},Int} where LT1<:R1 where R1<:Tuple{Int
14751499
end
14761500

14771501
# issue #31082 and #30741
1478-
@testintersect(Tuple{T, Ref{T}, T} where T,
1479-
Tuple{Ref{S}, S, S} where S,
1480-
Union{})
1502+
@test typeintersect(Tuple{T, Ref{T}, T} where T,
1503+
Tuple{Ref{S}, S, S} where S) != Union{}
14811504
@testintersect(Tuple{Pair{B,C},Union{C,Pair{B,C}},Union{B,Real}} where {B,C},
14821505
Tuple{Pair{B,C},C,C} where {B,C},
14831506
Tuple{Pair{B,C},C,C} where C<:Union{Real, B} where B)
1507+
f31082(::Pair{B, C}, ::Union{C, Pair{B, C}}, ::Union{B, Real}) where {B, C} = 0
1508+
f31082(::Pair{B, C}, ::C, ::C) where {B, C} = 1
1509+
@test f31082(""=>1, 2, 3) == 1
1510+
@test f31082(""=>1, 2, "") == 0
1511+
@test f31082(""=>1, 2, 3.0) == 0
1512+
@test f31082(Pair{Any,Any}(1,2), 1, 2) == 1
1513+
@test f31082(Pair{Any,Any}(1,2), Pair{Any,Any}(1,2), 2) == 1
1514+
@test f31082(Pair{Any,Any}(1,2), 1=>2, 2.0) == 1
14841515

14851516
# issue #31115
14861517
@testintersect(Tuple{Ref{Z} where Z<:(Ref{Y} where Y<:Tuple{<:B}), Int} where B,

0 commit comments

Comments
 (0)