Skip to content

Commit

Permalink
add typemap filtering option for Union{}
Browse files Browse the repository at this point in the history
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: #33780

This pattern turns out to have still to been making package loading
slow. We could keep adding methods following the ambiguity pattern
#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
  • Loading branch information
vtjnash committed Apr 18, 2023
1 parent 6ec0a1a commit a08e327
Show file tree
Hide file tree
Showing 29 changed files with 317 additions and 148 deletions.
5 changes: 4 additions & 1 deletion base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
Expand Down
2 changes: 2 additions & 0 deletions base/arrayshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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 ||
Expand Down
20 changes: 11 additions & 9 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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...)

Expand Down Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions base/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions base/complex.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions base/float.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions base/generator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
2 changes: 1 addition & 1 deletion base/indices.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions base/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions base/iterators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion base/missing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions base/number.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.

Expand All @@ -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)
Expand All @@ -388,3 +391,4 @@ Complex{BigInt}
```
"""
big(::Type{T}) where {T<:Number} = typeof(big(zero(T)))
big(::Type{Union{}}, slurp...) = Union{}(0)
1 change: 1 addition & 0 deletions base/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions base/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 ##

Expand Down
6 changes: 6 additions & 0 deletions base/promotion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions base/some.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion base/traits.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
"""
Expand Down Expand Up @@ -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))
19 changes: 13 additions & 6 deletions src/gf.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
Expand Down Expand Up @@ -3189,6 +3191,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;
Expand Down Expand Up @@ -3244,6 +3247,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);
Expand All @@ -3258,9 +3264,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
Expand Down Expand Up @@ -3292,9 +3299,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;
Expand Down Expand Up @@ -3358,7 +3365,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;
}
Expand Down
2 changes: 1 addition & 1 deletion src/jltypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1601,7 +1601,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))
Expand Down
Loading

0 comments on commit a08e327

Please sign in to comment.