Skip to content

Commit 44b3d2c

Browse files
committed
morespecific: add rule for Type{Union{}}
Make Type{Union{}} in method definitions always the most specific type (normally these would end up being ambiguous). This ensures we do not invalidate them, nor need to consider ambiguities that might arise from intersections with them.
1 parent 7560dea commit 44b3d2c

File tree

4 files changed

+35
-7
lines changed

4 files changed

+35
-7
lines changed

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ Language changes
88
----------------
99

1010
* When a task forks a child, the parent task's task-local RNG (random number generator) is no longer affected. The seeding of child based on the parent task also takes a more disciplined approach to collision resistance, using a design based on the SplitMix and DotMix splittable RNG schemes ([#49110]).
11+
* A new morespecific rule for methods resolves ambiguities containing Union{} in favor of
12+
the method defined explicitly to handle the Union{} argument. This makes it possible to
13+
define methods to explicitly handle Union{} without the ambiguities that commonly would
14+
result previously. This also lets the runtime optimize certain method lookups in a way
15+
that significantly improves load and inference times for heavily overloaded methods that
16+
dispatch on Types (such as traits and constructors).
1117

1218
Compiler/Runtime improvements
1319
-----------------------------

base/essentials.jl

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -310,13 +310,8 @@ See also: [`round`](@ref), [`trunc`](@ref), [`oftype`](@ref), [`reinterpret`](@r
310310
"""
311311
function convert end
312312

313-
# make convert(::Type{<:Union{}}, x::T) intentionally ambiguous for all T
314-
# so it will never get called or invalidated by loading packages
315-
# with carefully chosen types that won't have any other convert methods defined
316-
convert(T::Type{<:Core.IntrinsicFunction}, x) = throw(MethodError(convert, (T, x)))
317-
convert(T::Type{<:Nothing}, x) = throw(MethodError(convert, (Nothing, x)))
318-
convert(::Type{T}, x::T) where {T<:Core.IntrinsicFunction} = x
319-
convert(::Type{T}, x::T) where {T<:Nothing} = x
313+
# ensure this is never ambiguous, and therefore fast for lookup
314+
convert(T::Type{Union{}}, x) = throw(MethodError(convert, (T, x)))
320315

321316
convert(::Type{Type}, x::Type) = x # the ssair optimizer is strongly dependent on this method existing to avoid over-specialization
322317
# in the absence of inlining-enabled

src/subtype.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4460,6 +4460,21 @@ static int num_occurs(jl_tvar_t *v, jl_typeenv_t *env)
44604460
return 0;
44614461
}
44624462

4463+
int tuple_cmp_typeofbottom(jl_datatype_t *a, jl_datatype_t *b)
4464+
{
4465+
size_t i, la = jl_nparams(a), lb = jl_nparams(b);
4466+
for (i = 0; i < la || i < lb; i++) {
4467+
jl_value_t *pa = i < la ? jl_tparam(a, i) : NULL;
4468+
jl_value_t *pb = i < lb ? jl_tparam(b, i) : NULL;
4469+
int xa = pa == (jl_value_t*)jl_typeofbottom_type || pa == (jl_value_t*)jl_typeofbottom_type->super;
4470+
int xb = pb == (jl_value_t*)jl_typeofbottom_type || pb == (jl_value_t*)jl_typeofbottom_type->super;
4471+
if (xa != xb)
4472+
return xa - xb;
4473+
}
4474+
return 0;
4475+
}
4476+
4477+
44634478
#define HANDLE_UNIONALL_A \
44644479
jl_unionall_t *ua = (jl_unionall_t*)a; \
44654480
jl_typeenv_t newenv = { ua->var, 0x0, env }; \
@@ -4478,6 +4493,13 @@ static int type_morespecific_(jl_value_t *a, jl_value_t *b, jl_value_t *a0, jl_v
44784493
return 0;
44794494

44804495
if (jl_is_tuple_type(a) && jl_is_tuple_type(b)) {
4496+
// compare whether a and b have Type{Union{}} included,
4497+
// which makes them instantly the most specific, regardless of all else,
4498+
// for whichever is left most (the left-to-right behavior here ensures
4499+
// we do not need to keep track of conflicts with multiple methods).
4500+
int msp = tuple_cmp_typeofbottom((jl_datatype_t*)a, (jl_datatype_t*)b);
4501+
if (msp)
4502+
return msp > 0;
44814503
// When one is JL_VARARG_BOUND and the other has fixed length,
44824504
// allow the argument length to fix the tvar
44834505
jl_vararg_kind_t akind = jl_va_tuple_kind((jl_datatype_t*)a);

test/specificity.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,8 @@ let A = Tuple{Type{SubString{S}},AbstractString} where S<:AbstractString,
311311
@test args_morespecific(B, C)
312312
@test args_morespecific(A, C)
313313
end
314+
315+
@test args_morespecific(Tuple{Type{Union{}}, Any}, Tuple{Any, Type{Union{}}})
316+
@test args_morespecific(Tuple{typeof(Union{}), Any}, Tuple{Any, Type{Union{}}})
317+
@test args_morespecific(Tuple{Type{Union{}}, Type{Union{}}, Any}, Tuple{Type{Union{}}, Any, Type{Union{}}})
318+
@test args_morespecific(Tuple{Type{Union{}}, Type{Union{}}, Any, Type{Union{}}}, Tuple{Type{Union{}}, Any, Type{Union{}}, Type{Union{}}})

0 commit comments

Comments
 (0)