From 7560dea912ca1604598cda8d1744e456943b5382 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 18 Apr 2023 15:51:12 -0400 Subject: [PATCH 1/4] update Statistics.jl Removes some overly strict `@test_throws MethodError` for calls with `Union{}` or `Any`, in preparation for making those errors more precise. --- .../md5 | 1 + .../sha512 | 1 + .../md5 | 1 - .../sha512 | 1 - stdlib/Statistics.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/md5 create mode 100644 deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/sha512 delete mode 100644 deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/md5 delete mode 100644 deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/sha512 diff --git a/deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/md5 b/deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/md5 new file mode 100644 index 0000000000000..7e7a889eecd29 --- /dev/null +++ b/deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/md5 @@ -0,0 +1 @@ +6564297a5f5971231809bf9940f68b98 diff --git a/deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/sha512 b/deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/sha512 new file mode 100644 index 0000000000000..bbe9b8bed6371 --- /dev/null +++ b/deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/sha512 @@ -0,0 +1 @@ +22d14c82a30f3ec7af09028423cc823808abf86918d5707fd1fcf6ca20dea7871589da9b22e462d194e86fcee380f549aeb65f585048f00bf23281786b17e040 diff --git a/deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/md5 b/deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/md5 deleted file mode 100644 index 0e2d0534cd8c7..0000000000000 --- a/deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -62d47cffac86df3c59b3de8dd218aa79 diff --git a/deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/sha512 b/deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/sha512 deleted file mode 100644 index 95e88c63f1a14..0000000000000 --- a/deps/checksums/Statistics-e9ac70b760dcf87b77affe6c068548a3325d6e2b.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -6354b1e84d7df1fe8d7e1444181497cac87d22d10a2a21b9f7fab748c209bd9aba64f2df6489e9441624fcf27140ccffa3f7eabaf2517f4900b2661be0c74ba5 diff --git a/stdlib/Statistics.version b/stdlib/Statistics.version index 22857e138655a..27197b12be54c 100644 --- a/stdlib/Statistics.version +++ b/stdlib/Statistics.version @@ -1,4 +1,4 @@ STATISTICS_BRANCH = master -STATISTICS_SHA1 = e9ac70b760dcf87b77affe6c068548a3325d6e2b +STATISTICS_SHA1 = a3feba2bb63f06b7f40024185e9fa5f6385e2510 STATISTICS_GIT_URL := https://github.com/JuliaStats/Statistics.jl.git STATISTICS_TAR_URL = https://api.github.com/repos/JuliaStats/Statistics.jl/tarball/$1 From 44b3d2c8018d1b8da5d9f5be5166701c49b572e2 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 12 Apr 2023 17:20:17 -0400 Subject: [PATCH 2/4] 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. --- NEWS.md | 6 ++++++ base/essentials.jl | 9 ++------- src/subtype.c | 22 ++++++++++++++++++++++ test/specificity.jl | 5 +++++ 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/NEWS.md b/NEWS.md index 33fd3549284d5..931db0ad1081f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,12 @@ Language changes ---------------- * 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]). +* A new morespecific rule for methods resolves ambiguities containing Union{} in favor of + the method defined explicitly to handle the Union{} argument. This makes it possible to + define methods to explicitly handle Union{} without the ambiguities that commonly would + result previously. This also lets the runtime optimize certain method lookups in a way + that significantly improves load and inference times for heavily overloaded methods that + dispatch on Types (such as traits and constructors). Compiler/Runtime improvements ----------------------------- diff --git a/base/essentials.jl b/base/essentials.jl index 829341c482383..1cf3be297edb7 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -310,13 +310,8 @@ See also: [`round`](@ref), [`trunc`](@ref), [`oftype`](@ref), [`reinterpret`](@r """ function convert end -# make convert(::Type{<:Union{}}, x::T) intentionally ambiguous for all T -# so it will never get called or invalidated by loading packages -# with carefully chosen types that won't have any other convert methods defined -convert(T::Type{<:Core.IntrinsicFunction}, x) = throw(MethodError(convert, (T, x))) -convert(T::Type{<:Nothing}, x) = throw(MethodError(convert, (Nothing, x))) -convert(::Type{T}, x::T) where {T<:Core.IntrinsicFunction} = x -convert(::Type{T}, x::T) where {T<:Nothing} = x +# ensure this is never ambiguous, and therefore fast for lookup +convert(T::Type{Union{}}, x) = throw(MethodError(convert, (T, x))) convert(::Type{Type}, x::Type) = x # the ssair optimizer is strongly dependent on this method existing to avoid over-specialization # in the absence of inlining-enabled diff --git a/src/subtype.c b/src/subtype.c index 336d697423845..c1c03763ecf10 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -4460,6 +4460,21 @@ static int num_occurs(jl_tvar_t *v, jl_typeenv_t *env) return 0; } +int tuple_cmp_typeofbottom(jl_datatype_t *a, jl_datatype_t *b) +{ + size_t i, la = jl_nparams(a), lb = jl_nparams(b); + for (i = 0; i < la || i < lb; i++) { + jl_value_t *pa = i < la ? jl_tparam(a, i) : NULL; + jl_value_t *pb = i < lb ? jl_tparam(b, i) : NULL; + int xa = pa == (jl_value_t*)jl_typeofbottom_type || pa == (jl_value_t*)jl_typeofbottom_type->super; + int xb = pb == (jl_value_t*)jl_typeofbottom_type || pb == (jl_value_t*)jl_typeofbottom_type->super; + if (xa != xb) + return xa - xb; + } + return 0; +} + + #define HANDLE_UNIONALL_A \ jl_unionall_t *ua = (jl_unionall_t*)a; \ 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 return 0; if (jl_is_tuple_type(a) && jl_is_tuple_type(b)) { + // compare whether a and b have Type{Union{}} included, + // which makes them instantly the most specific, regardless of all else, + // for whichever is left most (the left-to-right behavior here ensures + // we do not need to keep track of conflicts with multiple methods). + int msp = tuple_cmp_typeofbottom((jl_datatype_t*)a, (jl_datatype_t*)b); + if (msp) + return msp > 0; // When one is JL_VARARG_BOUND and the other has fixed length, // allow the argument length to fix the tvar jl_vararg_kind_t akind = jl_va_tuple_kind((jl_datatype_t*)a); diff --git a/test/specificity.jl b/test/specificity.jl index 5808ac71fa54b..9b605444bad42 100644 --- a/test/specificity.jl +++ b/test/specificity.jl @@ -311,3 +311,8 @@ let A = Tuple{Type{SubString{S}},AbstractString} where S<:AbstractString, @test args_morespecific(B, C) @test args_morespecific(A, C) end + +@test args_morespecific(Tuple{Type{Union{}}, Any}, Tuple{Any, Type{Union{}}}) +@test args_morespecific(Tuple{typeof(Union{}), Any}, Tuple{Any, Type{Union{}}}) +@test args_morespecific(Tuple{Type{Union{}}, Type{Union{}}, Any}, Tuple{Type{Union{}}, Any, Type{Union{}}}) +@test args_morespecific(Tuple{Type{Union{}}, Type{Union{}}, Any, Type{Union{}}}, Tuple{Type{Union{}}, Any, Type{Union{}}, Type{Union{}}}) From 67aa46273ac47da6d6b91e732bef5ca5d3b8d7d7 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 12 Apr 2023 14:12:55 -0400 Subject: [PATCH 3/4] add typemap filtering option for Union{} Based on the new morespecific rule for Union{} and method definitions of the specific form `f(..., Type{Union{}}, Vararg)`. If a method definition exists with that specific form, the intersection visitor will ignore all intersections that have that as their only result, saving significant effort when working with lookups involving `Type{<:T}` (which usually ended up mostly ambiguous anyways). Fixes: https://github.com/JuliaLang/julia/issues/33780 This pattern turns out to have still to been making package loading slow. We could keep adding methods following the ambiguity pattern https://github.com/JuliaLang/julia/pull/46000 for the couple specific functions that need it (constructor, eltype, IteratorEltype, IteratorSize, and maybe a couple others) so the internals can detect those and optimize functions that have that method pair. But it seems somewhat odd, convoluted, and non-obvious behavior there. Instead, this breaks all ambiguities in which Union{} is present explicitly in favor of the method with Union{}. This means that when computing method matches, as soon as we see one method definition with Union{}, we can record that the method is the only possible match for that slot. This, in essence, permits creating a rule for dispatch that a TypeVar lower bound must be strictly a supertype of Union{}, but this creates it at the function level, instead of expecting the user to add it to every TypeVar they use to define methods. This also lets us improve the error message for these cases (generally they should error to avoid polluting the inference result), since we can be assured this method will be called, and not result in an ambiguous MethodError instead! Reverts the functional change of #46000 --- base/abstractarray.jl | 5 +- base/array.jl | 3 + base/arrayshow.jl | 2 + base/boot.jl | 20 +-- base/broadcast.jl | 6 +- base/complex.jl | 1 + base/essentials.jl | 3 +- base/float.jl | 1 + base/generator.jl | 8 +- base/indices.jl | 2 +- base/io.jl | 2 + base/iterators.jl | 2 + base/missing.jl | 2 +- base/number.jl | 4 + base/operators.jl | 1 + base/parse.jl | 2 + base/promotion.jl | 6 + base/some.jl | 1 + base/traits.jl | 4 +- src/gf.c | 19 ++- src/jltypes.c | 2 +- src/julia_internal.h | 2 + src/subtype.c | 27 +++- src/typemap.c | 323 ++++++++++++++++++++++++++++-------------- test/abstractarray.jl | 3 - test/ambiguous.jl | 6 +- test/core.jl | 6 +- test/missing.jl | 4 +- test/some.jl | 2 +- 29 files changed, 319 insertions(+), 150 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 7be3f39d16def..cb3956eb7c6d4 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -183,11 +183,13 @@ CartesianIndex{2} For arrays, this function requires at least Julia 1.2. """ keytype(a::AbstractArray) = keytype(typeof(a)) +keytype(::Type{Union{}}, slurp...) = eltype(Union{}) keytype(A::Type{<:AbstractArray}) = CartesianIndex{ndims(A)} keytype(A::Type{<:AbstractVector}) = Int valtype(a::AbstractArray) = valtype(typeof(a)) +valtype(::Type{Union{}}, slurp...) = eltype(Union{}) """ valtype(T::Type{<:AbstractArray}) @@ -232,7 +234,7 @@ UInt8 ``` """ eltype(::Type) = Any -eltype(::Type{Bottom}) = throw(ArgumentError("Union{} does not have elements")) +eltype(::Type{Bottom}, slurp...) = throw(ArgumentError("Union{} does not have elements")) eltype(x) = eltype(typeof(x)) eltype(::Type{<:AbstractArray{E}}) where {E} = @isdefined(E) ? E : Any @@ -268,6 +270,7 @@ julia> ndims(A) """ ndims(::AbstractArray{T,N}) where {T,N} = N ndims(::Type{<:AbstractArray{<:Any,N}}) where {N} = N +ndims(::Type{Union{}}, slurp...) = throw(ArgumentError("Union{} does not have elements")) """ length(collection) -> Integer diff --git a/base/array.jl b/base/array.jl index 60b1304f20ee0..0a5451bac5b74 100644 --- a/base/array.jl +++ b/base/array.jl @@ -252,7 +252,10 @@ function bitsunionsize(u::Union) return sz end +# Deprecate this, as it seems to have no documented meaning and is unused here, +# but is frequently accessed in packages elsize(@nospecialize _::Type{A}) where {T,A<:Array{T}} = aligned_sizeof(T) +elsize(::Type{Union{}}, slurp...) = 0 sizeof(a::Array) = Core.sizeof(a) function isassigned(a::Array, i::Int...) diff --git a/base/arrayshow.jl b/base/arrayshow.jl index af65df3c97b9d..e600e6281bd15 100644 --- a/base/arrayshow.jl +++ b/base/arrayshow.jl @@ -540,10 +540,12 @@ end # returning Any, as this would cause incorrect printing in e.g. `Vector[Any[1]]`, # because eltype(Vector) == Any so `Any` wouldn't be printed in `Any[1]`) typeinfo_eltype(typeinfo) = nothing # element type not precisely known +typeinfo_eltype(typeinfo::Type{Union{}}, slurp...) = nothing typeinfo_eltype(typeinfo::Type{<:AbstractArray{T}}) where {T} = eltype(typeinfo) typeinfo_eltype(typeinfo::Type{<:AbstractDict{K,V}}) where {K,V} = eltype(typeinfo) typeinfo_eltype(typeinfo::Type{<:AbstractSet{T}}) where {T} = eltype(typeinfo) + # types that can be parsed back accurately from their un-decorated representations function typeinfo_implicit(@nospecialize(T)) if T === Float64 || T === Int || T === Char || T === String || T === Symbol || diff --git a/base/boot.jl b/base/boot.jl index ca6e6c81405e2..3a8abde4bce14 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -258,9 +258,17 @@ UnionAll(v::TypeVar, @nospecialize(t)) = ccall(:jl_type_unionall, Any, (Any, Any const Vararg = ccall(:jl_toplevel_eval_in, Any, (Any, Any), Core, _expr(:new, TypeofVararg)) -# let the compiler assume that calling Union{} as a constructor does not need -# to be considered ever (which comes up often as Type{<:T}) -Union{}(a...) = throw(MethodError(Union{}, a)) +# dispatch token indicating a kwarg (keyword sorter) call +function kwcall end +# deprecated internal functions: +kwfunc(@nospecialize(f)) = kwcall +kwftype(@nospecialize(t)) = typeof(kwcall) + +# Let the compiler assume that calling Union{} as a constructor does not need +# to be considered ever (which comes up often as Type{<:T} inference, and +# occasionally in user code from eltype). +Union{}(a...) = throw(ArgumentError("cannot construct a value of type Union{} for return result")) +kwcall(kwargs, ::Type{Union{}}, a...) = Union{}(a...) Expr(@nospecialize args...) = _expr(args...) @@ -369,12 +377,6 @@ include(m::Module, fname::String) = ccall(:jl_load_, Any, (Any, Any), m, fname) eval(m::Module, @nospecialize(e)) = ccall(:jl_toplevel_eval_in, Any, (Any, Any), m, e) -# dispatch token indicating a kwarg (keyword sorter) call -function kwcall end -# deprecated internal functions: -kwfunc(@nospecialize(f)) = kwcall -kwftype(@nospecialize(t)) = typeof(kwcall) - mutable struct Box contents::Any Box(@nospecialize(x)) = new(x) diff --git a/base/broadcast.jl b/base/broadcast.jl index 955a5652353d7..1e057789509ed 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -34,6 +34,9 @@ that you may be able to leverage; see the """ abstract type BroadcastStyle end +struct Unknown <: BroadcastStyle end +BroadcastStyle(::Type{Union{}}, slurp...) = Unknown() # ambiguity resolution + """ `Broadcast.Style{C}()` defines a [`BroadcastStyle`](@ref) signaling through the type parameter `C`. You can use this as an alternative to creating custom subtypes of `BroadcastStyle`, @@ -45,9 +48,6 @@ struct Style{T} <: BroadcastStyle end BroadcastStyle(::Type{<:Tuple}) = Style{Tuple}() -struct Unknown <: BroadcastStyle end -BroadcastStyle(::Type{Union{}}) = Unknown() # ambiguity resolution - """ `Broadcast.AbstractArrayStyle{N} <: BroadcastStyle` is the abstract supertype for any style associated with an `AbstractArray` type. diff --git a/base/complex.jl b/base/complex.jl index 4ce43687aa932..a0473c90d5c17 100644 --- a/base/complex.jl +++ b/base/complex.jl @@ -120,6 +120,7 @@ Float64 real(T::Type) = typeof(real(zero(T))) real(::Type{T}) where {T<:Real} = T real(C::Type{<:Complex}) = fieldtype(C, 1) +real(::Type{Union{}}, slurp...) = Union{}(im) """ isreal(x) -> Bool diff --git a/base/essentials.jl b/base/essentials.jl index 1cf3be297edb7..e2035601f4fb5 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -311,7 +311,7 @@ See also: [`round`](@ref), [`trunc`](@ref), [`oftype`](@ref), [`reinterpret`](@r function convert end # ensure this is never ambiguous, and therefore fast for lookup -convert(T::Type{Union{}}, x) = throw(MethodError(convert, (T, x))) +convert(T::Type{Union{}}, x...) = throw(ArgumentError("cannot convert a value to Union{} for assignment")) convert(::Type{Type}, x::Type) = x # the ssair optimizer is strongly dependent on this method existing to avoid over-specialization # in the absence of inlining-enabled @@ -535,6 +535,7 @@ Neither `convert` nor `cconvert` should take a Julia object and turn it into a ` function cconvert end cconvert(T::Type, x) = x isa T ? x : convert(T, x) # do the conversion eagerly in most cases +cconvert(::Type{Union{}}, x...) = convert(Union{}, x...) cconvert(::Type{<:Ptr}, x) = x # but defer the conversion to Ptr to unsafe_convert unsafe_convert(::Type{T}, x::T) where {T} = x # unsafe_convert (like convert) defaults to assuming the convert occurred unsafe_convert(::Type{T}, x::T) where {T<:Ptr} = x # to resolve ambiguity with the next method diff --git a/base/float.jl b/base/float.jl index 4190bfa18bb2b..fad7146655ade 100644 --- a/base/float.jl +++ b/base/float.jl @@ -310,6 +310,7 @@ Float64 """ float(::Type{T}) where {T<:Number} = typeof(float(zero(T))) float(::Type{T}) where {T<:AbstractFloat} = T +float(::Type{Union{}}, slurp...) = Union{}(0.0) """ unsafe_trunc(T, x) diff --git a/base/generator.jl b/base/generator.jl index d11742fe5b72f..aa4b7f67cba95 100644 --- a/base/generator.jl +++ b/base/generator.jl @@ -92,13 +92,13 @@ Base.HasLength() """ IteratorSize(x) = IteratorSize(typeof(x)) IteratorSize(::Type) = HasLength() # HasLength is the default +IteratorSize(::Type{Union{}}, slurp...) = throw(ArgumentError("Union{} does not have elements")) +IteratorSize(::Type{Any}) = SizeUnknown() IteratorSize(::Type{<:Tuple}) = HasLength() IteratorSize(::Type{<:AbstractArray{<:Any,N}}) where {N} = HasShape{N}() IteratorSize(::Type{Generator{I,F}}) where {I,F} = IteratorSize(I) -IteratorSize(::Type{Any}) = SizeUnknown() - haslength(iter) = IteratorSize(iter) isa Union{HasShape, HasLength} abstract type IteratorEltype end @@ -126,7 +126,7 @@ Base.HasEltype() """ IteratorEltype(x) = IteratorEltype(typeof(x)) IteratorEltype(::Type) = HasEltype() # HasEltype is the default +IteratorEltype(::Type{Union{}}, slurp...) = throw(ArgumentError("Union{} does not have elements")) +IteratorEltype(::Type{Any}) = EltypeUnknown() IteratorEltype(::Type{Generator{I,T}}) where {I,T} = EltypeUnknown() - -IteratorEltype(::Type{Any}) = EltypeUnknown() diff --git a/base/indices.jl b/base/indices.jl index 6a28cf63316e6..a9189865048cd 100644 --- a/base/indices.jl +++ b/base/indices.jl @@ -92,7 +92,7 @@ particular, [`eachindex`](@ref) creates an iterator whose type depends on the setting of this trait. """ IndexStyle(A::AbstractArray) = IndexStyle(typeof(A)) -IndexStyle(::Type{Union{}}) = IndexLinear() +IndexStyle(::Type{Union{}}, slurp...) = IndexLinear() IndexStyle(::Type{<:AbstractArray}) = IndexCartesian() IndexStyle(::Type{<:Array}) = IndexLinear() IndexStyle(::Type{<:AbstractRange}) = IndexLinear() diff --git a/base/io.jl b/base/io.jl index 3bcae2e8d7836..9c00c57576bac 100644 --- a/base/io.jl +++ b/base/io.jl @@ -219,6 +219,8 @@ julia> read(io, String) ``` """ read(stream, t) +read(stream, ::Type{Union{}}, slurp...; kwargs...) = error("cannot read a value of type Union{}") + """ write(io::IO, x) diff --git a/base/iterators.jl b/base/iterators.jl index a4d12517aabcc..11e94d3384de8 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -1170,6 +1170,7 @@ IteratorEltype(::Type{Flatten{Tuple{}}}) = IteratorEltype(Tuple{}) _flatteneltype(I, ::HasEltype) = IteratorEltype(eltype(I)) _flatteneltype(I, et) = EltypeUnknown() +flatten_iteratorsize(::Union{HasShape, HasLength}, ::Type{Union{}}, slurp...) = HasLength() # length==0 flatten_iteratorsize(::Union{HasShape, HasLength}, ::Type{<:NTuple{N,Any}}) where {N} = HasLength() flatten_iteratorsize(::Union{HasShape, HasLength}, ::Type{<:Tuple}) = SizeUnknown() flatten_iteratorsize(::Union{HasShape, HasLength}, ::Type{<:Number}) = HasLength() @@ -1181,6 +1182,7 @@ _flatten_iteratorsize(sz, ::HasEltype, ::Type{Tuple{}}) = HasLength() IteratorSize(::Type{Flatten{I}}) where {I} = _flatten_iteratorsize(IteratorSize(I), IteratorEltype(I), I) +flatten_length(f, T::Type{Union{}}, slurp...) = 0 function flatten_length(f, T::Type{<:NTuple{N,Any}}) where {N} return N * length(f.it) end diff --git a/base/missing.jl b/base/missing.jl index e1988064aadc1..4544c2b38c460 100644 --- a/base/missing.jl +++ b/base/missing.jl @@ -41,6 +41,7 @@ nonmissingtype(::Type{T}) where {T} = typesplit(T, Missing) function nonmissingtype_checked(T::Type) R = nonmissingtype(T) R >: T && error("could not compute non-missing type") + R <: Union{} && error("cannot convert a value to missing for assignment") return R end @@ -69,7 +70,6 @@ convert(::Type{T}, x::T) where {T>:Union{Missing, Nothing}} = x convert(::Type{T}, x) where {T>:Missing} = convert(nonmissingtype_checked(T), x) convert(::Type{T}, x) where {T>:Union{Missing, Nothing}} = convert(nonmissingtype_checked(nonnothingtype_checked(T)), x) - # Comparison operators ==(::Missing, ::Missing) = missing ==(::Missing, ::Any) = missing diff --git a/base/number.jl b/base/number.jl index 31aa616b0eb55..923fc907d4038 100644 --- a/base/number.jl +++ b/base/number.jl @@ -307,6 +307,7 @@ julia> zero(rand(2,2)) """ zero(x::Number) = oftype(x,0) zero(::Type{T}) where {T<:Number} = convert(T,0) +zero(::Type{Union{}}, slurp...) = Union{}(0) """ one(x) @@ -345,6 +346,7 @@ julia> import Dates; one(Dates.Day(1)) """ one(::Type{T}) where {T<:Number} = convert(T,1) one(x::T) where {T<:Number} = one(T) +one(::Type{Union{}}, slurp...) = Union{}(1) # note that convert(T, 1) should throw an error if T is dimensionful, # so this fallback definition should be okay. @@ -368,6 +370,7 @@ julia> import Dates; oneunit(Dates.Day) """ oneunit(x::T) where {T} = T(one(x)) oneunit(::Type{T}) where {T} = T(one(T)) +oneunit(::Type{Union{}}, slurp...) = Union{}(1) """ big(T::Type) @@ -388,3 +391,4 @@ Complex{BigInt} ``` """ big(::Type{T}) where {T<:Number} = typeof(big(zero(T))) +big(::Type{Union{}}, slurp...) = Union{}(0) diff --git a/base/operators.jl b/base/operators.jl index 3b34e549ea849..5893c5944a3a0 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -888,6 +888,7 @@ julia> widen(1.5f0) """ widen(x::T) where {T} = convert(widen(T), x) widen(x::Type{T}) where {T} = throw(MethodError(widen, (T,))) +widen(x::Type{Union{}}, slurp...) = throw(MethodError(widen, (Union{},))) # function pipelining diff --git a/base/parse.jl b/base/parse.jl index 6e616004a47af..d800e54258b0d 100644 --- a/base/parse.jl +++ b/base/parse.jl @@ -36,6 +36,7 @@ julia> parse(Complex{Float64}, "3.2e-1 + 4.5im") ``` """ parse(T::Type, str; base = Int) +parse(::Type{Union{}}, slurp...; kwargs...) = error("cannot parse a value as Union{}") function parse(::Type{T}, c::AbstractChar; base::Integer = 10) where T<:Integer a::Int = (base <= 36 ? 10 : 36) @@ -251,6 +252,7 @@ function parse(::Type{T}, s::AbstractString; base::Union{Nothing,Integer} = noth convert(T, tryparse_internal(T, s, firstindex(s), lastindex(s), base===nothing ? 0 : check_valid_base(base), true)) end +tryparse(::Type{Union{}}, slurp...; kwargs...) = error("cannot parse a value as Union{}") ## string to float functions ## diff --git a/base/promotion.jl b/base/promotion.jl index 31f507d021e78..6e32bd7a42efa 100644 --- a/base/promotion.jl +++ b/base/promotion.jl @@ -323,6 +323,12 @@ it for new types as appropriate. function promote_rule end promote_rule(::Type, ::Type) = Bottom +# Define some methods to avoid needing to enumerate unrelated possibilities when presented +# with Type{<:T}, and return a value in general accordance with the result given by promote_type +promote_rule(::Type{Bottom}, slurp...) = Bottom +promote_rule(::Type{Bottom}, ::Type{Bottom}, slurp...) = Bottom # not strictly necessary, since the next method would match unambiguously anyways +promote_rule(::Type{Bottom}, ::Type{T}, slurp...) where {T} = T +promote_rule(::Type{T}, ::Type{Bottom}, slurp...) where {T} = T promote_result(::Type,::Type,::Type{T},::Type{S}) where {T,S} = (@inline; promote_type(T,S)) # If no promote_rule is defined, both directions give Bottom. In that diff --git a/base/some.jl b/base/some.jl index 08cb3c1648ba1..0d538cbed6c23 100644 --- a/base/some.jl +++ b/base/some.jl @@ -29,6 +29,7 @@ end function nonnothingtype_checked(T::Type) R = nonnothingtype(T) R >: T && error("could not compute non-nothing type") + R <: Union{} && error("cannot convert a value to nothing for assignment") return R end diff --git a/base/traits.jl b/base/traits.jl index 53ae14b12c61e..47ab8ddc0c7ac 100644 --- a/base/traits.jl +++ b/base/traits.jl @@ -11,7 +11,7 @@ OrderStyle(::Type{<:Real}) = Ordered() OrderStyle(::Type{<:AbstractString}) = Ordered() OrderStyle(::Type{Symbol}) = Ordered() OrderStyle(::Type{<:Any}) = Unordered() -OrderStyle(::Type{Union{}}) = Ordered() +OrderStyle(::Type{Union{}}, slurp...) = Ordered() # trait for objects that support arithmetic abstract type ArithmeticStyle end @@ -23,6 +23,7 @@ ArithmeticStyle(instance) = ArithmeticStyle(typeof(instance)) ArithmeticStyle(::Type{<:AbstractFloat}) = ArithmeticRounds() ArithmeticStyle(::Type{<:Integer}) = ArithmeticWraps() ArithmeticStyle(::Type{<:Any}) = ArithmeticUnknown() +ArithmeticStyle(::Type{Union{}}, slurp...) = ArithmeticUnknown() # trait for objects that support ranges with regular step """ @@ -58,5 +59,6 @@ ranges with an element type which is a subtype of `Integer`. abstract type RangeStepStyle end struct RangeStepRegular <: RangeStepStyle end # range with regular step struct RangeStepIrregular <: RangeStepStyle end # range with rounding error +RangeStepStyle(::Type{Union{}}, slurp...) = RangeStepIrregular() RangeStepStyle(instance) = RangeStepStyle(typeof(instance)) diff --git a/src/gf.c b/src/gf.c index 2c3485823202b..23ce8d33c82d2 100644 --- a/src/gf.c +++ b/src/gf.c @@ -1487,6 +1487,8 @@ static int get_intersect_visitor(jl_typemap_entry_t *oldentry, struct typemap_in // skip if no world has both active // also be careful not to try to scan something from the current dump-reload though return 1; + // don't need to consider other similar methods if this oldentry will always fully intersect with them and dominates all of them + typemap_slurp_search(oldentry, &closure->match); jl_method_t *oldmethod = oldentry->func.method; if (closure->match.issubty // e.g. jl_subtype(closure->newentry.sig, oldentry->sig) && jl_subtype(oldmethod->sig, (jl_value_t*)closure->newentry->sig)) { // e.g. jl_type_equal(closure->newentry->sig, oldentry->sig) @@ -1511,7 +1513,7 @@ static jl_value_t *get_intersect_matches(jl_typemap_t *defs, jl_typemap_entry_t else va = NULL; } - struct matches_env env = {{get_intersect_visitor, (jl_value_t*)type, va, + struct matches_env env = {{get_intersect_visitor, (jl_value_t*)type, va, /* .search_slurp = */ 0, /* .ti = */ NULL, /* .env = */ jl_emptysvec, /* .issubty = */ 0}, /* .newentry = */ newentry, /* .shadowed */ NULL, /* .replaced */ NULL}; JL_GC_PUSH3(&env.match.env, &env.match.ti, &env.shadowed); @@ -3208,6 +3210,7 @@ struct ml_matches_env { int intersections; size_t world; int lim; + int include_ambiguous; // results: jl_value_t *t; // array of method matches size_t min_valid; @@ -3263,6 +3266,9 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio return 0; closure->lim--; } + // don't need to consider other similar methods if this ml will always fully intersect with them and dominates all of them + if (!closure->include_ambiguous || closure->lim != -1) + typemap_slurp_search(ml, &closure->match); closure->matc = make_method_match((jl_tupletype_t*)closure->match.ti, closure->match.env, meth, closure->match.issubty ? FULLY_COVERS : NOT_FULLY_COVERS); @@ -3277,9 +3283,10 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio return 1; } -static int ml_mtable_visitor(jl_methtable_t *mt, void *env) +static int ml_mtable_visitor(jl_methtable_t *mt, void *closure0) { - return jl_typemap_intersection_visitor(jl_atomic_load_relaxed(&mt->defs), jl_cachearg_offset(mt), (struct typemap_intersection_env*)env); + struct typemap_intersection_env* env = (struct typemap_intersection_env*)closure0; + return jl_typemap_intersection_visitor(jl_atomic_load_relaxed(&mt->defs), jl_cachearg_offset(mt), env); } // This is the collect form of calling jl_typemap_intersection_visitor @@ -3311,9 +3318,9 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, else va = NULL; } - struct ml_matches_env env = {{ml_matches_visitor, (jl_value_t*)type, va, + struct ml_matches_env env = {{ml_matches_visitor, (jl_value_t*)type, va, /* .search_slurp = */ 0, /* .ti = */ NULL, /* .env = */ jl_emptysvec, /* .issubty = */ 0}, - intersections, world, lim, /* .t = */ jl_an_empty_vec_any, + intersections, world, lim, include_ambiguous, /* .t = */ jl_an_empty_vec_any, /* .min_valid = */ *min_valid, /* .max_valid = */ *max_valid, /* .matc = */ NULL}; struct jl_typemap_assoc search = {(jl_value_t*)type, world, jl_emptysvec, 1, ~(size_t)0}; jl_value_t *isect2 = NULL; @@ -3377,7 +3384,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, return env.t; } } - if (!jl_typemap_intersection_visitor(jl_atomic_load_relaxed(&mt->defs), jl_cachearg_offset(mt), &env.match)) { + if (!ml_mtable_visitor(mt, &env.match)) { JL_GC_POP(); return jl_nothing; } diff --git a/src/jltypes.c b/src/jltypes.c index bf15611de4587..c4bd02f8ae37d 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -1708,7 +1708,7 @@ static void check_datatype_parameters(jl_typename_t *tn, jl_value_t **params, si JL_GC_POP(); } -static jl_value_t *extract_wrapper(jl_value_t *t JL_PROPAGATES_ROOT) JL_GLOBALLY_ROOTED +jl_value_t *extract_wrapper(jl_value_t *t JL_PROPAGATES_ROOT) JL_GLOBALLY_ROOTED { t = jl_unwrap_unionall(t); if (jl_is_datatype(t)) diff --git a/src/julia_internal.h b/src/julia_internal.h index 61c8a40f7eeb3..0674806d35a5b 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1462,12 +1462,14 @@ struct typemap_intersection_env { jl_typemap_intersection_visitor_fptr const fptr; // fptr to call on a match jl_value_t *const type; // type to match jl_value_t *const va; // the tparam0 for the vararg in type, if applicable (or NULL) + size_t search_slurp; // output values jl_value_t *ti; // intersection type jl_svec_t *env; // intersection env (initialize to null to perform intersection without an environment) int issubty; // if `a <: b` is true in `intersect(a,b)` }; int jl_typemap_intersection_visitor(jl_typemap_t *a, int offs, struct typemap_intersection_env *closure); +void typemap_slurp_search(jl_typemap_entry_t *ml, struct typemap_intersection_env *closure); // -- simplevector.c -- // diff --git a/src/subtype.c b/src/subtype.c index c1c03763ecf10..a12faf1400b58 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -2256,20 +2256,34 @@ int jl_has_intersect_type_not_kind(jl_value_t *t) t = jl_unwrap_unionall(t); if (t == (jl_value_t*)jl_any_type) return 1; - if (jl_is_uniontype(t)) { + assert(!jl_is_vararg(t)); + if (jl_is_uniontype(t)) return jl_has_intersect_type_not_kind(((jl_uniontype_t*)t)->a) || jl_has_intersect_type_not_kind(((jl_uniontype_t*)t)->b); - } - if (jl_is_typevar(t)) { + if (jl_is_typevar(t)) return jl_has_intersect_type_not_kind(((jl_tvar_t*)t)->ub); - } - if (jl_is_datatype(t)) { + if (jl_is_datatype(t)) if (((jl_datatype_t*)t)->name == jl_type_typename) return 1; - } return 0; } +// compute if DataType<:t || Union<:t || UnionAll<:t etc. +int jl_has_intersect_kind_not_type(jl_value_t *t) +{ + t = jl_unwrap_unionall(t); + if (t == (jl_value_t*)jl_any_type || jl_is_kind(t)) + return 1; + assert(!jl_is_vararg(t)); + if (jl_is_uniontype(t)) + return jl_has_intersect_kind_not_type(((jl_uniontype_t*)t)->a) || + jl_has_intersect_kind_not_type(((jl_uniontype_t*)t)->b); + if (jl_is_typevar(t)) + return jl_has_intersect_kind_not_type(((jl_tvar_t*)t)->ub); + return 0; +} + + JL_DLLEXPORT int jl_isa(jl_value_t *x, jl_value_t *t) { if (jl_typeis(x,t) || t == (jl_value_t*)jl_any_type) @@ -4466,6 +4480,7 @@ int tuple_cmp_typeofbottom(jl_datatype_t *a, jl_datatype_t *b) for (i = 0; i < la || i < lb; i++) { jl_value_t *pa = i < la ? jl_tparam(a, i) : NULL; jl_value_t *pb = i < lb ? jl_tparam(b, i) : NULL; + assert(jl_typeofbottom_type); // for clang-sa int xa = pa == (jl_value_t*)jl_typeofbottom_type || pa == (jl_value_t*)jl_typeofbottom_type->super; int xb = pb == (jl_value_t*)jl_typeofbottom_type || pb == (jl_value_t*)jl_typeofbottom_type->super; if (xa != xb) diff --git a/src/typemap.c b/src/typemap.c index e60f3d566284e..4b2049552067c 100644 --- a/src/typemap.c +++ b/src/typemap.c @@ -23,7 +23,7 @@ static int jl_is_any(jl_value_t *t1) return t1 == (jl_value_t*)jl_any_type; } -static jl_value_t *jl_type_extract_name(jl_value_t *t1 JL_PROPAGATES_ROOT) +static jl_value_t *jl_type_extract_name(jl_value_t *t1 JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT { if (jl_is_unionall(t1)) t1 = jl_unwrap_unionall(t1); @@ -33,6 +33,9 @@ static jl_value_t *jl_type_extract_name(jl_value_t *t1 JL_PROPAGATES_ROOT) else if (jl_is_typevar(t1)) { return jl_type_extract_name(((jl_tvar_t*)t1)->ub); } + else if (t1 == jl_bottom_type || t1 == (jl_value_t*)jl_typeofbottom_type || t1 == (jl_value_t*)jl_typeofbottom_type->super) { + return (jl_value_t*)jl_typeofbottom_type->name; // put Union{} and typeof(Union{}) and Type{Union{}} together for convenience + } else if (jl_is_datatype(t1)) { jl_datatype_t *dt = (jl_datatype_t*)t1; if (!jl_is_kind(t1)) @@ -63,6 +66,9 @@ static int jl_type_extract_name_precise(jl_value_t *t1, int invariant) else if (jl_is_typevar(t1)) { return jl_type_extract_name_precise(((jl_tvar_t*)t1)->ub, 0); } + else if (t1 == jl_bottom_type || t1 == (jl_value_t*)jl_typeofbottom_type || t1 == (jl_value_t*)jl_typeofbottom_type->super) { + return 1; + } else if (jl_is_datatype(t1)) { jl_datatype_t *dt = (jl_datatype_t*)t1; if ((invariant || !dt->name->abstract) && !jl_is_kind(t1)) @@ -84,6 +90,18 @@ static int jl_type_extract_name_precise(jl_value_t *t1, int invariant) return 1; } +// return whether Type{Union{}} is a subtype of Type{t1} (which may have free typevars) +static int jl_parameter_includes_bottom(jl_value_t *t1) +{ + if (jl_is_typevar(t1) || t1 == jl_bottom_type) + return 1; + else if (jl_is_uniontype(t1)) { + jl_uniontype_t *u1 = (jl_uniontype_t*)t1; + return jl_parameter_includes_bottom(u1->a) && jl_parameter_includes_bottom(u1->b); + } + return 0; +} + // ----- Type Signature Subtype Testing ----- // @@ -367,7 +385,7 @@ int jl_typemap_visitor(jl_typemap_t *cache, jl_typemap_visitor_fptr fptr, void * } } -static unsigned jl_supertype_height(jl_datatype_t *dt) +static unsigned jl_supertype_height(jl_datatype_t *dt) JL_NOTSAFEPOINT { unsigned height = 1; while (dt != jl_any_type) { @@ -378,8 +396,10 @@ static unsigned jl_supertype_height(jl_datatype_t *dt) } // return true if a and b might intersect in the type domain (over just their type-names) -static int tname_intersection(jl_datatype_t *a, jl_typename_t *bname, unsigned ha) +static int tname_intersection_dt(jl_datatype_t *a, jl_typename_t *bname, unsigned ha) JL_NOTSAFEPOINT { + if (a == jl_any_type) + return 1; jl_datatype_t *b = (jl_datatype_t*)jl_unwrap_unionall(bname->wrapper); unsigned hb = 1; while (b != jl_any_type) { @@ -395,8 +415,42 @@ static int tname_intersection(jl_datatype_t *a, jl_typename_t *bname, unsigned h return a->name == bname; } -// tparam bit 1 is ::Type{T} (vs. T) -// tparam bit 2 is typename(T) (vs. T) +static int tname_intersection(jl_value_t *a, jl_typename_t *bname, int8_t tparam) JL_NOTSAFEPOINT +{ + if (a == (jl_value_t*)jl_any_type) + return 1; + a = jl_unwrap_unionall(a); + assert(!jl_is_vararg(a)); + if (jl_is_uniontype(a)) + return tname_intersection(((jl_uniontype_t*)a)->a, bname, tparam) || + tname_intersection(((jl_uniontype_t*)a)->b, bname, tparam); + if (jl_is_typevar(a)) + return tname_intersection(((jl_tvar_t*)a)->ub, bname, tparam); + if (jl_is_datatype(a)) { + if (tparam) { + if (!jl_is_type_type(a)) + return 0; + a = jl_unwrap_unionall(jl_tparam0(a)); + if (!jl_is_datatype(a)) + return tname_intersection(a, bname, 0); + } + return tname_intersection_dt((jl_datatype_t*)a, bname, jl_supertype_height((jl_datatype_t*)a)); + } + return 0; +} + +static int concrete_intersects(jl_value_t *t, jl_value_t *ty, int8_t tparam) +{ + if (ty == (jl_value_t*)jl_any_type) // easy case: Any always matches + return 1; + if (tparam & 1) + return jl_isa(t, ty); // (Type{t} <: ty), where is_leaf_type(t) => isa(t, ty) + else + return t == ty || jl_subtype(t, ty); +} + +// tparam bit 0 is ::Type{T} (vs. T) +// tparam bit 1 is typename(T) (vs. T) static int jl_typemap_intersection_array_visitor(jl_array_t *a, jl_value_t *ty, int8_t tparam, int8_t offs, struct typemap_intersection_env *closure) { @@ -404,15 +458,26 @@ static int jl_typemap_intersection_array_visitor(jl_array_t *a, jl_value_t *ty, size_t i, l = jl_array_len(a); _Atomic(jl_typemap_t*) *data = (_Atomic(jl_typemap_t*)*)jl_array_data(a); unsigned height = 0; - jl_datatype_t *tydt = NULL; - if (jl_is_kind(ty)) - ty = (jl_value_t*)jl_any_type; + jl_datatype_t *tydt = jl_any_type; if (tparam & 2) { - tydt = (jl_datatype_t*)jl_unwrap_unionall(ty); - if (jl_is_datatype(ty)) - height = jl_supertype_height(tydt); - else + // try to extract a description of ty for intersections, but since we + jl_value_t *ttype = jl_unwrap_unionall(ty); + if (tparam & 1) + // extract T from Type{T} (if possible) + ttype = jl_is_type_type(ttype) ? jl_tparam0(ttype) : NULL; + if (ttype && jl_is_datatype(ttype)) { + tydt = (jl_datatype_t*)ttype; + } + else if (ttype) { + ttype = jl_type_extract_name(ttype); + tydt = ttype ? (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)ttype)->wrapper) : NULL; + } + if (tydt == jl_any_type) + ty = (jl_value_t*)jl_any_type; + else if (tydt == NULL) tydt = jl_any_type; + else + height = jl_supertype_height(tydt); } for (i = 0; i < l; i += 2) { jl_value_t *t = jl_atomic_load_relaxed(&data[i]); @@ -422,8 +487,11 @@ static int jl_typemap_intersection_array_visitor(jl_array_t *a, jl_value_t *ty, if (tparam & 2) { jl_typemap_t *ml = jl_atomic_load_relaxed(&data[i + 1]); JL_GC_PROMISE_ROOTED(ml); - if (tydt == jl_any_type || // easy case: Any always matches - tname_intersection(tydt, (jl_typename_t*)t, height)) { + if (tydt == jl_any_type ? + tname_intersection(ty, (jl_typename_t*)t, tparam & 1) : + tname_intersection_dt(tydt, (jl_typename_t*)t, height)) { + if ((tparam & 1) && t == (jl_value_t*)jl_typeofbottom_type->name) // skip Type{Union{}} and Type{typeof(Union{})}, since the caller should have already handled those + continue; if (jl_is_array(ml)) { if (!jl_typemap_intersection_array_visitor((jl_array_t*)ml, ty, tparam & ~2, offs, closure)) goto exit; @@ -436,10 +504,7 @@ static int jl_typemap_intersection_array_visitor(jl_array_t *a, jl_value_t *ty, } else { // `t` is a leaftype, so intersection test becomes subtype (after excluding kinds) - if (ty == (jl_value_t*)jl_any_type || // easy case: Any always matches - (tparam & 1 - ? jl_isa(t, ty) // (Type{t} <: ty), where is_leaf_type(t) => isa(t, ty) - : (t == ty || jl_subtype(t, ty)))) { + if (concrete_intersects(t, ty, tparam)) { jl_typemap_t *ml = jl_atomic_load_relaxed(&data[i + 1]); JL_GC_PROMISE_ROOTED(ml); // NOTE: ml might be NULL if we're racing with the thread that's inserting the item @@ -456,6 +521,7 @@ static int jl_typemap_intersection_array_visitor(jl_array_t *a, jl_value_t *ty, return 0; } + // calls fptr on each jl_typemap_entry_t in cache in sort order // for which type ∩ ml->type != Union{}, until fptr return false static int jl_typemap_intersection_node_visitor(jl_typemap_entry_t *ml, struct typemap_intersection_env *closure) @@ -496,6 +562,30 @@ static int jl_typemap_intersection_node_visitor(jl_typemap_entry_t *ml, struct t return 1; } +int jl_has_intersect_type_not_kind(jl_value_t *t); +int jl_has_intersect_kind_not_type(jl_value_t *t); + +void typemap_slurp_search(jl_typemap_entry_t *ml, struct typemap_intersection_env *closure) +{ + // n.b. we could consider mt->max_args here too, so this optimization + // usually works even if the user forgets the `slurp...` argument, but + // there is discussion that parameter may be going away? (and it is + // already not accurately up-to-date for all tables currently anyways) + if (closure->search_slurp && ml->va) { + jl_value_t *sig = jl_unwrap_unionall((jl_value_t*)ml->sig); + size_t nargs = jl_nparams(sig); + if (nargs > 1 && nargs - 1 == closure->search_slurp) { + jl_vararg_t *va = (jl_vararg_t*)jl_tparam(sig, nargs - 1); + assert(jl_is_vararg((jl_value_t*)va)); + if (va->T == (jl_value_t*)jl_any_type && va->N == NULL) { + // instruct typemap it can set exclude_typeofbottom on parameter nargs + // since we found the necessary slurp argument + closure->search_slurp = 0; + } + } + } +} + int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, struct typemap_intersection_env *closure) { @@ -504,13 +594,12 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, //TODO: fast-path for leaf-type tuples? //if (ttypes->isdispatchtuple) { // register jl_typemap_intersection_visitor_fptr fptr = closure->fptr; - // struct jl_typemap_assoc search = {(jl_value_t*)closure->type, world, closure->env, 0, ~(size_t)0}; - // jl_typemap_entry_t *ml = jl_typemap_assoc_by_type(map, search, offs, /*subtype*/1); - // if (ml) { - // closure->env = search->env; - // if (!fptr(ml, closure)) - // return 0; - // } + // struct jl_typemap_assoc search = {(jl_value_t*)closure->type, world, closure->env, 0, ~(size_t)0}; + // jl_typemap_entry_t *ml = jl_typemap_assoc_by_type(map, search, offs, /*subtype*/1); + // if (ml) { + // closure->env = search->env; + // if (!fptr(ml, closure)) + // return 0; // } // return 1; //} @@ -532,23 +621,56 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, if (ty) { while (jl_is_typevar(ty)) ty = ((jl_tvar_t*)ty)->ub; - jl_value_t *typetype = jl_unwrap_unionall(ty); - typetype = jl_is_type_type(typetype) ? jl_tparam0(typetype) : NULL; - jl_value_t *name = typetype ? jl_type_extract_name(typetype) : NULL; // approxify the tparam until we have a valid type - if (jl_has_free_typevars(ty)) { - ty = jl_unwrap_unionall(ty); - if (jl_is_datatype(ty)) - ty = ((jl_datatype_t*)ty)->name->wrapper; - else - ty = (jl_value_t*)jl_any_type; - } + if (jl_has_free_typevars(ty)) + ty = jl_rewrap_unionall(ty, closure->type); + JL_GC_PUSH1(&ty); jl_array_t *targ = jl_atomic_load_relaxed(&cache->targ); - if (targ != (jl_array_t*)jl_an_empty_vec_any - && !(typetype && !jl_has_free_typevars(typetype) && !is_cache_leaf(typetype, 1))) { // cannot contain this, so don't bother with checking - if (name && !jl_is_typevar(typetype)) { - // semi-direct lookup of types via their names - if (jl_type_extract_name_precise(typetype, 1)) { + jl_array_t *tname = jl_atomic_load_relaxed(&cache->tname); + int maybe_type = 0; + int maybe_kind = 0; + int exclude_typeofbottom = 0; + jl_value_t *typetype = NULL; + jl_value_t *name = NULL; + // pre-check: optimized pre-intersection test to see if `ty` could intersect with any Type or Kind + if (targ != (jl_array_t*)jl_an_empty_vec_any || tname != (jl_array_t*)jl_an_empty_vec_any) { + maybe_kind = jl_has_intersect_kind_not_type(ty); + maybe_type = maybe_kind || jl_has_intersect_type_not_kind(ty); + if (maybe_type && !maybe_kind) { + typetype = jl_unwrap_unionall(ty); + typetype = jl_is_type_type(typetype) ? jl_tparam0(typetype) : NULL; + name = typetype ? jl_type_extract_name(typetype) : NULL; + exclude_typeofbottom = !(typetype ? jl_parameter_includes_bottom(typetype) : jl_subtype((jl_value_t*)jl_typeofbottom_type, ty)); + } + } + // First check for intersections with methods defined on Type{T}, where T was a concrete type + if (targ != (jl_array_t*)jl_an_empty_vec_any && maybe_type && + (!typetype || jl_has_free_typevars(typetype) || is_cache_leaf(typetype, 1))) { // otherwise cannot contain this particular kind, so don't bother with checking + if (!exclude_typeofbottom) { + // detect Type{Union{}}, Type{Type{Union{}}}, and Type{typeof(Union{}} and do those early here + // otherwise the possibility of encountering `Type{Union{}}` in this intersection may + // be forcing us to do some extra work here whenever we see a typevar, even though + // the likelihood of that value actually occurring is frequently likely to be + // zero (or result in an ambiguous match) + targ = jl_atomic_load_relaxed(&cache->targ); // may be GC'd during type-intersection + jl_value_t *ml = mtcache_hash_lookup(targ, (jl_value_t*)jl_typeofbottom_type->name); + if (ml != jl_nothing) { + size_t search_slurp = closure->search_slurp; + closure->search_slurp = offs + 1; + if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) { + closure->search_slurp = search_slurp; + JL_GC_POP(); + return 0; + } + if (closure->search_slurp == 0) + exclude_typeofbottom = 1; + closure->search_slurp = search_slurp; + } + } + if (name != (jl_value_t*)jl_typeofbottom_type->name) { + targ = jl_atomic_load_relaxed(&cache->targ); // may be GC'd earlier + if (exclude_typeofbottom && name && jl_type_extract_name_precise(typetype, 1)) { + // attempt semi-direct lookup of types via their names // consider the type name first jl_value_t *ml = mtcache_hash_lookup(targ, (jl_value_t*)name); if (jl_is_array(ml)) { @@ -557,33 +679,21 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, if (is_cache_leaf(typetype, 1)) { ml = mtcache_hash_lookup((jl_array_t*)ml, typetype); if (ml != jl_nothing) { - if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) return 0; + if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) { JL_GC_POP(); return 0; } } } } else { - if (!jl_typemap_intersection_array_visitor((jl_array_t*)ml, (jl_value_t*)ty, 1, offs, closure)) return 0; + if (!jl_typemap_intersection_array_visitor((jl_array_t*)ml, ty, 1, offs, closure)) { JL_GC_POP(); return 0; } } } else if (ml != jl_nothing) { - if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) return 0; + if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) { JL_GC_POP(); return 0; } } } else { - // consider all of the possible subtypes - // TODO: the possibility of encountering `Type{Union{}}` in this intersection may - // be forcing us to do some extra work here whenever we see a typevar, even though - // the likelihood of that value actually occurring is frequently likely to be - // zero (or result in an ambiguous match) - if (!jl_typemap_intersection_array_visitor((jl_array_t*)targ, (jl_value_t*)ty, 3, offs, closure)) return 0; - } - } - else { - // else an array scan is required to check subtypes - // first, fast-path: optimized pre-intersection test to see if `ty` could intersect with any Type - if (typetype || !jl_has_empty_intersection((jl_value_t*)jl_type_type, ty)) { - targ = jl_atomic_load_relaxed(&cache->targ); // may be GC'd during type-intersection - if (!jl_typemap_intersection_array_visitor(targ, ty, 3, offs, closure)) return 0; + // else an array scan is required to consider all the possible subtypes + if (!jl_typemap_intersection_array_visitor(targ, exclude_typeofbottom && !maybe_kind ? ty : (jl_value_t*)jl_any_type, 3, offs, closure)) { JL_GC_POP(); return 0; } } } } @@ -596,7 +706,7 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, if (jl_is_array(ml)) ml = mtcache_hash_lookup((jl_array_t*)ml, ty); if (ml != jl_nothing) { - if (!jl_typemap_intersection_visitor(ml, offs+1, closure)) return 0; + if (!jl_typemap_intersection_visitor(ml, offs+1, closure)) { JL_GC_POP(); return 0; } } } else { @@ -605,82 +715,87 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, // direct lookup of leaf types jl_value_t *ml = mtcache_hash_lookup(cachearg1, name); if (jl_is_array(ml)) { - if (!jl_typemap_intersection_array_visitor((jl_array_t*)ml, (jl_value_t*)ty, 0, offs, closure)) return 0; + if (!jl_typemap_intersection_array_visitor((jl_array_t*)ml, ty, 0, offs, closure)) { JL_GC_POP(); return 0; } } else { - if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) return 0; + if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) { JL_GC_POP(); return 0; } } } else { // else an array scan is required to check subtypes - if (!jl_typemap_intersection_array_visitor(cachearg1, ty, 2, offs, closure)) return 0; + if (!jl_typemap_intersection_array_visitor(cachearg1, ty, 2, offs, closure)) { JL_GC_POP(); return 0; } } } } - jl_array_t *tname = jl_atomic_load_relaxed(&cache->tname); - if (tname != (jl_array_t*)jl_an_empty_vec_any) { - if (name && !jl_is_typevar(typetype)) { - // semi-direct lookup of types - // TODO: the possibility of encountering `Type{Union{}}` in this intersection may + // Next check for intersections with methods defined on Type{T}, where T was not concrete (it might even have been a TypeVar), but had an extractable TypeName + if (tname != (jl_array_t*)jl_an_empty_vec_any && maybe_type) { + if (!exclude_typeofbottom || (!typetype && jl_isa((jl_value_t*)jl_typeofbottom_type, ty))) { + // detect Type{Union{}}, Type{Type{Union{}}}, and Type{typeof(Union{}} and do those early here + // otherwise the possibility of encountering `Type{Union{}}` in this intersection may // be forcing us to do some extra work here whenever we see a typevar, even though // the likelihood of that value actually occurring is frequently likely to be // zero (or result in an ambiguous match) - jl_datatype_t *super = (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)name)->wrapper); - if (jl_type_extract_name_precise(typetype, 1)) { - // just consider the type and its direct super types - while (1) { - tname = jl_atomic_load_relaxed(&cache->tname); // reload after callback - jl_typemap_t *ml = mtcache_hash_lookup(tname, (jl_value_t*)super->name); - if (ml != jl_nothing) { - if (!jl_typemap_intersection_visitor(ml, offs+1, closure)) return 0; - } - if (super == jl_any_type) - break; - super = super->super; + tname = jl_atomic_load_relaxed(&cache->tname); // may be GC'd earlier + jl_value_t *ml = mtcache_hash_lookup(tname, (jl_value_t*)jl_typeofbottom_type->name); + if (ml != jl_nothing) { + size_t search_slurp = closure->search_slurp; + closure->search_slurp = offs + 1; + if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) { + closure->search_slurp = search_slurp; + JL_GC_POP(); + return 0; } + if (closure->search_slurp == 0) + exclude_typeofbottom = 1; + closure->search_slurp = search_slurp; } - else { - // consider all of the possible subtypes - if (!jl_typemap_intersection_array_visitor(tname, (jl_value_t*)super, 3, offs, closure)) return 0; + } + if (exclude_typeofbottom && name && jl_type_extract_name_precise(typetype, 1)) { + // semi-direct lookup of types + // just consider the type and its direct super types + jl_datatype_t *super = (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)name)->wrapper); + if (super->name == jl_typeofbottom_type->name) + super = super->super; // this was handled above + while (1) { + tname = jl_atomic_load_relaxed(&cache->tname); // reload after callback + jl_typemap_t *ml = mtcache_hash_lookup(tname, (jl_value_t*)super->name); + if (ml != jl_nothing) { + if (!jl_typemap_intersection_visitor(ml, offs+1, closure)) { JL_GC_POP(); return 0; } + } + if (super == jl_any_type) + break; + super = super->super; } } else { - // else an array scan is required to check subtypes - // first, fast-path: optimized pre-intersection test to see if `ty` could intersect with any Type - if (name || !jl_has_empty_intersection((jl_value_t*)jl_type_type, ty)) { - tname = jl_atomic_load_relaxed(&cache->tname); // may be GC'd during type-intersection - if (!jl_typemap_intersection_array_visitor(tname, (jl_value_t*)jl_any_type, 3, offs, closure)) return 0; - } + // else an array scan is required to check subtypes of typetype too + tname = jl_atomic_load_relaxed(&cache->tname); // may be GC'd earlier + if (!jl_typemap_intersection_array_visitor(tname, exclude_typeofbottom && !maybe_kind ? ty : (jl_value_t*)jl_any_type, 3, offs, closure)) { JL_GC_POP(); return 0; } } } jl_array_t *name1 = jl_atomic_load_relaxed(&cache->name1); if (name1 != (jl_array_t*)jl_an_empty_vec_any) { jl_value_t *name = jl_type_extract_name(ty); - if (name) { + if (name && jl_type_extract_name_precise(ty, 0)) { jl_datatype_t *super = (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)name)->wrapper); - if (jl_type_extract_name_precise(ty, 0)) { - // direct lookup of concrete types - while (1) { - name1 = jl_atomic_load_relaxed(&cache->name1); // reload after callback - jl_typemap_t *ml = mtcache_hash_lookup(name1, (jl_value_t*)super->name); - if (ml != jl_nothing) { - if (!jl_typemap_intersection_visitor(ml, offs+1, closure)) return 0; - } - if (super == jl_any_type) - break; - super = super->super; + // direct lookup of concrete types + while (1) { + name1 = jl_atomic_load_relaxed(&cache->name1); // reload after callback + jl_typemap_t *ml = mtcache_hash_lookup(name1, (jl_value_t*)super->name); + if (ml != jl_nothing) { + if (!jl_typemap_intersection_visitor(ml, offs+1, closure)) { JL_GC_POP(); return 0; } } - } - else { - // consider all of the possible subtypes too - if (!jl_typemap_intersection_array_visitor(name1, (jl_value_t*)super, 2, offs, closure)) return 0; + if (super == jl_any_type) + break; + super = super->super; } } else { // else an array scan is required to check subtypes - if (!jl_typemap_intersection_array_visitor(name1, (jl_value_t*)jl_any_type, 2, offs, closure)) return 0; + if (!jl_typemap_intersection_array_visitor(name1, ty, 2, offs, closure)) { JL_GC_POP(); return 0; } } } + JL_GC_POP(); } if (!jl_typemap_intersection_node_visitor(jl_atomic_load_relaxed(&cache->linear), closure)) return 0; diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 070e5d7a7b289..c5ff97deb6777 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -520,9 +520,6 @@ function test_primitives(::Type{T}, shape, ::Type{TestAbstractArray}) where T @test convert(Matrix, Y) == Y @test convert(Matrix, view(Y, 1:2, 1:2)) == Y @test_throws MethodError convert(Matrix, X) - - # convert(::Type{Union{}}, A::AbstractMatrix) - @test_throws MethodError convert(Union{}, X) end mutable struct TestThrowNoGetindex{T} <: AbstractVector{T} end diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 67fb16d3b7458..a1b973f30a70c 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -177,12 +177,10 @@ ambs = detect_ambiguities(Ambig48312) @test good end - # some ambiguities involving Union{} type parameters are expected, but not required + # some ambiguities involving Union{} type parameters may be expected, but not required let ambig = Set(detect_ambiguities(Core; recursive=true, ambiguous_bottom=true)) - m1 = which(Core.Compiler.convert, Tuple{Type{<:Core.IntrinsicFunction}, Any}) - m2 = which(Core.Compiler.convert, Tuple{Type{<:Nothing}, Any}) - pop!(ambig, (m1, m2)) @test !isempty(ambig) + @test length(ambig) < 30 end STDLIB_DIR = Sys.STDLIB diff --git a/test/core.jl b/test/core.jl index a89d206182dbf..daec51ab5b566 100644 --- a/test/core.jl +++ b/test/core.jl @@ -1694,7 +1694,9 @@ end # issue #3221 let x = fill(nothing, 1) - @test_throws MethodError x[1] = 1 + @test_throws ErrorException("cannot convert a value to nothing for assignment") x[1] = 1 + x = Vector{Union{}}(undef, 1) + @test_throws ArgumentError("cannot convert a value to Union{} for assignment") x[1] = 1 end # issue #3220 @@ -4916,7 +4918,7 @@ struct f47209 x::Int f47209()::Nothing = new(1) end -@test_throws MethodError f47209() +@test_throws ErrorException("cannot convert a value to nothing for assignment") f47209() # issue #12096 let a = Val{Val{TypeVar(:_, Int)}}, diff --git a/test/missing.jl b/test/missing.jl index 450b816ea3e57..f06d1aad7a6b1 100644 --- a/test/missing.jl +++ b/test/missing.jl @@ -21,8 +21,8 @@ end @test convert(Union{Nothing, Missing}, nothing) === nothing @test convert(Union{Missing, Nothing, Float64}, 1) === 1.0 - @test_throws MethodError convert(Missing, 1) - @test_throws MethodError convert(Union{Nothing, Missing}, 1) + @test_throws ErrorException("cannot convert a value to missing for assignment") convert(Missing, 1) + @test_throws ErrorException("cannot convert a value to missing for assignment") convert(Union{Nothing, Missing}, 1) @test_throws MethodError convert(Union{Int, Missing}, "a") end diff --git a/test/some.jl b/test/some.jl index 27d50ca354a49..e49fc586a3a6e 100644 --- a/test/some.jl +++ b/test/some.jl @@ -33,7 +33,7 @@ @test convert(Union{Int, Nothing}, 1) === 1 @test convert(Union{Int, Nothing}, 1.0) === 1 @test convert(Nothing, nothing) === nothing -@test_throws MethodError convert(Nothing, 1) +@test_throws ErrorException("cannot convert a value to nothing for assignment") convert(Nothing, 1) ## show() From 7f4e129cf5a0fa853a8347edb2b52b40622182be Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 14 Apr 2023 09:41:45 -0400 Subject: [PATCH 4/4] also optimize {Type{T},T} lookup Since T cannot be Union{} here, the prior optimization would not get detected post facto, but a priori this cannot be inhabited for T=Union{}, so we can exclude it immediately. This does not happen during inference, but shows up during edge validation somewhat often. --- src/typemap.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/typemap.c b/src/typemap.c index 4b2049552067c..97ea31928251d 100644 --- a/src/typemap.c +++ b/src/typemap.c @@ -565,6 +565,16 @@ static int jl_typemap_intersection_node_visitor(jl_typemap_entry_t *ml, struct t int jl_has_intersect_type_not_kind(jl_value_t *t); int jl_has_intersect_kind_not_type(jl_value_t *t); +// if TypeVar tv is used covariantly, it cannot be Union{} +int has_covariant_var(jl_datatype_t *ttypes, jl_tvar_t *tv) +{ + size_t i, l = jl_nparams(ttypes); + for (i = 0; i < l; i++) + if (jl_tparam(ttypes, i) == (jl_value_t*)tv) + return 1; + return 0; +} + void typemap_slurp_search(jl_typemap_entry_t *ml, struct typemap_intersection_env *closure) { // n.b. we could consider mt->max_args here too, so this optimization @@ -640,7 +650,12 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, typetype = jl_unwrap_unionall(ty); typetype = jl_is_type_type(typetype) ? jl_tparam0(typetype) : NULL; name = typetype ? jl_type_extract_name(typetype) : NULL; - exclude_typeofbottom = !(typetype ? jl_parameter_includes_bottom(typetype) : jl_subtype((jl_value_t*)jl_typeofbottom_type, ty)); + if (!typetype) + exclude_typeofbottom = !jl_subtype((jl_value_t*)jl_typeofbottom_type, ty); + else if (jl_is_typevar(typetype)) + exclude_typeofbottom = has_covariant_var((jl_datatype_t*)ttypes, (jl_tvar_t*)typetype); + else + exclude_typeofbottom = !jl_parameter_includes_bottom(typetype); } } // First check for intersections with methods defined on Type{T}, where T was a concrete type