Skip to content

Commit 8ef5504

Browse files
N5N3lazarusA
authored andcommitted
typeintersect: followup cleanup for the nothrow path of type instantiation (JuliaLang#54514)
Adopt suggestions from JuliaLang#54465 (review) and fix various added regession & residual MWE.
1 parent 49954e5 commit 8ef5504

File tree

4 files changed

+94
-50
lines changed

4 files changed

+94
-50
lines changed

src/jltypes.c

+67-42
Original file line numberDiff line numberDiff line change
@@ -1499,11 +1499,11 @@ jl_unionall_t *jl_rename_unionall(jl_unionall_t *u)
14991499
return (jl_unionall_t*)t;
15001500
}
15011501

1502-
jl_value_t *jl_substitute_var_nothrow(jl_value_t *t, jl_tvar_t *var, jl_value_t *val)
1502+
jl_value_t *jl_substitute_var_nothrow(jl_value_t *t, jl_tvar_t *var, jl_value_t *val, int nothrow)
15031503
{
15041504
if (val == (jl_value_t*)var)
15051505
return t;
1506-
int nothrow = jl_is_typevar(val) ? 0 : 1;
1506+
nothrow = jl_is_typevar(val) ? 0 : nothrow;
15071507
jl_typeenv_t env = { var, val, NULL };
15081508
return inst_type_w_(t, &env, NULL, 1, nothrow);
15091509
}
@@ -1725,7 +1725,7 @@ void jl_precompute_memoized_dt(jl_datatype_t *dt, int cacheable)
17251725
dt->hash = typekey_hash(dt->name, jl_svec_data(dt->parameters), l, cacheable);
17261726
}
17271727

1728-
static void check_datatype_parameters(jl_typename_t *tn, jl_value_t **params, size_t np)
1728+
static int check_datatype_parameters(jl_typename_t *tn, jl_value_t **params, size_t np, int nothrow)
17291729
{
17301730
jl_value_t *wrapper = tn->wrapper;
17311731
jl_value_t **bounds;
@@ -1743,6 +1743,10 @@ static void check_datatype_parameters(jl_typename_t *tn, jl_value_t **params, si
17431743
assert(jl_is_unionall(wrapper));
17441744
jl_tvar_t *tv = ((jl_unionall_t*)wrapper)->var;
17451745
if (!within_typevar(params[i], bounds[2*i], bounds[2*i+1])) {
1746+
if (nothrow) {
1747+
JL_GC_POP();
1748+
return 1;
1749+
}
17461750
if (tv->lb != bounds[2*i] || tv->ub != bounds[2*i+1])
17471751
// pass a new version of `tv` containing the instantiated bounds
17481752
tv = jl_new_typevar(tv->name, bounds[2*i], bounds[2*i+1]);
@@ -1752,12 +1756,26 @@ static void check_datatype_parameters(jl_typename_t *tn, jl_value_t **params, si
17521756
int j;
17531757
for (j = 2*i + 2; j < 2*np; j++) {
17541758
jl_value_t *bj = bounds[j];
1755-
if (bj != (jl_value_t*)jl_any_type && bj != jl_bottom_type)
1756-
bounds[j] = jl_substitute_var(bj, tv, params[i]);
1759+
if (bj != (jl_value_t*)jl_any_type && bj != jl_bottom_type) {
1760+
int isub = j & 1;
1761+
// use different nothrow level for lb and ub substitution.
1762+
// TODO: This assuming the top instantiation could only start with
1763+
// `nothrow == 2` or `nothrow == 0`. If `nothrow` is initially set to 1
1764+
// then we might miss some inner error, perhaps the normal path should
1765+
// also follow this rule?
1766+
jl_value_t *nb = jl_substitute_var_nothrow(bj, tv, params[i], nothrow ? (isub ? 2 : 1) : 0 );
1767+
if (nb == NULL) {
1768+
assert(nothrow);
1769+
JL_GC_POP();
1770+
return 1;
1771+
}
1772+
bounds[j] = nb;
1773+
}
17571774
}
17581775
wrapper = ((jl_unionall_t*)wrapper)->body;
17591776
}
17601777
JL_GC_POP();
1778+
return 0;
17611779
}
17621780

17631781
static jl_value_t *extract_wrapper(jl_value_t *t JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT JL_GLOBALLY_ROOTED
@@ -2004,13 +2022,8 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value
20042022
// for whether this is even valid
20052023
if (check && !istuple) {
20062024
assert(ntp > 0);
2007-
JL_TRY {
2008-
check_datatype_parameters(tn, iparams, ntp);
2009-
}
2010-
JL_CATCH {
2011-
if (!nothrow) jl_rethrow();
2025+
if (check_datatype_parameters(tn, iparams, ntp, nothrow))
20122026
return NULL;
2013-
}
20142027
}
20152028
else if (ntp == 0 && jl_emptytuple_type != NULL) {
20162029
// empty tuple type case
@@ -2401,7 +2414,8 @@ static jl_value_t *inst_tuple_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_
24012414
jl_value_t *elt = jl_svecref(tp, i);
24022415
jl_value_t *pi = inst_type_w_(elt, env, stack, check, nothrow);
24032416
if (pi == NULL) {
2404-
if (i == ntp-1 && jl_is_vararg(elt)) {
2417+
assert(nothrow);
2418+
if (nothrow == 1 || (i == ntp-1 && jl_is_vararg(elt))) {
24052419
t = NULL;
24062420
break;
24072421
}
@@ -2420,6 +2434,10 @@ static jl_value_t *inst_tuple_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_
24202434
return t;
24212435
}
24222436

2437+
// `nothrow` means that when type checking fails, the type instantiation should
2438+
// return `NULL` instead of immediately throwing an error. If `nothrow` == 2 then
2439+
// we further assume that the imprecise instantiation for non invariant parameters
2440+
// is acceptable, and inner error (`NULL`) would be ignored.
24232441
static jl_value_t *inst_type_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_t *stack, int check, int nothrow)
24242442
{
24252443
size_t i;
@@ -2440,11 +2458,10 @@ static jl_value_t *inst_type_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_t
24402458
jl_value_t *var = NULL;
24412459
jl_value_t *newbody = NULL;
24422460
JL_GC_PUSH3(&lb, &var, &newbody);
2443-
JL_TRY {
2444-
lb = inst_type_w_(ua->var->lb, env, stack, check, 0);
2445-
}
2446-
JL_CATCH {
2447-
if (!nothrow) jl_rethrow();
2461+
// set nothrow <= 1 to ensure lb's accuracy.
2462+
lb = inst_type_w_(ua->var->lb, env, stack, check, nothrow ? 1 : 0);
2463+
if (lb == NULL) {
2464+
assert(nothrow);
24482465
t = NULL;
24492466
}
24502467
if (t != NULL) {
@@ -2468,11 +2485,9 @@ static jl_value_t *inst_type_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_t
24682485
if (newbody == NULL) {
24692486
t = NULL;
24702487
}
2471-
else if (newbody == (jl_value_t*)jl_emptytuple_type) {
2472-
// NTuple{0} => Tuple{} can make a typevar disappear
2473-
t = (jl_value_t*)jl_emptytuple_type;
2474-
}
2475-
else if (nothrow && !jl_has_typevar(newbody, (jl_tvar_t *)var)) {
2488+
else if (!jl_has_typevar(newbody, (jl_tvar_t *)var)) {
2489+
// inner instantiation might make a typevar disappear, e.g.
2490+
// NTuple{0,T} => Tuple{}
24762491
t = newbody;
24772492
}
24782493
else if (newbody != ua->body || var != (jl_value_t*)ua->var) {
@@ -2489,16 +2504,21 @@ static jl_value_t *inst_type_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_t
24892504
jl_value_t *b = NULL;
24902505
JL_GC_PUSH2(&a, &b);
24912506
b = inst_type_w_(u->b, env, stack, check, nothrow);
2507+
if (nothrow) {
2508+
// ensure jl_type_union nothrow.
2509+
if (a && !(jl_is_typevar(a) || jl_is_type(a)))
2510+
a = NULL;
2511+
if (b && !(jl_is_typevar(b) || jl_is_type(b)))
2512+
b = NULL;
2513+
}
24922514
if (a != u->a || b != u->b) {
24932515
if (!check) {
24942516
// fast path for `jl_rename_unionall`.
24952517
t = jl_new_struct(jl_uniontype_type, a, b);
24962518
}
2497-
else if (nothrow && a == NULL) {
2498-
t = b;
2499-
}
2500-
else if (nothrow && b == NULL) {
2501-
t = a;
2519+
else if (a == NULL || b == NULL) {
2520+
assert(nothrow);
2521+
t = nothrow == 1 ? NULL : a == NULL ? b : a;
25022522
}
25032523
else {
25042524
assert(a != NULL && b != NULL);
@@ -2516,15 +2536,21 @@ static jl_value_t *inst_type_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_t
25162536
JL_GC_PUSH2(&T, &N);
25172537
if (v->T) {
25182538
T = inst_type_w_(v->T, env, stack, check, nothrow);
2519-
if (T == NULL)
2520-
T = jl_bottom_type;
2521-
if (v->N) // This branch should never throw.
2522-
N = inst_type_w_(v->N, env, stack, check, 0);
2539+
if (T == NULL) {
2540+
if (nothrow == 2)
2541+
T = jl_bottom_type;
2542+
else
2543+
t = NULL;
2544+
}
2545+
if (t && v->N) {
2546+
// set nothrow <= 1 to ensure invariant parameter's accuracy.
2547+
N = inst_type_w_(v->N, env, stack, check, nothrow ? 1 : 0);
2548+
if (N == NULL)
2549+
t = NULL;
2550+
}
25232551
}
2524-
if (T != v->T || N != v->N) {
2525-
// `Vararg` is special, we'd better handle inner error at Tuple level.
2552+
if (t && (T != v->T || N != v->N))
25262553
t = (jl_value_t*)jl_wrap_vararg(T, N, check, nothrow);
2527-
}
25282554
JL_GC_POP();
25292555
return t;
25302556
}
@@ -2543,16 +2569,15 @@ static jl_value_t *inst_type_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_t
25432569
int bound = 0;
25442570
for (i = 0; i < ntp; i++) {
25452571
jl_value_t *elt = jl_svecref(tp, i);
2546-
JL_TRY {
2547-
jl_value_t *pi = inst_type_w_(elt, env, stack, check, 0);
2548-
iparams[i] = pi;
2549-
bound |= (pi != elt);
2550-
}
2551-
JL_CATCH {
2552-
if (!nothrow) jl_rethrow();
2572+
// set nothrow <= 1 to ensure invariant parameter's accuracy.
2573+
jl_value_t *pi = inst_type_w_(elt, env, stack, check, nothrow ? 1 : 0);
2574+
if (pi == NULL) {
2575+
assert(nothrow);
25532576
t = NULL;
2577+
break;
25542578
}
2555-
if (t == NULL) break;
2579+
iparams[i] = pi;
2580+
bound |= (pi != elt);
25562581
}
25572582
// if t's parameters are not bound in the environment, return it uncopied (#9378)
25582583
if (t != NULL && bound)

src/julia_internal.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@ JL_DLLEXPORT int jl_type_morespecific_no_subtype(jl_value_t *a, jl_value_t *b);
757757
jl_value_t *jl_instantiate_type_with(jl_value_t *t, jl_value_t **env, size_t n);
758758
JL_DLLEXPORT jl_value_t *jl_instantiate_type_in_env(jl_value_t *ty, jl_unionall_t *env, jl_value_t **vals);
759759
jl_value_t *jl_substitute_var(jl_value_t *t, jl_tvar_t *var, jl_value_t *val);
760-
jl_value_t *jl_substitute_var_nothrow(jl_value_t *t, jl_tvar_t *var, jl_value_t *val);
760+
jl_value_t *jl_substitute_var_nothrow(jl_value_t *t, jl_tvar_t *var, jl_value_t *val, int nothrow);
761761
jl_unionall_t *jl_rename_unionall(jl_unionall_t *u);
762762
JL_DLLEXPORT jl_value_t *jl_unwrap_unionall(jl_value_t *v JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT;
763763
JL_DLLEXPORT jl_value_t *jl_rewrap_unionall(jl_value_t *t, jl_value_t *u);

src/subtype.c

+7-5
Original file line numberDiff line numberDiff line change
@@ -2784,7 +2784,7 @@ static jl_value_t *omit_bad_union(jl_value_t *u, jl_tvar_t *t)
27842784
res = jl_bottom_type;
27852785
}
27862786
else if (obviously_egal(var->lb, ub)) {
2787-
res = jl_substitute_var_nothrow(body, var, ub);
2787+
res = jl_substitute_var_nothrow(body, var, ub, 2);
27882788
if (res == NULL)
27892789
res = jl_bottom_type;
27902790
}
@@ -2958,9 +2958,11 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind
29582958
}
29592959
}
29602960
if (varval) {
2961-
*btemp->ub = jl_substitute_var_nothrow(iub, vb->var, varval);
2962-
if (*btemp->ub == NULL)
2961+
iub = jl_substitute_var_nothrow(iub, vb->var, varval, 2);
2962+
if (iub == NULL)
29632963
res = jl_bottom_type;
2964+
else
2965+
*btemp->ub = iub;
29642966
}
29652967
else if (iub == (jl_value_t*)vb->var) {
29662968
// TODO: this loses some constraints, such as in this test, where we replace T4<:S3 (e.g. T4==S3 since T4 only appears covariantly once) with T4<:Any
@@ -3091,12 +3093,12 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind
30913093
if (varval) {
30923094
// you can construct `T{x} where x` even if T's parameter is actually
30933095
// limited. in that case we might get an invalid instantiation here.
3094-
res = jl_substitute_var_nothrow(res, vb->var, varval);
3096+
res = jl_substitute_var_nothrow(res, vb->var, varval, 2);
30953097
// simplify chains of UnionAlls where bounds become equal
30963098
while (res != NULL && jl_is_unionall(res) && obviously_egal(((jl_unionall_t*)res)->var->lb,
30973099
((jl_unionall_t*)res)->var->ub)) {
30983100
jl_unionall_t * ures = (jl_unionall_t *)res;
3099-
res = jl_substitute_var_nothrow(ures->body, ures->var, ures->var->lb);
3101+
res = jl_substitute_var_nothrow(ures->body, ures->var, ures->var->lb, 2);
31003102
}
31013103
if (res == NULL)
31023104
res = jl_bottom_type;

test/subtype.jl

+19-2
Original file line numberDiff line numberDiff line change
@@ -2617,13 +2617,30 @@ end
26172617
#issue 54356
26182618
abstract type A54356{T<:Real} end
26192619
struct B54356{T} <: A54356{T} end
2620-
let S = Tuple{Val, Val{T}} where {T}, R = Tuple{Val{Val{T}}, Val{T}} where {T}
2621-
# general parameters check
2620+
struct C54356{S,T<:Union{S,Complex{S}}} end
2621+
struct D54356{S<:Real,T} end
2622+
let S = Tuple{Val, Val{T}} where {T}, R = Tuple{Val{Val{T}}, Val{T}} where {T},
2623+
SS = Tuple{Val, Val{T}, Val{T}} where {T}, RR = Tuple{Val{Val{T}}, Val{T}, Val{T}} where {T}
2624+
# parameters check for self
26222625
@testintersect(Tuple{Val{A}, A} where {B, A<:Union{Val{B}, Complex{B}}}, S{1}, R{1})
2626+
# parameters check for supertype (B54356 -> A54356)
26232627
@testintersect(Tuple{Val{A}, A} where {B, A<:Union{Val{B}, B54356{B}}}, S{1}, R{1})
2628+
# enure unused TypeVar skips the `UnionAll` wrapping
2629+
@testintersect(Tuple{Val{A}, A} where {B, A<:(Union{Val{B}, D54356{B,C}} where {C})}, S{1}, R{1})
2630+
# invariant parameter should not get narrowed
2631+
@testintersect(Tuple{Val{A}, A} where {B, A<:Union{Val{B}, Val{Union{Int,Complex{B}}}}}, S{1}, R{1})
2632+
# bit value could not be `Union` element
2633+
@testintersect(Tuple{Val{A}, A, Val{B}} where {B, A<:Union{B, Val{B}}}, SS{1}, RR{1})
2634+
@testintersect(Tuple{Val{A}, A, Val{B}} where {B, A<:Union{B, Complex{B}}}, SS{1}, Union{})
2635+
# `check_datatype_parameters` should ignore bad `Union` elements in constraint's ub
2636+
T = Tuple{Val{Union{Val{Nothing}, Val{C54356{V,V}}}}, Val{Nothing}} where {Nothing<:V<:Nothing}
2637+
@test T <: S{Nothing}
2638+
@test T <: Tuple{Val{A}, A} where {B, C, A<:Union{Val{B}, Val{C54356{B,C}}}}
2639+
@test T <: typeintersect(Tuple{Val{A}, A} where {B, C, A<:Union{Val{B}, Val{C54356{B,C}}}}, S{Nothing})
26242640
# extra check for Vararg
26252641
@testintersect(Tuple{Val{A}, A} where {B, A<:Union{Val{B}, NTuple{B,Any}}}, S{-1}, R{-1})
26262642
@testintersect(Tuple{Val{A}, A} where {B, A<:Union{Val{B}, Tuple{Any,Vararg{Any,B}}}}, S{-1}, R{-1})
2643+
@testintersect(Tuple{Val{A}, A} where {B, A<:Union{Val{B}, Tuple{Vararg{Int,Union{Int,Complex{B}}}}}}, S{1}, R{1})
26272644
# extra check for NamedTuple
26282645
@testintersect(Tuple{Val{A}, A} where {B, A<:Union{Val{B}, NamedTuple{B,Tuple{Int}}}}, S{1}, R{1})
26292646
@testintersect(Tuple{Val{A}, A} where {B, A<:Union{Val{B}, NamedTuple{B,Tuple{Int}}}}, S{(1,)}, R{(1,)})

0 commit comments

Comments
 (0)