Skip to content

Commit

Permalink
Allow exact redefinition for types with recursive supertype reference (
Browse files Browse the repository at this point in the history
…JuliaLang#55380)

This PR allows redefining a type when the new type is exactly identical
to the previous one (like JuliaLang#17618, JuliaLang#20592 and JuliaLang#21024), even if the type
has a reference to itself in its supertype. That particular case used to
error (issue JuliaLang#54757), whereas with this PR:
```julia
julia> struct Rec <: AbstractVector{Rec} end

julia> struct Rec <: AbstractVector{Rec} end # this used to error

julia>
```


Fix JuliaLang#54757 by implementing the solution proposed there. Hence, this
should also fix downstream Revise bug
timholy/Revise.jl#813.

---------

Co-authored-by: N5N3 <[email protected]>
  • Loading branch information
Liozou and N5N3 authored Sep 2, 2024
1 parent 3a2a4d8 commit 5c706af
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -2197,6 +2197,9 @@ static int equiv_type(jl_value_t *ta, jl_value_t *tb)
JL_GC_PUSH2(&a, &b);
a = jl_rewrap_unionall((jl_value_t*)dta->super, dta->name->wrapper);
b = jl_rewrap_unionall((jl_value_t*)dtb->super, dtb->name->wrapper);
// if tb recursively refers to itself in its supertype, assume that it refers to ta
// before checking whether the supertypes are equal
b = jl_substitute_datatype(b, dtb, dta);
if (!jl_types_equal(a, b))
goto no;
JL_TRY {
Expand Down
112 changes: 112 additions & 0 deletions src/jltypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,118 @@ jl_value_t *jl_rewrap_unionall_(jl_value_t *t, jl_value_t *u)
return t;
}

// Create a copy of type expression t where any occurrence of data type x is replaced by y.
// If x does not occur in t, return t without any copy.
// For example, jl_substitute_datatype(Foo{Bar}, Foo{T}, Qux{S}) is Qux{Bar}, with T and S
// free type variables.
// To substitute type variables, use jl_substitute_var instead.
jl_value_t *jl_substitute_datatype(jl_value_t *t, jl_datatype_t * x, jl_datatype_t * y)
{
if jl_is_datatype(t) {
jl_datatype_t *typ = (jl_datatype_t*)t;
// For datatypes call itself recursively on the parameters to form new parameters.
// Then, if typename(t) == typename(x), rewrap the wrapper of y around the new
// parameters. Otherwise, do the same around the wrapper of t.
// This ensures that the types and supertype are properly set.
// Start by check whether there is a parameter that needs replacing.
long i_firstnewparam = -1;
size_t nparams = jl_svec_len(typ->parameters);
jl_value_t *firstnewparam = NULL;
JL_GC_PUSH1(&firstnewparam);
for (size_t i = 0; i < nparams; i++) {
jl_value_t *param = NULL;
JL_GC_PUSH1(&param);
param = jl_svecref(typ->parameters, i);
firstnewparam = jl_substitute_datatype(param, x, y);
if (param != firstnewparam) {
i_firstnewparam = i;
JL_GC_POP();
break;
}
JL_GC_POP();
}
// If one of the parameters needs to be updated, or if the type name is that to
// substitute, create a new datataype
if (i_firstnewparam != -1 || typ->name == x->name) {
jl_datatype_t *uw = typ->name == x->name ? y : typ; // substitution occurs here
jl_value_t *wrapper = uw->name->wrapper;
jl_datatype_t *w = (jl_datatype_t*)jl_unwrap_unionall(wrapper);
jl_svec_t *sv = jl_alloc_svec_uninit(jl_svec_len(uw->parameters));
JL_GC_PUSH1(&sv);
jl_value_t **vals = jl_svec_data(sv);
// no JL_GC_PUSHARGS(vals, ...) since GC is already aware of sv
for (long i = 0; i < i_firstnewparam; i++) { // copy the identical parameters
vals[i] = jl_svecref(typ->parameters, i); // value
}
if (i_firstnewparam != -1) { // insert the first non-identical parameter
vals[i_firstnewparam] = firstnewparam;
}
for (size_t i = i_firstnewparam+1; i < nparams; i++) { // insert the remaining parameters
vals[i] = jl_substitute_datatype(jl_svecref(typ->parameters, i), x, y);
}
if (jl_is_tuple_type(wrapper)) {
// special case for tuples, since the wrapper (Tuple) does not have as
// many parameters as t (it only has a Vararg instead).
t = jl_apply_tuple_type(sv, 0);
} else {
t = jl_instantiate_type_in_env((jl_value_t*)w, (jl_unionall_t*)wrapper, vals);
}
JL_GC_POP();
}
JL_GC_POP();
}
else if jl_is_unionall(t) { // recursively call itself on body and var bounds
jl_unionall_t* ut = (jl_unionall_t*)t;
jl_value_t *lb = NULL;
jl_value_t *ub = NULL;
jl_value_t *body = NULL;
JL_GC_PUSH3(&lb, &ub, &body);
lb = jl_substitute_datatype(ut->var->lb, x, y);
ub = jl_substitute_datatype(ut->var->ub, x, y);
body = jl_substitute_datatype(ut->body, x, y);
if (lb != ut->var->lb || ub != ut->var->ub) {
jl_tvar_t *newtvar = jl_new_typevar(ut->var->name, lb, ub);
JL_GC_PUSH1(&newtvar);
body = jl_substitute_var(body, ut->var, (jl_value_t*)newtvar);
t = jl_new_struct(jl_unionall_type, newtvar, body);
JL_GC_POP();
}
else if (body != ut->body) {
t = jl_new_struct(jl_unionall_type, ut->var, body);
}
JL_GC_POP();
}
else if jl_is_uniontype(t) { // recursively call itself on a and b
jl_uniontype_t *u = (jl_uniontype_t*)t;
jl_value_t *a = NULL;
jl_value_t *b = NULL;
JL_GC_PUSH2(&a, &b);
a = jl_substitute_datatype(u->a, x, y);
b = jl_substitute_datatype(u->b, x, y);
if (a != u->a || b != u->b) {
t = jl_new_struct(jl_uniontype_type, a, b);
}
JL_GC_POP();
}
else if jl_is_vararg(t) { // recursively call itself on T
jl_vararg_t *vt = (jl_vararg_t*)t;
if (vt->T) { // vt->T could be NULL
jl_value_t *rT = NULL;
JL_GC_PUSH1(&rT);
rT = jl_substitute_datatype(vt->T, x, y);
if (rT != vt->T) {
jl_task_t *ct = jl_current_task;
t = jl_gc_alloc(ct->ptls, sizeof(jl_vararg_t), jl_vararg_type);
jl_set_typetagof((jl_vararg_t *)t, jl_vararg_tag, 0);
((jl_vararg_t *)t)->T = rT;
((jl_vararg_t *)t)->N = vt->N;
}
JL_GC_POP();
}
}
return t;
}

static jl_value_t *lookup_type_stack(jl_typestack_t *stack, jl_datatype_t *tt, size_t ntp,
jl_value_t **iparams)
{
Expand Down
1 change: 1 addition & 0 deletions src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,7 @@ jl_unionall_t *jl_rename_unionall(jl_unionall_t *u);
JL_DLLEXPORT jl_value_t *jl_unwrap_unionall(jl_value_t *v JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT;
JL_DLLEXPORT jl_value_t *jl_rewrap_unionall(jl_value_t *t, jl_value_t *u);
JL_DLLEXPORT jl_value_t *jl_rewrap_unionall_(jl_value_t *t, jl_value_t *u);
jl_value_t* jl_substitute_datatype(jl_value_t *t, jl_datatype_t * x, jl_datatype_t * y);
int jl_count_union_components(jl_value_t *v);
JL_DLLEXPORT jl_value_t *jl_nth_union_component(jl_value_t *v JL_PROPAGATES_ROOT, int i) JL_NOTSAFEPOINT;
int jl_find_union_component(jl_value_t *haystack, jl_value_t *needle, unsigned *nth) JL_NOTSAFEPOINT;
Expand Down
20 changes: 20 additions & 0 deletions test/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5611,6 +5611,26 @@ end
x::Array{T} where T<:Integer
end

# issue #54757, type redefinitions with recursive reference in supertype
struct T54757{A>:Int,N} <: AbstractArray{Tuple{X,Tuple{Vararg},Union{T54757{Union{X,Integer}},T54757{A,N}},Vararg{Y,N}} where {X,Y<:T54757}, N}
x::A
y::Union{A,T54757{A,N}}
z::T54757{A}
end

struct T54757{A>:Int,N} <: AbstractArray{Tuple{X,Tuple{Vararg},Union{T54757{Union{X,Integer}},T54757{A,N}},Vararg{Y,N}} where {X,Y<:T54757}, N}
x::A
y::Union{A,T54757{A,N}}
z::T54757{A}
end

@test_throws ErrorException struct T54757{A>:Int,N} <: AbstractArray{Tuple{X,Tuple{Vararg},Union{T54757{Union{X,Integer}},T54757{A}},Vararg{Y,N}} where {X,Y<:T54757}, N}
x::A
y::Union{A,T54757{A,N}}
z::T54757{A}
end


let a = Vector{Core.TypeofBottom}(undef, 2)
@test a[1] == Union{}
@test a == [Union{}, Union{}]
Expand Down

0 comments on commit 5c706af

Please sign in to comment.