diff --git a/base/boot.jl b/base/boot.jl index 178f77d0d1857..bbe95933c2d3f 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 b7658d1ddb6ff..324bfbaf5f84f 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -484,6 +484,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 @@ -725,6 +727,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)) @@ -762,7 +768,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) @@ -1324,6 +1332,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 @@ -1407,6 +1419,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 0000000000000..11bce657e17b7 --- /dev/null +++ b/base/namedtuple.jl @@ -0,0 +1,163 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +@generated function NamedTuple{names}(args...) where names + N = length(names) + if length(args) == N + Expr(:new, :(NamedTuple{names,$(Tuple{args...})}), Any[ :(args[$i]) for i in 1:N ]...) + else + :(throw(ArgumentError("wrong number of arguments to named tuple constructor"))) + end +end + +@generated function NamedTuple{names,T}(args...) where {names, T <: Tuple} + N = length(names) + types = T.parameters + if length(args) == N + Expr(:new, :(NamedTuple{names,T}), Any[ :(convert($(types[i]), args[$i])) for i in 1:N ]...) + else + :(throw(ArgumentError("wrong number of arguments to named tuple constructor"))) + end +end + +NamedTuple() = NamedTuple{()}() + +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) + +function getindex(t::NamedTuple, I::AbstractVector) + idxs = unique( Int[ isa(i, Symbol) ? fieldindex(typeof(t), i) : i for i in I ] ) + names = keys(t)[idxs] + NamedTuple{names}([ getfield( t, i ) for i in idxs ]...) +end + +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}(nt...) +end + +function show(io::IO, t::NamedTuple) + n = nfields(t) + 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 + +function map(f, nt::NamedTuple, nts::NamedTuple...) + # this method makes sure we don't define a map(f) method + _nt_map(f, nt, nts...) +end + +@generated function _nt_map(f, nts::NamedTuple...) + fields = _nt_names(nts[1]) + for x in nts[2:end] + if _nt_names(x) != fields + throw(ArgumentError("All NamedTuple arguments to map must have the same fields in the same order")) + end + end + N = fieldcount(nts[1]) + M = length(nts) + + NT = NamedTuple{fields} + args = Expr[:(f($(Expr[:(getfield(nts[$i], $j)) for i = 1:M]...))) for j = 1:N] + :( $NT($(args...)) ) +end + +# a version of `in` for the older world these generated functions run in +function sym_in(x, itr) + for y in itr + y === x && return true + end + return false +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) +``` +""" +@generated function merge(a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn} + names = Symbol[an...] + for n in bn + if !sym_in(n, an) + push!(names, n) + end + end + vals = map(names) do n + if sym_in(n, bn) + :(getfield(b, $(Expr(:quote, n)))) + else + :(getfield(a, $(Expr(:quote, n)))) + end + end + names = (names...,) + :( NamedTuple{$names}($(vals...)) ) +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 fae06328abab4..e2fc2fd90eca5 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 16ba7ca29029d..0e90605c7a298 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -135,6 +135,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/src/ast.scm b/src/ast.scm index 18d389cc46037..15ca104234a3e 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 fa61ab54eadd6..38ae82d232cc1 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 f413596592675..9ebc3a6d0ac15 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 6e223ab4d6017..9760dde5a2969 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 7faa061f405c3..b58ea8b3ec6a1 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 320c12b969936..dfce92fb663ec 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,37 @@ 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); + 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 +1302,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 +1313,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 +1335,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 +1722,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 +1740,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 +2183,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 4d668dc861222..b1bbb066af6bd 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -878,7 +878,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 @@ -1875,6 +1875,76 @@ (extract (cdr params) (cons p newparams) whereparams))))) (extract (cddr e) '() '())) +(define (named-tuple-expr names values) + `(call (curly (core NamedTuple) (tuple ,@names)) + ,@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 @@ -2202,11 +2272,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) @@ -2811,7 +2886,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)))))))) @@ -2821,7 +2896,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)) @@ -2838,7 +2913,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))))) @@ -2847,7 +2922,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 30e2c26548c70..766ec9649ac24 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 e81cc2d2a6035..e3f5957b8cabf 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 a5d8146fca578..2111834662393 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -1606,6 +1606,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, @@ -1672,6 +1673,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/choosetests.jl b/test/choosetests.jl index dfc70a23d3123..c3521a3465c60 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 4d7e69c2f14c2..7c09a7f70336f 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 0000000000000..4ebfeec9352f0 --- /dev/null +++ b/test/namedtuple.jl @@ -0,0 +1,144 @@ +# 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 (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 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 (x=4, y=5, z=6)[[1,3]] == (x=4, z=6) +@test (x=4, y=5, z=6)[[:z,:y]] == (z=6, y=5) +@test_throws BoundsError (x=4, y=5, z=6)[[1,5]] + +@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 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 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