diff --git a/NEWS.md b/NEWS.md index 38daf8585e7b0c..69174c72bda85f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,9 @@ New language features a function argument name, the argument is unpacked into local variables `x` and `y` as in the assignment `(x, y) = arg` ([#6614]). + * Named tuples, with the syntax `(a=1, b=2)`. These behave very similarly to tuples, + except components can also be accessed by name using dot syntax `t.a` ([#22194]). + * Custom infix operators can now be defined by appending Unicode combining marks, primes, and sub/superscripts to other operators. For example, `+̂ₐ″` is parsed as an infix operator with the same diff --git a/base/boot.jl b/base/boot.jl index 7d1163f24d32d7..ae4d02e553d09a 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -120,7 +120,7 @@ export # key types Any, DataType, Vararg, ANY, NTuple, Tuple, Type, UnionAll, TypeName, TypeVar, Union, Void, - SimpleVector, AbstractArray, DenseArray, + SimpleVector, AbstractArray, DenseArray, NamedTuple, # special objects Function, CodeInfo, Method, MethodTable, TypeMapEntry, TypeMapLevel, Module, Symbol, Task, Array, WeakRef, VecElement, diff --git a/base/inference.jl b/base/inference.jl index 4f65ff8a3ba967..f64d221d727aba 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -493,6 +493,8 @@ end const _Type_name = Type.body.name isType(@nospecialize t) = isa(t, DataType) && (t::DataType).name === _Type_name +const _NamedTuple_name = NamedTuple.body.body.name + # true if Type is inlineable as constant (is a singleton) function isconstType(@nospecialize t) isType(t) || return false @@ -734,6 +736,10 @@ function isdefined_tfunc(args...) end if 1 <= idx <= a1.ninitialized return Const(true) + elseif a1.name === _NamedTuple_name + if isleaftype(a1) + return Const(false) + end elseif idx <= 0 || (!isvatuple(a1) && idx > fieldcount(a1)) return Const(false) elseif !isvatuple(a1) && isbits(fieldtype(a1, idx)) @@ -771,7 +777,9 @@ add_tfunc(nfields, 1, 1, # TODO: remove with deprecation in builtins.c for nfields(::Type) isleaftype(x.parameters[1]) && return Const(old_nfields(x.parameters[1])) elseif isa(x,DataType) && !x.abstract && !(x.name === Tuple.name && isvatuple(x)) && x !== DataType - return Const(length(x.types)) + if !(x.name === _NamedTuple_name && !isleaftype(x)) + return Const(length(x.types)) + end end return Int end, 0) @@ -1333,6 +1341,10 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name)) end return Any end + if s.name === _NamedTuple_name && !isleaftype(s) + # TODO: better approximate inference + return Any + end if isempty(s.types) return Bottom end @@ -1416,6 +1428,9 @@ function fieldtype_tfunc(@nospecialize(s0), @nospecialize(name)) if !isa(u,DataType) || u.abstract return Type end + if u.name === _NamedTuple_name && !isleaftype(u) + return Type + end ftypes = u.types if isempty(ftypes) return Bottom diff --git a/base/namedtuple.jl b/base/namedtuple.jl new file mode 100644 index 00000000000000..e43ffaf1a47c73 --- /dev/null +++ b/base/namedtuple.jl @@ -0,0 +1,178 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +function NamedTuple{names,T}(args::Tuple) where {names, T <: Tuple} + if length(args) == length(names) + if @generated + N = length(names) + types = T.parameters + Expr(:new, :(NamedTuple{names,T}), Any[ :(convert($(types[i]), args[$i])) for i in 1:N ]...) + else + N = length(names) + NT = NamedTuple{names,T} + ccall(:jl_new_structv, Any, (Any, Ptr{Void}, UInt32), NT, collect(Any, T(args)), N)::NT + end + else + throw(ArgumentError("Wrong number of arguments to named tuple constructor.")) + end +end + +function NamedTuple{names}(args::Tuple) where {names} + NamedTuple{names,typeof(args)}(args) +end + +NamedTuple() = NamedTuple{(),Tuple{}}(()) + +length(t::NamedTuple) = nfields(t) +start(t::NamedTuple) = 1 +done(t::NamedTuple, iter) = iter > nfields(t) +next(t::NamedTuple, iter) = (getfield(t, iter), iter + 1) +endof(t::NamedTuple) = nfields(t) +getindex(t::NamedTuple, i::Int) = getfield(t, i) +getindex(t::NamedTuple, i::Symbol) = getfield(t, i) +indexed_next(t::NamedTuple, i::Int, state) = (getfield(t,i), i+1) + +convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names,T}) where {names,T} = nt +convert(::Type{NamedTuple{names}}, nt::NamedTuple{names}) where {names} = nt + +function convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names}) where {names,T} + NamedTuple{names,T}(T(nt)) +end + +function show(io::IO, t::NamedTuple) + n = nfields(t) + for i = 1:n + # if field types aren't concrete, show full type + if typeof(getfield(t,i)) !== fieldtype(typeof(t), i) + show(io, typeof(t)) + print(io, "(") + show(io, Tuple(t)) + print(io, ")") + return + end + end + if n == 0 + print(io, "NamedTuple()") + else + print(io, "(") + for i = 1:n + print(io, fieldname(typeof(t),i), " = "); show(io, getfield(t,i)) + if n == 1 + print(io, ",") + elseif i < n + print(io, ", ") + end + end + print(io, ")") + end +end + +eltype(::Type{NamedTuple{names,T}}) where {names,T} = eltype(T) + +==(a::NamedTuple{n}, b::NamedTuple{n}) where {n} = Tuple(a) == Tuple(b) +==(a::NamedTuple, b::NamedTuple) = false + +isequal(a::NamedTuple{n}, b::NamedTuple{n}) where {n} = isequal(Tuple(a), Tuple(b)) +isequal(a::NamedTuple, b::NamedTuple) = false + +_nt_names(::NamedTuple{names}) where {names} = names +_nt_names(::Type{T}) where {names,T<:NamedTuple{names}} = names + +hash(x::NamedTuple, h::UInt) = xor(object_id(_nt_names(x)), hash(Tuple(x), h)) + +isless(a::NamedTuple{n}, b::NamedTuple{n}) where {n} = isless(Tuple(a), Tuple(b)) +# TODO: case where one argument's names are a prefix of the other's + +same_names(::NamedTuple{names}...) where {names} = true +same_names(::NamedTuple...) = false + +function map(f, nt::NamedTuple{names}, nts::NamedTuple...) where names + if !same_names(nt, nts...) + throw(ArgumentError("Named tuple names do not match.")) + end + # this method makes sure we don't define a map(f) method + NT = NamedTuple{names} + if @generated + N = length(names) + M = length(nts) + args = Expr[:(f($(Expr[:(getfield(nt, $j)), (:(getfield(nts[$i], $j)) for i = 1:M)...]...))) for j = 1:N] + :( NT(($(args...),)) ) + else + NT(map(f, map(Tuple, (nt, nts...))...)) + end +end + +# a version of `in` for the older world these generated functions run in +@pure function sym_in(x::Symbol, itr::Tuple{Vararg{Symbol}}) + for y in itr + y === x && return true + end + return false +end + +@pure function merge_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) + names = Symbol[an...] + for n in bn + if !sym_in(n, an) + push!(names, n) + end + end + (names...,) +end + +@pure function merge_types(names::Tuple{Vararg{Symbol}}, a::Type{<:NamedTuple}, b::Type{<:NamedTuple}) + bn = _nt_names(b) + Tuple{Any[ fieldtype(sym_in(n, bn) ? b : a, n) for n in names ]...} +end + +""" + merge(a::NamedTuple, b::NamedTuple) + +Construct a new named tuple by merging two existing ones. +The order of fields in `a` is preserved, but values are taken from matching +fields in `b`. Fields present only in `b` are appended at the end. + +```jldoctest +julia> merge((a=1, b=2, c=3), (b=4, d=5)) +(a = 1, b = 4, c = 3, d = 5) +``` +""" +function merge(a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn} + if @generated + names = merge_names(an, bn) + types = merge_types(names, a, b) + vals = Any[ :(getfield($(sym_in(n, bn) ? :b : :a), $(Expr(:quote, n)))) for n in names ] + :( NamedTuple{$names,$types}(($(vals...),)) ) + else + names = merge_names(an, bn) + types = merge_types(names, typeof(a), typeof(b)) + NamedTuple{names,types}(map(n->getfield(sym_in(n, bn) ? b : a, n), names)) + end +end + +merge(a::NamedTuple{()}, b::NamedTuple) = b + +""" + merge(a::NamedTuple, iterable) + +Interpret an iterable of key-value pairs as a named tuple, and perform a merge. + +```jldoctest +julia> merge((a=1, b=2, c=3), [:b=>4, :d=>5]) +(a = 1, b = 4, c = 3, d = 5) +``` +""" +function merge(a::NamedTuple, itr) + names = Symbol[] + vals = Any[] + for (k,v) in itr + push!(names, k) + push!(vals, v) + end + merge(a, NamedTuple{(names...)}((vals...,))) +end + +keys(nt::NamedTuple{names}) where {names} = names +values(nt::NamedTuple) = Tuple(nt) +haskey(nt::NamedTuple, key::Union{Integer, Symbol}) = isdefined(nt, key) +get(nt::NamedTuple, key::Union{Integer, Symbol}, default) = haskey(nt, key) ? getfield(nt, key) : default +get(f::Callable, nt::NamedTuple, key::Union{Integer, Symbol}) = haskey(nt, key) ? getfield(nt, key) : f() diff --git a/base/reflection.jl b/base/reflection.jl index 27a24eb540fd02..dc31ea7f3d668f 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -127,12 +127,14 @@ julia> fieldname(SparseMatrixCSC, 5) ``` """ function fieldname(t::DataType, i::Integer) - n_fields = length(t.name.names) + names = isdefined(t, :names) ? t.names : t.name.names + n_fields = length(names) field_label = n_fields == 1 ? "field" : "fields" i > n_fields && throw(ArgumentError("Cannot access field $i since type $t only has $n_fields $field_label.")) i < 1 && throw(ArgumentError("Field numbers must be positive integers. $i is invalid.")) - return t.name.names[i]::Symbol + return names[i]::Symbol end + fieldname(t::UnionAll, i::Integer) = fieldname(unwrap_unionall(t), i) fieldname(t::Type{<:Tuple}, i::Integer) = i < 1 || i > fieldcount(t) ? throw(BoundsError(t, i)) : Int(i) diff --git a/base/sysimg.jl b/base/sysimg.jl index 0f1a6d2cd514f6..bbbce580731f15 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -142,6 +142,8 @@ Vector(m::Integer) = Array{Any,1}(Int(m)) Matrix{T}(m::Integer, n::Integer) where {T} = Matrix{T}(Int(m), Int(n)) Matrix(m::Integer, n::Integer) = Matrix{Any}(Int(m), Int(n)) +include("namedtuple.jl") + # numeric operations include("hashing.jl") include("rounding.jl") diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index 3b1478c4dd659f..2779d27c8f9a00 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -221,6 +221,48 @@ A zero-argument anonymous function is written as `()->3`. The idea of a function may seem strange, but is useful for "delaying" a computation. In this usage, a block of code is wrapped in a zero-argument function, which is later invoked by calling it as `f`. +## Tuples + +Julia has a built-in data structure called a *tuple* that is closely related to function +arguments and return values. +A tuple is a fixed-length container that can hold any values, but cannot be modified +(it is *immutable*). +Tuples are constructed with commas and parentheses, and can be accessed via indexing: + +```jldoctest +julia> (1, 1+1) +(1, 2) + +julia> (1,) +(1,) + +julia> x = (0.0, "hello", 6*7) +(0.0, "hello", 42) + +julia> x[2] +"hello" +``` + +Notice that a length-1 tuple must be written with a comma, `(1,)`, since `(1)` would just +be a parenthesized value. +`()` represents the empty (length-0) tuple. + +## Named Tuples + +The components of tuples can optionally be named, in which case a *named tuple* is +constructed: + +```jldoctest +julia> x = (a=1, b=1+1) +(a = 1, b = 2) + +julia> x.a +1 +``` + +Named tuples are very similar to tuples, except that fields can additionally be accessed by name +using dot syntax (`x.a`). + ## Multiple Return Values In Julia, one returns a tuple of values to simulate returning multiple values. However, tuples @@ -320,7 +362,7 @@ In all these cases, `x` is bound to a tuple of the trailing values passed to `ba It is possible to constrain the number of values passed as a variable argument; this will be discussed later in [Parametrically-constrained Varargs methods](@ref). -On the flip side, it is often handy to "splice" the values contained in an iterable collection +On the flip side, it is often handy to "splat" the values contained in an iterable collection into a function call as individual arguments. To do this, one also uses `...` but in the function call instead: @@ -349,7 +391,7 @@ julia> bar(x...) (1, 2, (3, 4)) ``` -Furthermore, the iterable object spliced into a function call need not be a tuple: +Furthermore, the iterable object splatted into a function call need not be a tuple: ```jldoctest barfunc julia> x = [3,4] @@ -371,7 +413,7 @@ julia> bar(x...) (1, 2, (3, 4)) ``` -Also, the function that arguments are spliced into need not be a varargs function (although it +Also, the function that arguments are splatted into need not be a varargs function (although it often is): ```jldoctest @@ -397,7 +439,7 @@ Closest candidates are: baz(::Any, ::Any) at none:1 ``` -As you can see, if the wrong number of elements are in the spliced container, then the function +As you can see, if the wrong number of elements are in the splatted container, then the function call will fail, just as it would if too many arguments were given explicitly. ## Optional Arguments diff --git a/doc/src/manual/types.md b/doc/src/manual/types.md index ef2955262bd1ac..9eacec5057752a 100644 --- a/doc/src/manual/types.md +++ b/doc/src/manual/types.md @@ -905,6 +905,31 @@ used to represent the arguments accepted by varargs methods (see [Varargs Functi The type `Vararg{T,N}` corresponds to exactly `N` elements of type `T`. `NTuple{N,T}` is a convenient alias for `Tuple{Vararg{T,N}}`, i.e. a tuple type containing exactly `N` elements of type `T`. +### Named Tuple Types + +Named tuples are instances of the `NamedTuple` type, which has two parameters: a tuple of +symbols giving the field names, and a tuple type giving the field types. + +```jldoctest +julia> typeof((a=1,b="hello")) +NamedTuple{(:a, :b),Tuple{Int64,String}} +``` + +A `NamedTuple` type can be used as a constructor, accepting a single tuple argument. +The constructed `NamedTuple` type can be either a concrete type, with both parameters specified, +or a type that specifies only field names: + +```jldoctest +julia> NamedTuple{(:a, :b),Tuple{Float32, String}}((1,"")) +(a = 1.0f0, b = "") + +julia> NamedTuple{(:a, :b)}((1,"")) +(a = 1, b = "") +``` + +If field types are specified, the arguments are converted. Otherwise the types of the arguments +are used directly. + #### [Singleton Types](@id man-singleton-types) There is a special kind of abstract parametric type that must be mentioned here: singleton types. diff --git a/src/ast.scm b/src/ast.scm index c8799364a93e29..5f724c34d39987 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -153,6 +153,7 @@ ;; predicates and accessors (define (quoted? e) (memq (car e) '(quote top core globalref outerref line break inert meta))) +(define (quotify e) `',e) (define (lam:args x) (cadr x)) (define (lam:vars x) (llist-vars (lam:args x))) diff --git a/src/builtins.c b/src/builtins.c index fa61ab54eadd63..38ae82d232cc1b 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1295,6 +1295,7 @@ void jl_init_primitives(void) add_builtin("QuoteNode", (jl_value_t*)jl_quotenode_type); add_builtin("NewvarNode", (jl_value_t*)jl_newvarnode_type); add_builtin("GlobalRef", (jl_value_t*)jl_globalref_type); + add_builtin("NamedTuple", (jl_value_t*)jl_namedtuple_type); add_builtin("Bool", (jl_value_t*)jl_bool_type); add_builtin("UInt8", (jl_value_t*)jl_uint8_type); diff --git a/src/datatype.c b/src/datatype.c index f4135965926753..9ebc3a6d0ac158 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -81,6 +81,7 @@ jl_datatype_t *jl_new_uninitialized_datatype(void) t->hasfreetypevars = 0; t->isleaftype = 1; t->layout = NULL; + t->names = NULL; return t; } @@ -288,7 +289,7 @@ void jl_compute_field_offsets(jl_datatype_t *st) return; } } - if (st->types == NULL) + if (st->types == NULL || (jl_is_namedtuple_type(st) && !jl_is_leaf_type((jl_value_t*)st))) return; uint32_t nfields = jl_svec_len(st->types); if (nfields == 0) { diff --git a/src/dump.c b/src/dump.c index e45087a82bd258..3a12afde8dc166 100644 --- a/src/dump.c +++ b/src/dump.c @@ -367,6 +367,7 @@ static void jl_serialize_datatype(jl_serializer_state *s, jl_datatype_t *dt) if (has_instance) jl_serialize_value(s, dt->instance); jl_serialize_value(s, dt->name); + jl_serialize_value(s, dt->names); jl_serialize_value(s, dt->parameters); jl_serialize_value(s, dt->super); jl_serialize_value(s, dt->types); @@ -1245,6 +1246,8 @@ static jl_value_t *jl_deserialize_datatype(jl_serializer_state *s, int pos, jl_v } dt->name = (jl_typename_t*)jl_deserialize_value(s, (jl_value_t**)&dt->name); jl_gc_wb(dt, dt->name); + dt->names = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&dt->names); + jl_gc_wb(dt, dt->names); dt->parameters = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&dt->parameters); jl_gc_wb(dt, dt->parameters); dt->super = (jl_datatype_t*)jl_deserialize_value(s, (jl_value_t**)&dt->super); @@ -2803,7 +2806,6 @@ void jl_init_serializer(void) jl_box_int32(30), jl_box_int32(31), jl_box_int32(32), #ifndef _P64 jl_box_int32(33), jl_box_int32(34), jl_box_int32(35), - jl_box_int32(36), jl_box_int32(37), #endif jl_box_int64(0), jl_box_int64(1), jl_box_int64(2), jl_box_int64(3), jl_box_int64(4), jl_box_int64(5), @@ -2818,7 +2820,6 @@ void jl_init_serializer(void) jl_box_int64(30), jl_box_int64(31), jl_box_int64(32), #ifdef _P64 jl_box_int64(33), jl_box_int64(34), jl_box_int64(35), - jl_box_int64(36), jl_box_int64(37), #endif jl_labelnode_type, jl_linenumbernode_type, jl_gotonode_type, jl_quotenode_type, jl_type_type, jl_bottom_type, jl_ref_type, @@ -2844,7 +2845,8 @@ void jl_init_serializer(void) jl_intrinsic_type->name, jl_task_type->name, jl_labelnode_type->name, jl_linenumbernode_type->name, jl_builtin_type->name, jl_gotonode_type->name, jl_quotenode_type->name, jl_globalref_type->name, jl_typeofbottom_type->name, - jl_string_type->name, jl_abstractstring_type->name, + jl_string_type->name, jl_abstractstring_type->name, jl_namedtuple_type, + jl_namedtuple_typename, ptls->root_task, @@ -2882,6 +2884,7 @@ void jl_init_serializer(void) arraylist_push(&builtin_typenames, ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_densearray_type))->name); arraylist_push(&builtin_typenames, jl_tuple_typename); arraylist_push(&builtin_typenames, jl_vararg_typename); + arraylist_push(&builtin_typenames, jl_namedtuple_typename); } #ifdef __cplusplus diff --git a/src/interpreter.c b/src/interpreter.c index 8c2e1d0e521b03..12fad826c6b728 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -172,7 +172,7 @@ void jl_set_datatype_super(jl_datatype_t *tt, jl_value_t *super) if (!jl_is_datatype(super) || !jl_is_abstracttype(super) || tt->name == ((jl_datatype_t*)super)->name || jl_subtype(super,(jl_value_t*)jl_vararg_type) || - jl_is_tuple_type(super) || + jl_is_tuple_type(super) || jl_is_namedtuple_type(super) || jl_subtype(super,(jl_value_t*)jl_type_type) || super == (jl_value_t*)jl_builtin_type) { jl_errorf("invalid subtyping in definition of %s", diff --git a/src/jltypes.c b/src/jltypes.c index 56209a26aedeff..09d44e8f9dc8a9 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -112,6 +112,8 @@ jl_unionall_t *jl_pointer_type; jl_typename_t *jl_pointer_typename; jl_datatype_t *jl_void_type; jl_datatype_t *jl_voidpointer_type; +jl_typename_t *jl_namedtuple_typename; +jl_unionall_t *jl_namedtuple_type; jl_value_t *jl_an_empty_vec_any=NULL; jl_value_t *jl_stackovf_exception; #ifdef SEGV_EXCEPTION @@ -1133,6 +1135,7 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value jl_typestack_t top; jl_typename_t *tn = dt->name; int istuple = (tn == jl_tuple_typename); + int isnamedtuple = (tn == jl_namedtuple_typename); // check type cache if (cacheable) { JL_LOCK(&typecache_lock); // Might GC @@ -1255,7 +1258,41 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value ndt->super = NULL; ndt->parameters = p; jl_gc_wb(ndt, ndt->parameters); - ndt->types = istuple ? p : NULL; // to be filled in below + ndt->types = NULL; // to be filled in below + if (istuple) { + ndt->types = p; + } + else if (isnamedtuple) { + jl_value_t *names_tup = jl_svecref(p, 0); + jl_value_t *values_tt = jl_svecref(p, 1); + if (!jl_has_free_typevars(names_tup) && !jl_has_free_typevars(values_tt)) { + if (!jl_is_tuple(names_tup)) + jl_type_error_rt("NamedTuple", "names", (jl_value_t*)jl_anytuple_type, names_tup); + size_t nf = jl_nfields(names_tup); + jl_svec_t *names = jl_alloc_svec_uninit(nf); + for (size_t i = 0; i < nf; i++) { + jl_value_t *ni = jl_fieldref(names_tup, i); + if (!jl_is_symbol(ni)) + jl_type_error_rt("NamedTuple", "name", (jl_value_t*)jl_symbol_type, ni); + for (size_t j = 0; j < i; j++) { + if (ni == jl_svecref(names, j)) + jl_errorf("duplicate field name in NamedTuple: \"%s\" is not unique", jl_symbol_name((jl_sym_t*)ni)); + } + jl_svecset(names, i, 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->names = names; + jl_gc_wb(ndt, ndt->names); + ndt->types = ((jl_datatype_t*)values_tt)->parameters; + jl_gc_wb(ndt, ndt->types); + } + else { + ndt->types = jl_emptysvec; + } + } ndt->mutabl = dt->mutabl; ndt->abstract = dt->abstract; ndt->instance = NULL; @@ -1269,7 +1306,7 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value if (cacheable && !ndt->abstract) ndt->uid = jl_assign_type_uid(); - if (istuple) { + if (istuple || isnamedtuple) { ndt->super = jl_any_type; } else if (dt->super) { @@ -1280,13 +1317,13 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value if (ftypes == NULL || dt->super == NULL) { // in the process of creating this type definition: // need to instantiate the super and types fields later - assert(inside_typedef && !istuple); + assert(inside_typedef && !istuple && !isnamedtuple); arraylist_push(&partial_inst, ndt); } else { - assert(ftypes != jl_emptysvec || jl_field_names(ndt) == jl_emptysvec); + assert(ftypes != jl_emptysvec || jl_field_names(ndt) == jl_emptysvec || isnamedtuple); assert(ftypes == jl_emptysvec || !ndt->abstract); - if (!istuple) { + if (!istuple && !isnamedtuple) { // recursively instantiate the types of the fields ndt->types = inst_all(ftypes, env, stack, 1); jl_gc_wb(ndt, ndt->types); @@ -1302,6 +1339,8 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value if (istuple) ndt->ninitialized = ntp - isvatuple; + else if (isnamedtuple) + ndt->ninitialized = jl_svec_len(ndt->types); else ndt->ninitialized = dt->ninitialized; @@ -1687,11 +1726,12 @@ void jl_init_types(void) jl_datatype_type->name->wrapper = (jl_value_t*)jl_datatype_type; jl_datatype_type->super = (jl_datatype_t*)jl_type_type; jl_datatype_type->parameters = jl_emptysvec; - jl_datatype_type->name->names = jl_perm_symsvec(16, + jl_datatype_type->name->names = jl_perm_symsvec(17, "name", "super", "parameters", "types", + "names", "instance", "layout", "size", @@ -1704,11 +1744,11 @@ void jl_init_types(void) "depth", "hasfreetypevars", "isleaftype"); - jl_datatype_type->types = jl_svec(16, + jl_datatype_type->types = jl_svec(17, jl_typename_type, jl_datatype_type, jl_simplevector_type, - jl_simplevector_type, + jl_simplevector_type, jl_simplevector_type, jl_any_type, // instance jl_any_type, jl_any_type, jl_any_type, jl_any_type, jl_any_type, jl_any_type, jl_any_type, jl_any_type, @@ -2147,20 +2187,29 @@ void jl_init_types(void) jl_string_type->instance = NULL; jl_compute_field_offsets(jl_string_type); + jl_tvar_t *ntval_var = jl_new_typevar(jl_symbol("T"), (jl_value_t*)jl_bottom_type, + (jl_value_t*)jl_anytuple_type); + tv = jl_svec2(tvar("names"), ntval_var); + jl_datatype_t *ntt = jl_new_datatype(jl_symbol("NamedTuple"), core, jl_any_type, tv, + jl_emptysvec, jl_emptysvec, 0, 0, 0); + jl_namedtuple_type = (jl_unionall_t*)ntt->name->wrapper; + ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_namedtuple_type))->layout = NULL; + jl_namedtuple_typename = ntt->name; + // complete builtin type metadata jl_value_t *pointer_void = jl_apply_type1((jl_value_t*)jl_pointer_type, (jl_value_t*)jl_void_type); jl_voidpointer_type = (jl_datatype_t*)pointer_void; - jl_svecset(jl_datatype_type->types, 5, jl_voidpointer_type); - jl_svecset(jl_datatype_type->types, 6, jl_int32_type); + jl_svecset(jl_datatype_type->types, 6, jl_voidpointer_type); jl_svecset(jl_datatype_type->types, 7, jl_int32_type); jl_svecset(jl_datatype_type->types, 8, jl_int32_type); - jl_svecset(jl_datatype_type->types, 9, jl_bool_type); + jl_svecset(jl_datatype_type->types, 9, jl_int32_type); jl_svecset(jl_datatype_type->types, 10, jl_bool_type); - jl_svecset(jl_datatype_type->types, 11, jl_voidpointer_type); + jl_svecset(jl_datatype_type->types, 11, jl_bool_type); jl_svecset(jl_datatype_type->types, 12, jl_voidpointer_type); - jl_svecset(jl_datatype_type->types, 13, jl_int32_type); - jl_svecset(jl_datatype_type->types, 14, jl_bool_type); + jl_svecset(jl_datatype_type->types, 13, jl_voidpointer_type); + jl_svecset(jl_datatype_type->types, 14, jl_int32_type); jl_svecset(jl_datatype_type->types, 15, jl_bool_type); + jl_svecset(jl_datatype_type->types, 16, jl_bool_type); jl_svecset(jl_typename_type->types, 1, jl_module_type); jl_svecset(jl_typename_type->types, 6, jl_long_type); jl_svecset(jl_typename_type->types, 3, jl_type_type); diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index f57a8c90be1cd0..0f58aad183a15a 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -924,7 +924,7 @@ ,@(map (lambda (v) `(local ,v)) params) ,@(map (lambda (n v) (make-assignment n (bounds-to-TypeVar v))) params bounds) (struct_type ,name (call (core svec) ,@params) - (call (core svec) ,@(map (lambda (x) `',x) field-names)) + (call (core svec) ,@(map quotify field-names)) ,super (call (core svec) ,@field-types) ,mut ,min-initialized))) ;; "inner" constructors (scope-block @@ -1920,6 +1920,76 @@ (extract (cdr params) (cons p newparams) whereparams))))) (extract (cddr e) '() '())) +(define (named-tuple-expr names values) + `(call (curly (core NamedTuple) (tuple ,@names)) + (tuple ,@values))) + +(define (lower-named-tuple lst) + (let* ((names (apply append + (map (lambda (x) + (cond #;((symbol? x) (list x)) + ((and (or (assignment? x) (kwarg? x)) (symbol? (cadr x))) + (list (cadr x))) + #;((and (length= x 3) (eq? (car x) '|.|)) + (list (cadr (caddr x)))) + (else '()))) + lst))) + (dups (has-dups names))) + (if dups + (error (string "field name \"" (car dups) "\" repeated in named tuple")))) + (define (to-nt n v) + (if (null? n) + #f + (named-tuple-expr (reverse! (map quotify n)) (reverse v)))) + (define (merge old new) + (if old + (if new + `(call (top merge) ,old ,new) + old) + new)) + (let loop ((L lst) + (current-names '()) + (current-vals '()) + (expr #f)) + (if (null? L) + (merge expr (to-nt current-names current-vals)) + (let ((el (car L))) + (cond ((or (assignment? el) (kwarg? el)) + (if (not (symbol? (cadr el))) + (error (string "invalid named tuple field name \"" (deparse (cadr el)) "\""))) + (loop (cdr L) + (cons (cadr el) current-names) + (cons (caddr el) current-vals) + expr)) +#| + ((symbol? el) ;; x => x = x + (loop (cdr L) + (cons el current-names) + (cons el current-vals) + expr)) + ((and (length= el 3) (eq? (car el) '|.|)) ;; a.x => x = a.x + (loop (cdr L) + (cons (cadr (caddr el)) current-names) + (cons el current-vals) + expr)) + ((and (length= el 4) (eq? (car el) 'call) (eq? (cadr el) '=>)) + (loop (cdr L) + '() + '() + (merge (merge expr (to-nt current-names current-vals)) + (named-tuple-expr (list (caddr el)) (list (cadddr el)))))) + ((vararg? el) + (loop (cdr L) + '() + '() + (let ((current (merge expr (to-nt current-names current-vals)))) + (if current + (merge current (cadr el)) + `(call (top merge) (call (top NamedTuple)) ,(cadr el)))))) +|# + (else + (error (string "invalid named tuple element \"" (deparse el) "\"")))))))) + (define (expand-forms e) (if (or (atom? e) (memq (car e) '(quote inert top core globalref outerref line module toplevel ssavalue null meta))) e @@ -2246,11 +2316,16 @@ 'tuple (lambda (e) - (if (and (length> e 1) (pair? (cadr e)) (eq? (caadr e) 'parameters)) - (error "unexpected semicolon in tuple")) - (if (any assignment? (cdr e)) - (error "assignment not allowed inside tuple")) - (expand-forms `(call (core tuple) ,@(cdr e)))) + (cond ((and (length> e 1) (pair? (cadr e)) (eq? (caadr e) 'parameters)) + (error "unexpected semicolon in tuple") + ;; this enables `(; ...)` named tuple syntax + #;(if (length= e 2) + (expand-forms (lower-named-tuple (cdr (cadr e)))) + (error "unexpected semicolon in tuple"))) + ((any assignment? (cdr e)) + (expand-forms (lower-named-tuple (cdr e)))) + (else + (expand-forms `(call (core tuple) ,@(cdr e)))))) '=> (lambda (e) @@ -2855,7 +2930,7 @@ f(x) = yt(x) (body (global ,name) (const ,name) ,@(map (lambda (p n) `(= ,p (call (core TypeVar) ',n (core Any)))) P names) (struct_type ,name (call (core svec) ,@P) - (call (core svec) ,@(map (lambda (v) `',v) fields)) + (call (core svec) ,@(map quotify fields)) ,super (call (core svec) ,@types) false ,(length fields)) (return (null)))))))) @@ -2865,7 +2940,7 @@ f(x) = yt(x) (() () 0 ()) (body (global ,name) (const ,name) (struct_type ,name (call (core svec)) - (call (core svec) ,@(map (lambda (v) `',v) fields)) + (call (core svec) ,@(map quotify fields)) ,super (call (core svec) ,@(map (lambda (v) '(core Box)) fields)) false ,(length fields)) @@ -2882,7 +2957,7 @@ f(x) = yt(x) ; (const ,name) ; ,@(map (lambda (p n) `(= ,p (call (core TypeVar) ',n (core Any)))) P names) ; (struct_type ,name (call (core svec) ,@P) -; (call (core svec) ,@(map (lambda (v) `',v) fields)) +; (call (core svec) ,@(map quotify fields)) ; ,super ; (call (core svec) ,@types) false ,(length fields))))) @@ -2891,7 +2966,7 @@ f(x) = yt(x) ; `((global ,name) ; (const ,name) ; (struct_type ,name (call (core svec)) -; (call (core svec) ,@(map (lambda (v) `',v) fields)) +; (call (core svec) ,@(map quotify fields)) ; ,super ; (call (core svec) ,@(map (lambda (v) 'Any) fields)) ; false ,(length fields)))) diff --git a/src/julia.h b/src/julia.h index 32a7d2a62f4b89..4d20c9195f9d25 100644 --- a/src/julia.h +++ b/src/julia.h @@ -374,6 +374,7 @@ typedef struct _jl_datatype_t { struct _jl_datatype_t *super; jl_svec_t *parameters; jl_svec_t *types; + jl_svec_t *names; jl_value_t *instance; // for singletons const jl_datatype_layout_t *layout; int32_t size; // TODO: move to _jl_datatype_layout_t @@ -556,6 +557,8 @@ extern JL_DLLEXPORT jl_datatype_t *jl_voidpointer_type; extern JL_DLLEXPORT jl_unionall_t *jl_pointer_type; extern JL_DLLEXPORT jl_unionall_t *jl_ref_type; extern JL_DLLEXPORT jl_typename_t *jl_pointer_typename; +extern JL_DLLEXPORT jl_typename_t *jl_namedtuple_typename; +extern JL_DLLEXPORT jl_unionall_t *jl_namedtuple_type; extern JL_DLLEXPORT jl_value_t *jl_array_uint8_type; extern JL_DLLEXPORT jl_value_t *jl_array_any_type; @@ -778,7 +781,10 @@ STATIC_INLINE void jl_array_uint8_set(void *a, size_t i, uint8_t x) // struct type info STATIC_INLINE jl_svec_t *jl_field_names(jl_datatype_t *st) { - return st->name->names; + jl_svec_t *names = st->names; + if (!names) + names = st->name->names; + return names; } STATIC_INLINE jl_sym_t *jl_field_name(jl_datatype_t *st, size_t i) { @@ -964,6 +970,12 @@ STATIC_INLINE int jl_is_tuple_type(void *t) ((jl_datatype_t*)(t))->name == jl_tuple_typename); } +STATIC_INLINE int jl_is_namedtuple_type(void *t) +{ + return (jl_is_datatype(t) && + ((jl_datatype_t*)(t))->name == jl_namedtuple_typename); +} + STATIC_INLINE int jl_is_vecelement_type(jl_value_t* t) { return (jl_is_datatype(t) && diff --git a/src/rtutils.c b/src/rtutils.c index e81cc2d2a60357..e3f5957b8cabf5 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -862,8 +862,8 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt n += jl_printf(out, ")"); } else if (jl_is_datatype(vt)) { - int istuple = jl_is_tuple_type(vt); - if (!istuple) + int istuple = jl_is_tuple_type(vt), isnamedtuple = jl_is_namedtuple_type(vt); + if (!istuple && !isnamedtuple) n += jl_static_show_x(out, (jl_value_t*)vt, depth); n += jl_printf(out, "("); size_t nb = jl_datatype_size(vt); @@ -896,7 +896,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } n += jl_static_show_x_(out, (jl_value_t*)fld_ptr, ft, depth); } - if (istuple && tlen == 1) + if ((istuple || isnamedtuple) && tlen == 1) n += jl_printf(out, ","); else if (i != tlen - 1) n += jl_printf(out, ", "); diff --git a/src/staticdata.c b/src/staticdata.c index a5e7b75f73f66f..0da6a8ac79e61e 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -1583,6 +1583,7 @@ static void jl_init_serializer2(int for_serialize) jl_gotonode_type->name, jl_quotenode_type->name, jl_globalref_type->name, jl_typeofbottom_type->name, jl_string_type->name, jl_abstractstring_type->name, + jl_namedtuple_type, jl_namedtuple_typename, jl_int32_type, jl_int64_type, jl_bool_type, jl_uint8_type, @@ -1649,6 +1650,7 @@ static void jl_init_serializer2(int for_serialize) arraylist_push(&builtin_typenames, ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_densearray_type))->name); arraylist_push(&builtin_typenames, jl_tuple_typename); arraylist_push(&builtin_typenames, jl_vararg_typename); + arraylist_push(&builtin_typenames, jl_namedtuple_typename); } static void jl_cleanup_serializer2(void) diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 6165813bc395e6..5364f8fb2fd093 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -279,6 +279,7 @@ end pop!(need_to_handle_undef_sparam, which(Base.SparseArrays._absspvec_vcat, (AbstractSparseArray{Tv, Ti, 1} where {Tv, Ti},))) pop!(need_to_handle_undef_sparam, which(Base.SparseArrays._absspvec_hcat, (AbstractSparseArray{Tv, Ti, 1} where {Tv, Ti},))) pop!(need_to_handle_undef_sparam, which(Base.cat, (Any, Base.SparseArrays._TypedDenseConcatGroup{T} where T))) + pop!(need_to_handle_undef_sparam, first(methods(Base.same_names))) @test need_to_handle_undef_sparam == Set() end end diff --git a/test/choosetests.jl b/test/choosetests.jl index dfc70a23d31238..c3521a3465c602 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -37,7 +37,7 @@ function choosetests(choices = []) "bitarray", "copy", "math", "fastmath", "functional", "iterators", "operators", "path", "ccall", "parse", "loading", "bigint", "bigfloat", "sorting", "statistics", "spawn", "backtrace", - "file", "read", "version", "resolve", + "file", "read", "version", "resolve", "namedtuple", "pollfd", "mpfr", "broadcast", "complex", "socket", "floatapprox", "stdlib", "reflection", "regex", "float16", "combinatorics", "sysinfo", "env", "rounding", "ranges", "mod2pi", diff --git a/test/inference.jl b/test/inference.jl index 0530d3012d0023..f9dca24b0eb8fc 100644 --- a/test/inference.jl +++ b/test/inference.jl @@ -1001,6 +1001,22 @@ copy_dims_pair(out, dim::Colon, tail...) = copy_dims_pair(out => dim, tail...) @test Base.return_types(copy_dims_pair, (Tuple{}, Vararg{Union{Int,Colon}})) == Any[Tuple{}, Tuple{}, Tuple{}] @test all(m -> 5 < count_specializations(m) < 25, methods(copy_dims_pair)) +@test isdefined_tfunc(typeof(NamedTuple()), Const(0)) === Const(false) +@test isdefined_tfunc(typeof(NamedTuple()), Const(1)) === Const(false) +@test isdefined_tfunc(typeof((a=1,b=2)), Const(:a)) === Const(true) +@test isdefined_tfunc(typeof((a=1,b=2)), Const(:b)) === Const(true) +@test isdefined_tfunc(typeof((a=1,b=2)), Const(:c)) === Const(false) +@test isdefined_tfunc(typeof((a=1,b=2)), Const(0)) === Const(false) +@test isdefined_tfunc(typeof((a=1,b=2)), Const(1)) === Const(true) +@test isdefined_tfunc(typeof((a=1,b=2)), Const(2)) === Const(true) +@test isdefined_tfunc(typeof((a=1,b=2)), Const(3)) === Const(false) +@test isdefined_tfunc(NamedTuple, Const(1)) == Bool +@test isdefined_tfunc(NamedTuple, Symbol) == Bool +@test Const(false) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(:z)) +@test Const(true) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(1)) +@test Const(false) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(3)) +@test Const(true) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(:y)) + # splatting an ::Any should still allow inference to use types of parameters preceding it f22364(::Int, ::Any...) = 0 f22364(::String, ::Any...) = 0.0 diff --git a/test/namedtuple.jl b/test/namedtuple.jl new file mode 100644 index 00000000000000..d1c8c2cf5e2f65 --- /dev/null +++ b/test/namedtuple.jl @@ -0,0 +1,154 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +@test_throws TypeError NamedTuple{1,Tuple{}} +@test_throws TypeError NamedTuple{(),1} +@test_throws TypeError NamedTuple{(:a,1),Tuple{Int}} +@test_throws ErrorException NamedTuple{(:a,:b),Tuple{Int}} +@test_throws ErrorException NamedTuple{(:a,:b),Tuple{Int,Vararg{Int}}} +@test_throws ErrorException NamedTuple{(:a,),Union{Tuple{Int},Tuple{String}}} +@test_throws ErrorException NamedTuple{(:a,:a),Tuple{Int,Int}} +@test_throws ErrorException NamedTuple{(:a,:a)}((1,2)) + +@test (a=1,).a == 1 +@test (a=2,)[1] == 2 +@test (a=3,)[:a] == 3 +@test (x=4, y=5, z=6).y == 5 +@test (x=4, y=5, z=6).z == 6 +@test_throws ErrorException (x=4, y=5, z=6).a +@test_throws BoundsError (a=2,)[0] +@test_throws BoundsError (a=2,)[2] + +@test length(NamedTuple()) == 0 +@test length((a=1,)) == 1 +@test length((a=1, b=0)) == 2 + +@test (a=1,b=2) === (a=1,b=2) +@test (a=1,b=2) !== (b=1,a=2) + +@test (a=1,b=2) == (a=1,b=2) +@test (a=1,b=2) != (b=1,a=2) +@test NamedTuple() === NamedTuple() +@test NamedTuple() != (a=1,) + +@test string((a=1,)) == "(a = 1,)" +@test string((name="", day=:today)) == "(name = \"\", day = :today)" +@test string(NamedTuple()) == "NamedTuple()" + +@test hash((a = 1, b = "hello")) == hash(NamedTuple{(:a,:b),Tuple{Int,String}}((1, "hello"))) +@test hash((a = 1, b = "hello")) != hash(NamedTuple{(:a,:c),Tuple{Int,String}}((1, "hello"))) +@test hash((a = 1, b = "hello")) != hash(NamedTuple{(:a,:b),Tuple{Int,String}}((1, "helo"))) + +@test NamedTuple{(:a,:b),Tuple{Int8,Int16}}((1,2)) === (a=Int8(1), b=Int16(2)) +@test convert(NamedTuple{(:a,:b),Tuple{Int8,Int16}}, (a=3,b=4)) === (a=Int8(3), b=Int16(4)) +@test_throws MethodError convert(NamedTuple{(:a,:b),Tuple{Int8,Int16}}, (x=3,y=4)) === (a=Int8(3), b=Int16(4)) + +@test eltype((a=[1,2], b=[3,4])) === Vector{Int} + +@test Tuple((a=[1,2], b=[3,4])) == ([1,2], [3,4]) +@test Tuple(NamedTuple()) === () +@test Tuple((x=4, y=5, z=6)) == (4,5,6) +@test collect((x=4, y=5, z=6)) == [4,5,6] +@test Tuple((a=1, b=2, c=3)) == (1, 2, 3) + +@test isless((a=1,b=2), (a=1,b=3)) +@test_broken isless((a=1,), (a=1,b=2)) +@test !isless((a=1,b=2), (a=1,b=2)) +@test !isless((a=2,b=1), (a=1,b=2)) +@test_throws MethodError isless((a=1,), (x=2,)) + +@test map(-, (x=1, y=2)) == (x=-1, y=-2) +@test map(+, (x=1, y=2), (x=10, y=20)) == (x=11, y=22) +@test_throws ArgumentError map(+, (x=1, y=2), (y=10, x=20)) +@test map(string, (x=1, y=2)) == (x="1", y="2") +@test map(round, (x=1//3, y=Int), (x=3, y=2//3)) == (x=0.333, y=1) + +@test merge((a=1, b=2), (a=10,)) == (a=10, b=2) +@test merge((a=1, b=2), (a=10, z=20)) == (a=10, b=2, z=20) +@test merge((a=1, b=2), (z=20,)) == (a=1, b=2, z=20) +@test merge(NamedTuple(), (a=2, b=1)) == (a=2, b=1) +@test merge((a=2, b=1), NamedTuple()) == (a=2, b=1) +@test merge(NamedTuple(), NamedTuple()) == NamedTuple() +# `merge` should preserve element types +let nt = merge(NamedTuple{(:a,:b),Tuple{Int32,Union{Int32,Void}}}((1,Int32(2))), + NamedTuple{(:a,:c),Tuple{Union{Int8,Void},Float64}}((nothing,1.0))) + @test typeof(nt) == NamedTuple{(:a,:b,:c),Tuple{Union{Int8,Void},Union{Int32,Void},Float64}} + @test repr(nt) == "NamedTuple{(:a, :b, :c),Tuple{Union{Void, Int8},Union{Void, Int32},Float64}}((nothing, 2, 1.0))" +end + +@test keys((a=1, b=2, c=3)) == (:a, :b, :c) +@test keys(NamedTuple()) == () +@test keys((a=1,)) == (:a,) +@test values((a=1, b=2, c=3)) == (1, 2, 3) +@test values(NamedTuple()) == () +@test values((a=1,)) == (1,) +@test haskey((a=1, b=2, c=3), :a) +@test !haskey(NamedTuple(), :a) +@test !haskey((a=1,), :b) +@test get((a=1, b=2, c=3), :a, 0) == 1 +@test get(NamedTuple(), :a, 0) == 0 +@test get((a=1,), :b, 0) == 0 +@test get(()->0, (a=1, b=2, c=3), :a) == 1 +@test get(()->0, NamedTuple(), :a) == 0 +@test get(()->0, (a=1,), :b) == 0 + +# syntax errors + +@test expand(Main, parse("(a=1, 0)")) == Expr(:error, "invalid named tuple element \"0\"") +@test expand(Main, parse("(a=1, f(x))")) == Expr(:error, "invalid named tuple element \"f(x)\"") +@test expand(Main, parse("(a=1,a=2)")) == Expr(:error, "field name \"a\" repeated in named tuple") +@test expand(Main, parse("(a=1,b=0,a=2)")) == Expr(:error, "field name \"a\" repeated in named tuple") +@test expand(Main, parse("(c=1,a=1,b=0,a=2)")) == Expr(:error, "field name \"a\" repeated in named tuple") + +@test parse("(;)") == quote end +@test expand(Main, parse("(1,;2)")) == Expr(:error, "unexpected semicolon in tuple") + +# splatting and implicit naming syntax +#= +let d = [:a=>1, :b=>2, :c=>3] # use an array to preserve order + x = 10 + t = (x=1, y=20) + @test (;d...) == (a=1, b=2, c=3) + @test (;d..., a=10) == (a=10, b=2, c=3) + @test (;d..., x, t.y) == (a=1, b=2, c=3, x=10, y=20) + @test (;d..., :z=>20) == (a=1, b=2, c=3, z=20) + @test (;a=10, d..., :c=>30) == (a=1, b=2, c=30) + @test (;a=0, b=0, z=1, d..., x=4, y=5) == (a=1, b=2, z=1, c=3, x=4, y=5) + @test (;t.x, t.y) == (x=1, y=20) + @test (b=1, t.y) == (b=1, y=20) + y = (w=30, z=40) + @test (;t..., y...) == (x=1, y=20, w=30, z=40) + @test (;t..., y=0, y...) == (x=1, y=0, w=30, z=40) +end + +@test expand(Main, parse("(; f(x))")) == Expr(:error, "invalid named tuple element \"f(x)\"") +@test expand(Main, parse("(;1=0)")) == Expr(:error, "invalid named tuple field name \"1\"") +@test expand(Main, parse("(c=1,a=1,b=0,d.a)")) == Expr(:error, "field name \"a\" repeated in named tuple") +@test expand(Main, parse("(c=1,a=1,b=0,c)")) == Expr(:error, "field name \"c\" repeated in named tuple") +@test expand(Main, parse("(;d.c,c)")) == Expr(:error, "field name \"c\" repeated in named tuple") +=# + +# inference tests + +namedtuple_get_a(x) = x.a +@test Base.return_types(namedtuple_get_a, (NamedTuple,)) == Any[Any] +@test Base.return_types(namedtuple_get_a, (typeof((b=1,a="")),)) == Any[String] + +namedtuple_fieldtype_a(x) = fieldtype(typeof(x), :a) +@test Base.return_types(namedtuple_fieldtype_a, (NamedTuple,)) == Any[Type] +@test Base.return_types(namedtuple_fieldtype_a, (typeof((b=1,a="")),)) == Any[Type{String}] + +namedtuple_nfields(x) = nfields(x) === 0 ? 1 : "" +@test Union{Int,String} <: Base.return_types(namedtuple_nfields, (NamedTuple,))[1] + +function nt_from_abstractly_typed_array() + a = NamedTuple[(a=3,b=5)] + (getfield(a[1],1), getfield(a[1],2)) +end +@test nt_from_abstractly_typed_array() === (3,5) + +let T = NamedTuple{(:a, :b), Tuple{Int64, Union{Float64, Void}}}, nt = T((1, nothing)) + @test nt == (a=1, b=nothing) + @test typeof(nt) == T + @test convert(T, (a=1, b=nothing)) == nt + @test typeof(convert(T, (a=1, b=nothing))) === T +end