Skip to content

Commit b4415e8

Browse files
committed
use named tuples as keyword varargs. fixes #4916
also fixes #9972
1 parent cc13293 commit b4415e8

19 files changed

+208
-236
lines changed

NEWS.md

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ New language features
1414
* Named tuples, with the syntax `(a=1, b=2)`. These behave very similarly to tuples,
1515
except components can also be accessed by name using dot syntax `t.a` ([#22194]).
1616

17+
* Keyword argument containers (`kw` in `f(; kw...)`) are now named tuples. Dictionary
18+
functions like `haskey` and indexing can be used on them, and name-value pairs can be
19+
iterated using `pairs(kw)`. `kw` can no longer contain multiple entries for the same
20+
argument name ([#4916]).
21+
1722
* Custom infix operators can now be defined by appending Unicode
1823
combining marks, primes, and sub/superscripts to other operators.
1924
For example, `+̂ₐ″` is parsed as an infix operator with the same

base/boot.jl

+38-2
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,6 @@ export
149149
# constants
150150
nothing, Main
151151

152-
const AnyVector = Array{Any,1}
153-
154152
abstract type Number end
155153
abstract type Real <: Number end
156154
abstract type AbstractFloat <: Real end
@@ -476,4 +474,42 @@ function (g::GeneratedFunctionStub)(@nospecialize args...)
476474
end
477475
end
478476

477+
NamedTuple() = NamedTuple{(),Tuple{}}(())
478+
479+
"""
480+
NamedTuple{names}(args::Tuple)
481+
482+
Construct a named tuple with the given `names` (a tuple of Symbols) from a tuple of values.
483+
"""
484+
NamedTuple{names}(args::Tuple) where {names} = NamedTuple{names,typeof(args)}(args)
485+
486+
using .Intrinsics: sle_int, add_int
487+
488+
macro generated()
489+
return Expr(:generated)
490+
end
491+
492+
function NamedTuple{names,T}(args::T) where {names, T <: Tuple}
493+
if @generated
494+
N = nfields(names)
495+
flds = Array{Any,1}(N)
496+
i = 1
497+
while sle_int(i, N)
498+
arrayset(false, flds, :(getfield(args, $i)), i)
499+
i = add_int(i, 1)
500+
end
501+
Expr(:new, :(NamedTuple{names,T}), flds...)
502+
else
503+
N = nfields(names)
504+
NT = NamedTuple{names,T}
505+
flds = Array{Any,1}(N)
506+
i = 1
507+
while sle_int(i, N)
508+
arrayset(false, flds, getfield(args, i), i)
509+
i = add_int(i, 1)
510+
end
511+
ccall(:jl_new_structv, Any, (Any, Ptr{Void}, UInt32), NT, fields, N)::NT
512+
end
513+
end
514+
479515
ccall(:jl_set_istopmod, Void, (Any, Bool), Core, true)

base/coreimg.jl

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ include("reduce.jl")
5757
include("bitarray.jl")
5858
include("bitset.jl")
5959
include("associative.jl")
60+
include("namedtuple.jl")
6061

6162
# core docsystem
6263
include("docs/core.jl")

base/distributed/cluster.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ function addprocs(manager::ClusterManager; kwargs...)
381381
end
382382

383383
function addprocs_locked(manager::ClusterManager; kwargs...)
384-
params = merge(default_addprocs_params(), AnyDict(kwargs))
384+
params = merge(default_addprocs_params(), AnyDict(pairs(kwargs)))
385385
topology(Symbol(params[:topology]))
386386

387387
if PGRP.topology != :all_to_all

base/distributed/managers.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ end
3636

3737
function check_addprocs_args(kwargs)
3838
valid_kw_names = collect(keys(default_addprocs_params()))
39-
for keyname in kwargs
40-
!(keyname[1] in valid_kw_names) && throw(ArgumentError("Invalid keyword argument $(keyname[1])"))
39+
for keyname in keys(kwargs)
40+
!(keyname in valid_kw_names) && throw(ArgumentError("Invalid keyword argument $(keyname)"))
4141
end
4242
end
4343

base/distributed/messages.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,17 @@ null_id(id) = id == RRID(0, 0)
3838
struct CallMsg{Mode} <: AbstractMsg
3939
f::Function
4040
args::Tuple
41-
kwargs::Array
41+
kwargs
4242
end
4343
struct CallWaitMsg <: AbstractMsg
4444
f::Function
4545
args::Tuple
46-
kwargs::Array
46+
kwargs
4747
end
4848
struct RemoteDoMsg <: AbstractMsg
4949
f::Function
5050
args::Tuple
51-
kwargs::Array
51+
kwargs
5252
end
5353
struct ResultMsg <: AbstractMsg
5454
value::Any

base/essentials.jl

-20
Original file line numberDiff line numberDiff line change
@@ -634,26 +634,6 @@ function vector_any(@nospecialize xs...)
634634
a
635635
end
636636

637-
function as_kwargs(xs::Union{AbstractArray,Associative})
638-
n = length(xs)
639-
to = Vector{Any}(uninitialized, n*2)
640-
i = 1
641-
for (k, v) in xs
642-
to[i] = k::Symbol
643-
to[i+1] = v
644-
i += 2
645-
end
646-
return to
647-
end
648-
649-
function as_kwargs(xs)
650-
to = Vector{Any}()
651-
for (k, v) in xs
652-
ccall(:jl_array_ptr_1d_push2, Void, (Any, Any, Any), to, k::Symbol, v)
653-
end
654-
return to
655-
end
656-
657637
"""
658638
invokelatest(f, args...; kwargs...)
659639

base/methodshow.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ function arg_decl_parts(m::Method)
7575
end
7676

7777
function kwarg_decl(m::Method, kwtype::DataType)
78-
sig = rewrap_unionall(Tuple{kwtype, Core.AnyVector, unwrap_unionall(m.sig).parameters...}, m.sig)
78+
sig = rewrap_unionall(Tuple{kwtype, NamedTuple, unwrap_unionall(m.sig).parameters...}, m.sig)
7979
kwli = ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), kwtype.name.mt, sig, typemax(UInt))
8080
if kwli !== nothing
8181
kwli = kwli::Method

base/namedtuple.jl

+34-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

3+
if module_name(@__MODULE__) === :Base
4+
35
"""
46
NamedTuple{names,T}(args::Tuple)
57
@@ -24,16 +26,6 @@ function NamedTuple{names,T}(args::Tuple) where {names, T <: Tuple}
2426
end
2527
end
2628

27-
"""
28-
NamedTuple{names}(args::Tuple)
29-
30-
Construct a named tuple with the given `names` (a tuple of Symbols) from a tuple of
31-
values.
32-
"""
33-
function NamedTuple{names}(args::Tuple) where {names}
34-
NamedTuple{names,typeof(args)}(args)
35-
end
36-
3729
"""
3830
NamedTuple{names}(nt::NamedTuple)
3931
@@ -50,7 +42,7 @@ function NamedTuple{names}(nt::NamedTuple) where {names}
5042
end
5143
end
5244

53-
NamedTuple() = NamedTuple{(),Tuple{}}(())
45+
end # if Base
5446

5547
length(t::NamedTuple) = nfields(t)
5648
start(t::NamedTuple) = 1
@@ -60,6 +52,8 @@ endof(t::NamedTuple) = nfields(t)
6052
getindex(t::NamedTuple, i::Int) = getfield(t, i)
6153
getindex(t::NamedTuple, i::Symbol) = getfield(t, i)
6254
indexed_next(t::NamedTuple, i::Int, state) = (getfield(t, i), i+1)
55+
isempty(::NamedTuple{()}) = true
56+
isempty(::NamedTuple) = false
6357

6458
convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names,T}) where {names,T} = nt
6559
convert(::Type{NamedTuple{names}}, nt::NamedTuple{names}) where {names} = nt
@@ -213,3 +207,32 @@ values(nt::NamedTuple) = Tuple(nt)
213207
haskey(nt::NamedTuple, key::Union{Integer, Symbol}) = isdefined(nt, key)
214208
get(nt::NamedTuple, key::Union{Integer, Symbol}, default) = haskey(nt, key) ? getfield(nt, key) : default
215209
get(f::Callable, nt::NamedTuple, key::Union{Integer, Symbol}) = haskey(nt, key) ? getfield(nt, key) : f()
210+
211+
@pure function diff_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}})
212+
names = Symbol[]
213+
for n in an
214+
if !sym_in(n, bn)
215+
push!(names, n)
216+
end
217+
end
218+
(names...,)
219+
end
220+
221+
"""
222+
structdiff(a::NamedTuple{an}, b::Union{NamedTuple{bn},Type{NamedTuple{bn}}}) where {an,bn}
223+
224+
Construct a copy of named tuple `a`, except with fields that exist in `b` removed.
225+
`b` can be a named tuple, or a type of the form `NamedTuple{field_names}`.
226+
"""
227+
function structdiff(a::NamedTuple{an}, b::Union{NamedTuple{bn}, Type{NamedTuple{bn}}}) where {an, bn}
228+
if @generated
229+
names = diff_names(an, bn)
230+
types = Tuple{Any[ fieldtype(a, n) for n in names ]...}
231+
vals = Any[ :(getfield(a, $(QuoteNode(n)))) for n in names ]
232+
:( NamedTuple{$names,$types}(($(vals...),)) )
233+
else
234+
names = diff_names(an, bn)
235+
types = Tuple{Any[ fieldtype(typeof(a), n) for n in names ]...}
236+
NamedTuple{names,types}(map(n->getfield(a, n), names))
237+
end
238+
end

base/replutil.jl

+4-5
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,7 @@ function showerror(io::IO, ex::MethodError)
317317
ft = typeof(f)
318318
name = ft.name.mt.name
319319
arg_types_param = arg_types_param[3:end]
320-
temp = ex.args[1]
321-
kwargs = Any[(temp[i*2-1], temp[i*2]) for i in 1:(length(temp) ÷ 2)]
320+
kwargs = ex.args[1]
322321
ex = MethodError(f, ex.args[3:end])
323322
end
324323
if f == Base.convert && length(arg_types_param) == 2 && !is_arg_types
@@ -362,7 +361,7 @@ function showerror(io::IO, ex::MethodError)
362361
end
363362
if !isempty(kwargs)
364363
print(io, "; ")
365-
for (i, (k, v)) in enumerate(kwargs)
364+
for (i, (k, v)) in enumerate(pairs(kwargs))
366365
print(io, k, "=")
367366
show(IOContext(io, :limit => true), v)
368367
i == length(kwargs) || print(io, ", ")
@@ -452,7 +451,7 @@ function showerror_nostdio(err, msg::AbstractString)
452451
ccall(:jl_printf, Cint, (Ptr{Void},Cstring), stderr_stream, "\n")
453452
end
454453

455-
function show_method_candidates(io::IO, ex::MethodError, kwargs::Vector=Any[])
454+
function show_method_candidates(io::IO, ex::MethodError, kwargs::NamedTuple = NamedTuple())
456455
is_arg_types = isa(ex.args, DataType)
457456
arg_types = is_arg_types ? ex.args : typesof(ex.args...)
458457
arg_types_param = Any[arg_types.parameters...]
@@ -582,7 +581,7 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs::Vector=Any[])
582581
if !isempty(kwargs)
583582
unexpected = Symbol[]
584583
if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords))
585-
for (k, v) in kwargs
584+
for (k, v) in pairs(kwargs)
586585
if !(k in kwords)
587586
push!(unexpected, k)
588587
end

doc/src/manual/functions.md

+6-8
Original file line numberDiff line numberDiff line change
@@ -517,14 +517,12 @@ function f(x; y=0, kwargs...)
517517
end
518518
```
519519

520-
Inside `f`, `kwargs` will be a collection of `(key,value)` tuples, where each `key` is a symbol.
521-
Such collections can be passed as keyword arguments using a semicolon in a call, e.g. `f(x, z=1; kwargs...)`.
522-
Dictionaries can also be used for this purpose.
523-
524-
One can also pass `(key,value)` tuples, or any iterable expression (such as a `=>` pair) that
525-
can be assigned to such a tuple, explicitly after a semicolon. For example, `plot(x, y; (:width,2))`
526-
and `plot(x, y; :width => 2)` are equivalent to `plot(x, y, width=2)`. This is useful in situations
527-
where the keyword name is computed at runtime.
520+
Inside `f`, `kwargs` will be a named tuple. Named tuples (as well as dictionaries) can be passed as
521+
keyword arguments using a semicolon in a call, e.g. `f(x, z=1; kwargs...)`.
522+
523+
One can also pass `key => value` expressions after a semicolon. For example, `plot(x, y; :width => 2)`
524+
is equivalent to `plot(x, y, width=2)`. This is useful in situations where the keyword name is computed
525+
at runtime.
528526

529527
The nature of keyword arguments makes it possible to specify the same argument more than once.
530528
For example, in the call `plot(x, y; options..., width=2)` it is possible that the `options` structure

src/array.c

-9
Original file line numberDiff line numberDiff line change
@@ -1139,15 +1139,6 @@ JL_DLLEXPORT void jl_array_ptr_1d_append(jl_array_t *a, jl_array_t *a2)
11391139
}
11401140
}
11411141

1142-
JL_DLLEXPORT void jl_array_ptr_1d_push2(jl_array_t *a, jl_value_t *b, jl_value_t *c)
1143-
{
1144-
assert(jl_typeis(a, jl_array_any_type));
1145-
jl_array_grow_end(a, 2);
1146-
size_t n = jl_array_nrows(a);
1147-
jl_array_ptr_set(a, n - 2, b);
1148-
jl_array_ptr_set(a, n - 1, c);
1149-
}
1150-
11511142
JL_DLLEXPORT jl_value_t *(jl_array_data_owner)(jl_array_t *a)
11521143
{
11531144
return jl_array_data_owner(a);

src/builtins.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -962,12 +962,12 @@ JL_CALLABLE(jl_f_invoke_kwsorter)
962962
jl_nfields(argtypes));
963963
}
964964
if (jl_is_tuple_type(argtypes)) {
965-
// construct a tuple type for invoking a keyword sorter by putting `Vector{Any}`
965+
// construct a tuple type for invoking a keyword sorter by putting the kw container type
966966
// and the type of the function at the front.
967967
size_t i, nt = jl_nparams(argtypes) + 2;
968968
if (nt < jl_page_size/sizeof(jl_value_t*)) {
969969
jl_value_t **types = (jl_value_t**)alloca(nt*sizeof(jl_value_t*));
970-
types[0] = jl_array_any_type; types[1] = jl_typeof(func);
970+
types[0] = (jl_value_t*)jl_namedtuple_type; types[1] = jl_typeof(func);
971971
for(i=2; i < nt; i++)
972972
types[i] = jl_tparam(argtypes,i-2);
973973
argtypes = (jl_value_t*)jl_apply_tuple_type_v(types, nt);

0 commit comments

Comments
 (0)