From d1a2c9e96691db0460ffb8bf2a68ef6178f3dfc2 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 13 Apr 2023 11:49:51 -0400 Subject: [PATCH] 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 | 7 +- 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 | 12 ++- src/julia_internal.h | 2 + src/subtype.c | 26 ++++-- src/typemap.c | 181 +++++++++++++++++++++++++++--------------- test/abstractarray.jl | 3 - test/ambiguous.jl | 6 +- test/core.jl | 6 +- test/missing.jl | 4 +- test/some.jl | 2 +- 28 files changed, 215 insertions(+), 107 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..1cbb6b235bc2c 100644 --- a/base/arrayshow.jl +++ b/base/arrayshow.jl @@ -540,9 +540,10 @@ 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{<: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) +typeinfo_eltype(typeinfo::Type{Union{}}, slurp...) = nothing +typeinfo_eltype(typeinfo::Type{<:AbstractArray}) = eltype(typeinfo) +typeinfo_eltype(typeinfo::Type{<:AbstractDict}) = eltype(typeinfo) +typeinfo_eltype(typeinfo::Type{<:AbstractSet}) = eltype(typeinfo) # types that can be parsed back accurately from their un-decorated representations function typeinfo_implicit(@nospecialize(T)) 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 d86b5cd92e02f..94413ae05c87a 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 82f8516e7a36d..97f0a9f43fe27 100644 --- a/src/gf.c +++ b/src/gf.c @@ -1448,6 +1448,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) @@ -1472,7 +1474,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); @@ -3149,6 +3151,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; @@ -3204,6 +3207,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); @@ -3253,9 +3259,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; diff --git a/src/julia_internal.h b/src/julia_internal.h index 4f1a0b4513d8d..cb7cd42376932 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1459,12 +1459,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 4209e98c2055d..e75a9004d445e 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -2299,20 +2299,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) diff --git a/src/typemap.c b/src/typemap.c index dc2035cbcc5d9..1546e964ffd1e 100644 --- a/src/typemap.c +++ b/src/typemap.c @@ -396,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) { + 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) { @@ -413,23 +415,42 @@ static int tname_intersection(jl_datatype_t *a, jl_typename_t *bname, unsigned h return a->name == bname; } +static int tname_intersection(jl_value_t *a, jl_typename_t *bname, int8_t tparam) +{ + 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) { - if (tparam & 4) - return jl_isa(t, ty); // (Type{t} <: ty), where is_leaf_type(t) => isa(t, ty) - return 1; - } - else { + 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) -// tparam bit 2 is exclude_typeofbottom (only if tparam bit 1 is set) 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) { @@ -438,28 +459,24 @@ static int jl_typemap_intersection_array_visitor(jl_array_t *a, jl_value_t *ty, _Atomic(jl_typemap_t*) *data = (_Atomic(jl_typemap_t*)*)jl_array_data(a); unsigned height = 0; jl_datatype_t *tydt = jl_any_type; - if (jl_is_kind(ty)) - ty = (jl_value_t*)jl_any_type; if (tparam & 2) { // try to extract a description of ty for intersections, but since we - // know we got here because we already failed to find an exact match, - // we don't try too hard - if (tparam & 1) { - if (tparam & 4) { - // extract T from Type{T} (if possible) - jl_value_t *ttype = jl_unwrap_unionall(ty); - ttype = jl_is_type_type(ttype) ? jl_tparam0(ttype) : NULL; - ttype = ttype ? jl_type_extract_name(ttype) : NULL; - if (ttype) - tydt = (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)ttype)->wrapper); - } + 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 { - tydt = (jl_datatype_t*)jl_unwrap_unionall(ty); - if (!jl_is_datatype(tydt)) - tydt = jl_any_type; + 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) + 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) { @@ -470,8 +487,9 @@ 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)) { @@ -544,8 +562,29 @@ 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); -jl_value_t *extract_wrapper(jl_value_t *t JL_PROPAGATES_ROOT) JL_GLOBALLY_ROOTED; +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(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) @@ -583,28 +622,29 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, while (jl_is_typevar(ty)) ty = ((jl_tvar_t*)ty)->ub; // approxify the tparam until we have a valid type - if (jl_has_free_typevars(ty)) { - ty = extract_wrapper(ty); - if (ty == NULL) - ty = (jl_value_t*)jl_any_type; - } + if (jl_has_free_typevars(ty)) + ty = jl_rewrap_unionall(ty, ttypes); + JL_GC_PUSH1(&ty); jl_array_t *targ = jl_atomic_load_relaxed(&cache->targ); jl_array_t *tname = jl_atomic_load_relaxed(&cache->tname); - int maybe_Type = 0; + 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 + // 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) { - typetype = jl_unwrap_unionall(ty); - typetype = jl_is_type_type(typetype) ? jl_tparam0(typetype) : NULL; - name = typetype ? jl_type_extract_name(typetype) : NULL; - maybe_Type = typetype || !jl_has_empty_intersection((jl_value_t*)jl_type_type, ty); - if (maybe_Type) + 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 && + 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 @@ -615,13 +655,21 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, 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) { - if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) return 0; - //exclude_typeofbottom = 1; + 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 (name && jl_type_extract_name_precise(typetype, 1)) { + 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); @@ -631,21 +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, ty, 1 | (exclude_typeofbottom << 2), 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 { // else an array scan is required to consider all the possible subtypes - if (!jl_typemap_intersection_array_visitor(targ, ty, 3 | (exclude_typeofbottom << 2), offs, closure)) return 0; + 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; } } } } @@ -658,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 { @@ -667,20 +715,20 @@ 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, 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; } } } } // 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 (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 @@ -690,11 +738,19 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, 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) { - if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) return 0; - //exclude_typeofbottom = 1; + 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_type_extract_name_precise(typetype, 1)) { + 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); @@ -704,7 +760,7 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, 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 (!jl_typemap_intersection_visitor(ml, offs+1, closure)) { JL_GC_POP(); return 0; } } if (super == jl_any_type) break; @@ -714,7 +770,7 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, else { // 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, ty, 3 | (exclude_typeofbottom << 2), offs, closure)) return 0; + 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); @@ -727,7 +783,7 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, 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 (!jl_typemap_intersection_visitor(ml, offs+1, closure)) { JL_GC_POP(); return 0; } } if (super == jl_any_type) break; @@ -736,9 +792,10 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, } else { // else an array scan is required to check subtypes - if (!jl_typemap_intersection_array_visitor(name1, ty, 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()