Skip to content

Commit

Permalink
permit NamedTuple{<:Any, Union{}} to be created
Browse files Browse the repository at this point in the history
This is a NamedTuple where all of the fields exist but have type
Union{}. It can be useful for initializing a typejoin in some cases, and
unlike Tuple{Union{}} there are no problems with covariance that should
be preventing it from existing. A few parameters of some constructors were
adjusted for performance and correctness (mostly the former).

Really just an edge case, since nobody should reasonably need a type
that cannot be constructed, but it does seem to mildly improve the
quality of some type queries such as these:

```
julia> fieldtype(NamedTuple{(:a, :b), T} where T<:Union{}, :a)
was: ERROR: TypeError: in fieldtype, expected DataType, got Type{Union{}}
now: Union{}

julia> fieldtypes(NamedTuple{(:a, :b), T} where T<:Union{})
was: ERROR: TypeError: in fieldtype, expected DataType, got Type{Union{}}
now: (Union{}, Union{})
```

Note that `NamedTuple{(), Union{}}` is disallowed still, as that
`Union{}` implies that the generating `Tuple` had least one field.
  • Loading branch information
vtjnash committed Mar 8, 2024
1 parent e618369 commit d28bceb
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 20 deletions.
4 changes: 2 additions & 2 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -736,8 +736,8 @@ eval(Core, :(NamedTuple{names}(args::Tuple) where {names} =

using .Intrinsics: sle_int, add_int

eval(Core, :(NamedTuple{names,T}(args::T) where {names, T <: Tuple} =
$(Expr(:splatnew, :(NamedTuple{names,T}), :args))))
eval(Core, :((NT::Type{NamedTuple{names,T}})(args::T) where {names, T <: Tuple} =
$(Expr(:splatnew, :NT, :args))))

# constructors for built-in types

Expand Down
2 changes: 1 addition & 1 deletion base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2695,7 +2695,7 @@ function refine_partial_type(@nospecialize t)
# if the first/second parameter of `NamedTuple` is known to be empty,
# the second/first argument should also be empty tuple type,
# so refine it here
return Const(NamedTuple())
return Const((;))
end
return t
end
Expand Down
19 changes: 8 additions & 11 deletions base/namedtuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -112,24 +112,24 @@ Core.NamedTuple

if nameof(@__MODULE__) === :Base

@eval function NamedTuple{names,T}(args::Tuple) where {names, T <: Tuple}
@eval function (NT::Type{NamedTuple{names,T}})(args::Tuple) where {names, T <: Tuple}
if length(args) != length(names::Tuple)
throw(ArgumentError("Wrong number of arguments to named tuple constructor."))
end
# Note T(args) might not return something of type T; e.g.
# Tuple{Type{Float64}}((Float64,)) returns a Tuple{DataType}
$(Expr(:splatnew, :(NamedTuple{names,T}), :(T(args))))
$(Expr(:splatnew, :NT, :(T(args))))
end

function NamedTuple{names, T}(nt::NamedTuple) where {names, T <: Tuple}
function (NT::Type{NamedTuple{names, T}})(nt::NamedTuple) where {names, T <: Tuple}
if @generated
Expr(:new, :(NamedTuple{names, T}),
Any[ :(let Tn = fieldtype(T, $n),
Expr(:new, :NT,
Any[ :(let Tn = fieldtype(NT, $n),
ntn = getfield(nt, $(QuoteNode(names[n])))
ntn isa Tn ? ntn : convert(Tn, ntn)
end) for n in 1:length(names) ]...)
else
NamedTuple{names, T}(map(Fix1(getfield, nt), names))
NT(map(Fix1(getfield, nt), names))
end
end

Expand All @@ -145,14 +145,11 @@ function NamedTuple{names}(nt::NamedTuple) where {names}
end
end

NamedTuple{names, T}(itr) where {names, T <: Tuple} = NamedTuple{names, T}(T(itr))
NamedTuple{names}(itr) where {names} = NamedTuple{names}(Tuple(itr))
(NT::Type{NamedTuple{names, T}})(itr) where {names, T <: Tuple} = NT(T(itr))
(NT::Type{NamedTuple{names}})(itr) where {names} = NT(Tuple(itr))

NamedTuple(itr) = (; itr...)

# avoids invalidating Union{}(...)
NamedTuple{names, Union{}}(itr::Tuple) where {names} = throw(MethodError(NamedTuple{names, Union{}}, (itr,)))

end # if Base

# Like NamedTuple{names, T} as a constructor, but omits the additional
Expand Down
2 changes: 2 additions & 0 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,8 @@ static jl_value_t *get_fieldtype(jl_value_t *t, jl_value_t *f, int dothrow)
tt = ((jl_tvar_t*)tt)->ub;
if (tt == (jl_value_t*)jl_any_type)
return (jl_value_t*)jl_any_type;
if (tt == (jl_value_t*)jl_bottom_type)
return (jl_value_t*)jl_bottom_type;
JL_GC_PUSH1(&f);
if (jl_is_symbol(f))
f = jl_box_long(field_index+1);
Expand Down
17 changes: 11 additions & 6 deletions src/jltypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -2141,15 +2141,20 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value
jl_errorf("duplicate field name in NamedTuple: \"%s\" is not unique", jl_symbol_name((jl_sym_t*)ni));
}
}
if (!jl_is_datatype(values_tt))
jl_error("NamedTuple field type must be a tuple type");
if (jl_is_va_tuple((jl_datatype_t*)values_tt) || jl_nparams(values_tt) != nf)
jl_error("NamedTuple names and field types must have matching lengths");
ndt->types = ((jl_datatype_t*)values_tt)->parameters;
if (values_tt == jl_bottom_type && nf > 0) {
ndt->types = jl_svec_fill(nf, jl_bottom_type);
}
else {
if (!jl_is_datatype(values_tt))
jl_error("NamedTuple field type must be a tuple datatype");
if (jl_is_va_tuple((jl_datatype_t*)values_tt) || jl_nparams(values_tt) != nf)
jl_error("NamedTuple names and field types must have matching lengths");
ndt->types = ((jl_datatype_t*)values_tt)->parameters;
}
jl_gc_wb(ndt, ndt->types);
}
else {
ndt->types = jl_emptysvec; // XXX: this is essentially always false
ndt->types = jl_emptysvec; // XXX: this is essentially always incorrect
}
}
else if (tn == jl_genericmemoryref_typename || tn == jl_genericmemory_typename) {
Expand Down
15 changes: 15 additions & 0 deletions test/namedtuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -437,3 +437,18 @@ let c = (a=1, b=2),
d = (b=3, c=(d=1,))
@test @inferred(mergewith51009((x,y)->y, c, d)) === (a = 1, b = 3, c = (d = 1,))
end

@test_throws ErrorException NamedTuple{(), Union{}}
for NT in (NamedTuple{(:a, :b), Union{}}, NamedTuple{(:a, :b), T} where T<:Union{})
@test fieldtype(NT, 1) == Union{}
@test fieldtype(NT, :b) == Union{}
@test_throws ErrorException fieldtype(NT, :c)
@test_throws BoundsError fieldtype(NT, 0)
@test_throws BoundsError fieldtype(NT, 3)
@test Base.return_types((Type{NT},)) do NT; fieldtype(NT, :a); end == Any[Type{Union{}}]
@test fieldtype(NamedTuple{<:Any, Union{}}, 1) == Union{}
end
let NT = NamedTuple{<:Any, Union{}}
@test fieldtype(NT, 100) == Union{}
@test only(Base.return_types((Type{NT},)) do NT; fieldtype(NT, 100); end) >: Type{Union{}}
end

0 comments on commit d28bceb

Please sign in to comment.