From b8d788299ca4a9eefeea751c8bb22b710954f05d Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Sun, 26 Nov 2017 13:39:36 -0500 Subject: [PATCH] use named tuples as keyword varargs. fixes #4916 also fixes #9972 --- NEWS.md | 5 + base/boot.jl | 40 +++- base/coreimg.jl | 1 + base/distributed/cluster.jl | 2 +- base/distributed/managers.jl | 4 +- base/distributed/messages.jl | 6 +- base/essentials.jl | 20 -- base/interactiveutil.jl | 2 +- base/methodshow.jl | 2 +- base/namedtuple.jl | 45 +++- base/replutil.jl | 11 +- doc/src/manual/functions.md | 14 +- src/array.c | 9 - src/builtins.c | 4 +- src/julia-syntax.scm | 238 +++++++------------- src/julia.h | 1 - stdlib/DelimitedFiles/src/DelimitedFiles.jl | 2 +- test/ambiguous.jl | 1 + test/keywordargs.jl | 12 +- test/namedtuple.jl | 19 ++ test/replutil.jl | 8 +- test/syntax.jl | 5 +- 22 files changed, 212 insertions(+), 239 deletions(-) diff --git a/NEWS.md b/NEWS.md index bc5018c3f1c46..efdfadd8c4860 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,6 +14,11 @@ New language features * 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]). + * Keyword argument containers (`kw` in `f(; kw...)`) are now named tuples. Dictionary + functions like `haskey` and indexing can be used on them, and name-value pairs can be + iterated using `pairs(kw)`. `kw` can no longer contain multiple entries for the same + argument name ([#4916]). + * 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 b357c5ec45ad2..097c884db90c4 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -149,8 +149,6 @@ export # constants nothing, Main -const AnyVector = Array{Any,1} - abstract type Number end abstract type Real <: Number end abstract type AbstractFloat <: Real end @@ -476,4 +474,42 @@ function (g::GeneratedFunctionStub)(@nospecialize args...) end end +NamedTuple() = NamedTuple{(),Tuple{}}(()) + +""" + NamedTuple{names}(args::Tuple) + +Construct a named tuple with the given `names` (a tuple of Symbols) from a tuple of values. +""" +NamedTuple{names}(args::Tuple) where {names} = NamedTuple{names,typeof(args)}(args) + +using .Intrinsics: sle_int, add_int + +macro generated() + return Expr(:generated) +end + +function NamedTuple{names,T}(args::T) where {names, T <: Tuple} + if @generated + N = nfields(names) + flds = Array{Any,1}(N) + i = 1 + while sle_int(i, N) + arrayset(false, flds, :(getfield(args, $i)), i) + i = add_int(i, 1) + end + Expr(:new, :(NamedTuple{names,T}), flds...) + else + N = nfields(names) + NT = NamedTuple{names,T} + flds = Array{Any,1}(N) + i = 1 + while sle_int(i, N) + arrayset(false, flds, getfield(args, i), i) + i = add_int(i, 1) + end + ccall(:jl_new_structv, Any, (Any, Ptr{Void}, UInt32), NT, fields, N)::NT + end +end + ccall(:jl_set_istopmod, Void, (Any, Bool), Core, true) diff --git a/base/coreimg.jl b/base/coreimg.jl index 1c365cbf55d5f..10b56c1f8b063 100644 --- a/base/coreimg.jl +++ b/base/coreimg.jl @@ -57,6 +57,7 @@ include("reduce.jl") include("bitarray.jl") include("bitset.jl") include("associative.jl") +include("namedtuple.jl") # core docsystem include("docs/core.jl") diff --git a/base/distributed/cluster.jl b/base/distributed/cluster.jl index 22051fb4d4d5a..76a682691288e 100644 --- a/base/distributed/cluster.jl +++ b/base/distributed/cluster.jl @@ -381,7 +381,7 @@ function addprocs(manager::ClusterManager; kwargs...) end function addprocs_locked(manager::ClusterManager; kwargs...) - params = merge(default_addprocs_params(), AnyDict(kwargs)) + params = merge(default_addprocs_params(), AnyDict(pairs(kwargs))) topology(Symbol(params[:topology])) if PGRP.topology != :all_to_all diff --git a/base/distributed/managers.jl b/base/distributed/managers.jl index 9f35e1a26dced..389307fa03514 100644 --- a/base/distributed/managers.jl +++ b/base/distributed/managers.jl @@ -36,8 +36,8 @@ end function check_addprocs_args(kwargs) valid_kw_names = collect(keys(default_addprocs_params())) - for keyname in kwargs - !(keyname[1] in valid_kw_names) && throw(ArgumentError("Invalid keyword argument $(keyname[1])")) + for keyname in keys(kwargs) + !(keyname in valid_kw_names) && throw(ArgumentError("Invalid keyword argument $(keyname)")) end end diff --git a/base/distributed/messages.jl b/base/distributed/messages.jl index 12f347fa493f0..f78ed8f21b401 100644 --- a/base/distributed/messages.jl +++ b/base/distributed/messages.jl @@ -38,17 +38,17 @@ null_id(id) = id == RRID(0, 0) struct CallMsg{Mode} <: AbstractMsg f::Function args::Tuple - kwargs::Array + kwargs end struct CallWaitMsg <: AbstractMsg f::Function args::Tuple - kwargs::Array + kwargs end struct RemoteDoMsg <: AbstractMsg f::Function args::Tuple - kwargs::Array + kwargs end struct ResultMsg <: AbstractMsg value::Any diff --git a/base/essentials.jl b/base/essentials.jl index a3f3f444ba8d4..07faf9f1ed9ad 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -634,26 +634,6 @@ function vector_any(@nospecialize xs...) a end -function as_kwargs(xs::Union{AbstractArray,Associative}) - n = length(xs) - to = Vector{Any}(uninitialized, n*2) - i = 1 - for (k, v) in xs - to[i] = k::Symbol - to[i+1] = v - i += 2 - end - return to -end - -function as_kwargs(xs) - to = Vector{Any}() - for (k, v) in xs - ccall(:jl_array_ptr_1d_push2, Void, (Any, Any, Any), to, k::Symbol, v) - end - return to -end - """ invokelatest(f, args...; kwargs...) diff --git a/base/interactiveutil.jl b/base/interactiveutil.jl index be6067f7c81a9..fdad979f6b664 100644 --- a/base/interactiveutil.jl +++ b/base/interactiveutil.jl @@ -406,7 +406,7 @@ function gen_call_with_extracted_types(__module__, fcn, ex0) return quote local arg1 = $(esc(args[1])) $(fcn)(Core.kwfunc(arg1), - Tuple{Vector{Any}, Core.Typeof(arg1), + Tuple{NamedTuple, Core.Typeof(arg1), $(typesof)($(map(esc, args[2:end])...)).parameters...}) end elseif ex0.head == :call diff --git a/base/methodshow.jl b/base/methodshow.jl index 64cac4e726797..c10805e5e5684 100644 --- a/base/methodshow.jl +++ b/base/methodshow.jl @@ -75,7 +75,7 @@ function arg_decl_parts(m::Method) end function kwarg_decl(m::Method, kwtype::DataType) - sig = rewrap_unionall(Tuple{kwtype, Core.AnyVector, unwrap_unionall(m.sig).parameters...}, m.sig) + sig = rewrap_unionall(Tuple{kwtype, NamedTuple, unwrap_unionall(m.sig).parameters...}, m.sig) kwli = ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), kwtype.name.mt, sig, typemax(UInt)) if kwli !== nothing kwli = kwli::Method diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 4f224bf47afcf..e2be45251134b 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -1,5 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +if module_name(@__MODULE__) === :Base + """ NamedTuple{names,T}(args::Tuple) @@ -24,16 +26,6 @@ function NamedTuple{names,T}(args::Tuple) where {names, T <: Tuple} end end -""" - NamedTuple{names}(args::Tuple) - -Construct a named tuple with the given `names` (a tuple of Symbols) from a tuple of -values. -""" -function NamedTuple{names}(args::Tuple) where {names} - NamedTuple{names,typeof(args)}(args) -end - """ NamedTuple{names}(nt::NamedTuple) @@ -50,7 +42,7 @@ function NamedTuple{names}(nt::NamedTuple) where {names} end end -NamedTuple() = NamedTuple{(),Tuple{}}(()) +end # if Base length(t::NamedTuple) = nfields(t) start(t::NamedTuple) = 1 @@ -60,6 +52,8 @@ 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) +isempty(::NamedTuple{()}) = true +isempty(::NamedTuple) = false convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names,T}) where {names,T} = nt convert(::Type{NamedTuple{names}}, nt::NamedTuple{names}) where {names} = nt @@ -213,3 +207,32 @@ 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() + +@pure function diff_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) + names = Symbol[] + for n in an + if !sym_in(n, bn) + push!(names, n) + end + end + (names...,) +end + +""" + structdiff(a::NamedTuple{an}, b::Union{NamedTuple{bn},Type{NamedTuple{bn}}}) where {an,bn} + +Construct a copy of named tuple `a`, except with fields that exist in `b` removed. +`b` can be a named tuple, or a type of the form `NamedTuple{field_names}`. +""" +function structdiff(a::NamedTuple{an}, b::Union{NamedTuple{bn}, Type{NamedTuple{bn}}}) where {an, bn} + if @generated + names = diff_names(an, bn) + types = Tuple{Any[ fieldtype(a, n) for n in names ]...} + vals = Any[ :(getfield(a, $(QuoteNode(n)))) for n in names ] + :( NamedTuple{$names,$types}(($(vals...),)) ) + else + names = diff_names(an, bn) + types = Tuple{Any[ fieldtype(typeof(a), n) for n in names ]...} + NamedTuple{names,types}(map(n->getfield(a, n), names)) + end +end diff --git a/base/replutil.jl b/base/replutil.jl index e41da4c82b47f..5e21e526ff37c 100644 --- a/base/replutil.jl +++ b/base/replutil.jl @@ -311,14 +311,13 @@ function showerror(io::IO, ex::MethodError) ft = typeof(f) name = ft.name.mt.name f_is_function = false - kwargs = Any[] + kwargs = NamedTuple() if startswith(string(ft.name.name), "#kw#") f = ex.args[2] ft = typeof(f) name = ft.name.mt.name arg_types_param = arg_types_param[3:end] - temp = ex.args[1] - kwargs = Any[(temp[i*2-1], temp[i*2]) for i in 1:(length(temp) ÷ 2)] + kwargs = ex.args[1] ex = MethodError(f, ex.args[3:end]) end if f == Base.convert && length(arg_types_param) == 2 && !is_arg_types @@ -362,7 +361,7 @@ function showerror(io::IO, ex::MethodError) end if !isempty(kwargs) print(io, "; ") - for (i, (k, v)) in enumerate(kwargs) + for (i, (k, v)) in enumerate(pairs(kwargs)) print(io, k, "=") show(IOContext(io, :limit => true), v) i == length(kwargs) || print(io, ", ") @@ -452,7 +451,7 @@ function showerror_nostdio(err, msg::AbstractString) ccall(:jl_printf, Cint, (Ptr{Void},Cstring), stderr_stream, "\n") end -function show_method_candidates(io::IO, ex::MethodError, kwargs::Vector=Any[]) +function show_method_candidates(io::IO, ex::MethodError, kwargs::NamedTuple = NamedTuple()) is_arg_types = isa(ex.args, DataType) arg_types = is_arg_types ? ex.args : typesof(ex.args...) arg_types_param = Any[arg_types.parameters...] @@ -582,7 +581,7 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs::Vector=Any[]) if !isempty(kwargs) unexpected = Symbol[] if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords)) - for (k, v) in kwargs + for (k, v) in pairs(kwargs) if !(k in kwords) push!(unexpected, k) end diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index 2779d27c8f9a0..72732e34b5897 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -517,14 +517,12 @@ function f(x; y=0, kwargs...) end ``` -Inside `f`, `kwargs` will be a collection of `(key,value)` tuples, where each `key` is a symbol. -Such collections can be passed as keyword arguments using a semicolon in a call, e.g. `f(x, z=1; kwargs...)`. -Dictionaries can also be used for this purpose. - -One can also pass `(key,value)` tuples, or any iterable expression (such as a `=>` pair) that -can be assigned to such a tuple, explicitly after a semicolon. For example, `plot(x, y; (:width,2))` -and `plot(x, y; :width => 2)` are equivalent to `plot(x, y, width=2)`. This is useful in situations -where the keyword name is computed at runtime. +Inside `f`, `kwargs` will be a named tuple. Named tuples (as well as dictionaries) can be passed as +keyword arguments using a semicolon in a call, e.g. `f(x, z=1; kwargs...)`. + +One can also pass `key => value` expressions after a semicolon. For example, `plot(x, y; :width => 2)` +is equivalent to `plot(x, y, width=2)`. This is useful in situations where the keyword name is computed +at runtime. The nature of keyword arguments makes it possible to specify the same argument more than once. For example, in the call `plot(x, y; options..., width=2)` it is possible that the `options` structure diff --git a/src/array.c b/src/array.c index ad460ab4c595b..6135704f8e7dd 100644 --- a/src/array.c +++ b/src/array.c @@ -1139,15 +1139,6 @@ JL_DLLEXPORT void jl_array_ptr_1d_append(jl_array_t *a, jl_array_t *a2) } } -JL_DLLEXPORT void jl_array_ptr_1d_push2(jl_array_t *a, jl_value_t *b, jl_value_t *c) -{ - assert(jl_typeis(a, jl_array_any_type)); - jl_array_grow_end(a, 2); - size_t n = jl_array_nrows(a); - jl_array_ptr_set(a, n - 2, b); - jl_array_ptr_set(a, n - 1, c); -} - JL_DLLEXPORT jl_value_t *(jl_array_data_owner)(jl_array_t *a) { return jl_array_data_owner(a); diff --git a/src/builtins.c b/src/builtins.c index 38ae82d232cc1..44cf30e1f18e0 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -962,12 +962,12 @@ JL_CALLABLE(jl_f_invoke_kwsorter) jl_nfields(argtypes)); } if (jl_is_tuple_type(argtypes)) { - // construct a tuple type for invoking a keyword sorter by putting `Vector{Any}` + // construct a tuple type for invoking a keyword sorter by putting the kw container type // and the type of the function at the front. size_t i, nt = jl_nparams(argtypes) + 2; if (nt < jl_page_size/sizeof(jl_value_t*)) { jl_value_t **types = (jl_value_t**)alloca(nt*sizeof(jl_value_t*)); - types[0] = jl_array_any_type; types[1] = jl_typeof(func); + types[0] = (jl_value_t*)jl_namedtuple_type; types[1] = jl_typeof(func); for(i=2; i < nt; i++) types[i] = jl_tparam(argtypes,i-2); argtypes = (jl_value_t*)jl_apply_tuple_type_v(types, nt); diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 6817d573ff4a3..9671e2b39f4f7 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -410,8 +410,6 @@ (block ,(scopenest (cdr names) (cdr vals) expr))))) -(define empty-vector-any '(call (core AnyVector) 0)) - (define (keywords-method-def-expr name sparams argl body rett) (let* ((kargl (cdar argl)) ;; keyword expressions (= k v) (kargl (map (lambda (a) @@ -464,11 +462,10 @@ (not (any (lambda (p) (eq? (car p) (car s))) positional-sparams))) sparams))) - (let ((kw (gensy)) (i (gensy)) (ii (gensy)) (elt (gensy)) - (rkw (if (null? restkw) '() (symbol (string (car restkw) "...")))) + (let ((kw (gensy)) + (rkw (if (null? restkw) (make-ssavalue) (symbol (string (car restkw) "...")))) (mangled (symbol (string "#" (if name (undot-name name) 'call) "#" - (string (current-julia-module-counter))))) - (tempnames (map (lambda (x) (gensy)) keynames))) + (string (current-julia-module-counter)))))) `(block ;; call with no keyword args ,(method-def-expr- @@ -478,7 +475,7 @@ ,(let (;; call mangled(vals..., [rest_kw,] pargs..., [vararg]...) (ret `(return (call ,mangled ,@(if ordered-defaults keynames vals) - ,@(if (null? restkw) '() (list empty-vector-any)) + ,@(if (null? restkw) '() `((call (core NamedTuple)))) ,@(map arg-name pargl) ,@(if (null? vararg) '() (list `(... ,(arg-name (car vararg))))))))) @@ -513,79 +510,58 @@ `((|::| ;; if there are optional positional args, we need to be able to reference the function name ,(if (any kwarg? pargl) (gensy) UNUSED) - (call (core kwftype) ,ftype)) (:: ,kw (core AnyVector)) ,@pargl ,@vararg) + (call (core kwftype) ,ftype)) (:: ,kw (core NamedTuple)) ,@pargl ,@vararg) `(block - ;; temp variables that will be assigned if their corresponding keywords are passed. - ;; `isdefined` is then used to check whether default values should be evaluated. - ,@(map (lambda (v) `(local ,v)) tempnames) - ,@(if (null? restkw) '() - `((= ,rkw ,empty-vector-any))) - ;; for i = 1:(length(kw)>>1) - (for (= ,i (: 1 (call (top >>) (call (top length) ,kw) 1))) - (block - ;; ii = i*2 - 1 - (= ,ii (call (top sub_int) (call (top mul_int) ,i 2) 1)) - (= ,elt (call (core arrayref) true ,kw ,ii)) - ,(foldl (lambda (kn else) - (let* ((k (car kn)) - (rval0 `(call (core arrayref) true ,kw - (call (top add_int) ,ii 1))) - ;; note: if the "declared" type of a KW arg - ;; includes something from keyword-sparams - ;; then don't assert it here, since those static - ;; parameters don't have values yet. - ;; instead, the type will be picked up when the - ;; underlying method is called. - (rval (if (and (decl? k) - (not (any (lambda (s) - (expr-contains-eq (car s) (caddr k))) - keyword-sparams))) - (let ((T (caddr k))) - `(call (core typeassert) - ,rval0 - ;; work around `ANY` not being a type. if arg type - ;; looks like `ANY`, test whether it is `ANY` at run - ;; time and if so, substitute `Any`. issue #21510 - ,(if (or (eq? T 'ANY) - (and (globalref? T) - (eq? (caddr T) 'ANY))) - `(call (|.| (core Intrinsics) 'select_value) - (call (core ===) ,T (core ANY)) - (core Any) - ,T) - T))) - rval0))) - ;; if kw[ii] == 'k; k_temp = kw[ii+1]::Type; end - `(if (comparison ,elt === (quote ,(cdr kn))) - (= ,(decl-var k) ,rval) - ,else))) - (if (null? restkw) - ;; if no rest kw, give error for unrecognized - `(call (top kwerr) ,kw ,@(map arg-name pargl) - ,@(if (null? vararg) '() - (list `(... ,(arg-name (car vararg)))))) - ;; otherwise add to rest keywords - `(foreigncall 'jl_array_ptr_1d_push (core Void) (call (core svec) Any Any) - 'ccall 2 - ,rkw (tuple ,elt - (call (core arrayref) true ,kw - (call (top add_int) ,ii 1))))) - (map (lambda (k temp) - (cons (if (decl? k) `(,(car k) ,temp ,(caddr k)) temp) - (decl-var k))) - vars tempnames)))) - ;; set keywords that weren't present to their default values - ,(scopenest keynames - (map (lambda (v dflt) `(if (isdefined ,v) - ,v - ,dflt)) - tempnames vals) - `(return (call ,mangled ;; finally, call the core function - ,@keynames - ,@(if (null? restkw) '() (list rkw)) - ,@(map arg-name pargl) - ,@(if (null? vararg) '() - (list `(... ,(arg-name (car vararg)))))))))) + ,(scopenest + keynames + (map (lambda (v dflt) + (let* ((k (decl-var v)) + (rval0 `(call (top getfield) ,kw (quote ,k))) + ;; note: if the "declared" type of a KW arg includes something + ;; from keyword-sparams then don't assert it here, since those + ;; static parameters don't have values yet. instead, the type + ;; will be picked up when the underlying method is called. + (rval (if (and (decl? v) + (not (any (lambda (s) + (expr-contains-eq (car s) (caddr v))) + keyword-sparams))) + (let ((T (caddr v))) + `(call (core typeassert) + ,rval0 + ;; work around `ANY` not being a type. if arg type + ;; looks like `ANY`, test whether it is `ANY` at run + ;; time and if so, substitute `Any`. issue #21510 + ,(if (or (eq? T 'ANY) + (and (globalref? T) + (eq? (caddr T) 'ANY))) + `(call (|.| (core Intrinsics) 'select_value) + (call (core ===) ,T (core ANY)) + (core Any) + ,T) + T))) + rval0))) + `(if (call (top haskey) ,kw (quote ,k)) + ,rval + ,dflt))) + vars vals) + `(block + (= ,rkw ,(if (null? keynames) + kw + `(call (top structdiff) ,kw (curly (core NamedTuple) + (tuple ,@(map quotify keynames)))))) + ,@(if (null? restkw) + `((if (call (top isempty) ,rkw) + (null) + (call (top kwerr) ,kw ,@(map arg-name pargl) + ,@(if (null? vararg) '() + (list `(... ,(arg-name (car vararg)))))))) + '()) + (return (call ,mangled ;; finally, call the core function + ,@keynames + ,@(if (null? restkw) '() (list rkw)) + ,@(map arg-name pargl) + ,@(if (null? vararg) '() + (list `(... ,(arg-name (car vararg))))))))))) ;; return primary function ,(if (not (symbol? name)) '(null) name))))) @@ -1506,95 +1482,36 @@ (reverse a))))) (define (lower-kw-call f args) - (let* ((p (if (has-parameters? args) (car args) '(parameters))) + (let* ((para (if (has-parameters? args) (cdar args) '())) (args (if (has-parameters? args) (cdr args) args))) (let* ((parg-stmts (remove-argument-side-effects `(call ,f ,@args))) (call-ex (car parg-stmts)) (fexpr (cadr call-ex)) - (cargs (cddr call-ex)) - (para-stmts (remove-argument-side-effects p)) - (pkws (cdr (car para-stmts)))) + (cargs (cddr call-ex))) `(block ,.(cdr parg-stmts) - ,.(cdr para-stmts) ,(receive (kws pargs) (separate kwarg? cargs) - (lower-kw-call- fexpr (append! kws pkws) pargs)))))) - -;; lower function call containing keyword arguments -(define (lower-kw-call- fexpr kw0 pa) - - ;; check for keyword arguments syntactically passed more than once - (let ((dups (has-dups (map cadr (filter kwarg? kw0))))) - (if dups - (error (string "keyword argument \"" (car dups) "\" repeated in call to \"" (deparse fexpr) "\"")))) + (lower-kw-call- fexpr (append! kws para) pargs)))))) +(define (lower-kw-call- fexpr kw pa) (define (kwcall-unless-empty f pa kw-container-test kw-container) `(if (call (top isempty) ,kw-container-test) (call ,f ,@pa) (call (call (core kwfunc) ,f) ,kw-container ,f ,@pa))) - (let ((f (if (sym-ref? fexpr) fexpr (make-ssavalue)))) + (let ((f (if (sym-ref? fexpr) fexpr (make-ssavalue))) + (kw-container (make-ssavalue))) `(block ,@(if (eq? f fexpr) '() `((= ,f, fexpr))) - ,(if ;; optimize splatting one existing container, `f(...; kw...)` - (and (length= kw0 1) (vararg? (car kw0))) - (let* ((container (cadr (car kw0))) - (expr_stmts (remove-argument-side-effects `(call _ ,container))) - (container (caddr (car expr_stmts))) - (stmts (cdr expr_stmts))) - `(block - ,@stmts - ,(kwcall-unless-empty f pa container `(call (top as_kwargs) ,container)))) - (let ((container (make-ssavalue))) - (let loop ((kw kw0) - (initial-kw '()) ;; keyword args before any splats - (stmts '()) - (has-kw #f)) ;; whether there are definitely >0 kwargs - (if (null? kw) - (if (null? stmts) - `(call (call (core kwfunc) ,f) (call (top vector_any) ,@(reverse initial-kw)) ,f ,@pa) - `(block - (= ,container (call (top vector_any) ,@(reverse initial-kw))) - ,@(reverse stmts) - ,(if has-kw - `(call (call (core kwfunc) ,f) ,container ,f ,@pa) - (kwcall-unless-empty f pa container container)))) - (let ((arg (car kw))) - (cond ((and (pair? arg) (eq? (car arg) 'parameters)) - (error "more than one semicolon in argument list")) - ((kwarg? arg) - (if (not (symbol? (cadr arg))) - (error (string "keyword argument is not a symbol: \"" - (deparse (cadr arg)) "\""))) - (if (vararg? (caddr arg)) - (error "splicing with \"...\" cannot be used for a keyword argument value")) - (if (null? stmts) - (loop (cdr kw) (list* (caddr arg) `(quote ,(cadr arg)) initial-kw) stmts #t) - (loop (cdr kw) initial-kw - (cons `(foreigncall 'jl_array_ptr_1d_push2 (core Void) (call (core svec) Any Any Any) - 'ccall 3 - ,container - (|::| (quote ,(cadr arg)) (core Symbol)) - ,(caddr arg)) - stmts) - #t))) - (else - (loop (cdr kw) initial-kw - (cons (let* ((k (make-ssavalue)) - (v (make-ssavalue)) - (push-expr `(foreigncall 'jl_array_ptr_1d_push2 (core Void) (call (core svec) Any Any Any) - 'ccall 3 - ,container - (|::| ,k (core Symbol)) - ,v))) - (if (vararg? arg) - `(for (= (tuple ,k ,v) ,(cadr arg)) - ,push-expr) - `(block (= (tuple ,k ,v) ,arg) - ,push-expr))) - stmts) - (or has-kw (not (vararg? arg)))))))))))))) + (= ,kw-container ,(lower-named-tuple kw + (lambda (name) (string "keyword argument \"" name + "\" repeated in call to \"" (deparse fexpr) "\"")) + "keyword argument" + "keyword argument syntax")) + ,(if (every vararg? kw) + (kwcall-unless-empty f pa kw-container kw-container) + `(call (call (core kwfunc) ,f) ,kw-container ,f ,@pa))))) ;; convert e.g. A'*B to Ac_mul_B(A,B) (define (expand-transposed-op e ops) @@ -1924,7 +1841,10 @@ `(call (curly (core NamedTuple) (tuple ,@names)) (tuple ,@values))) -(define (lower-named-tuple lst) +(define (lower-named-tuple lst + (dup-error-fn (lambda (name) (string "field name \"" name "\" repeated in named tuple"))) + (name-str "named tuple field") + (syntax-str "named tuple element")) (let* ((names (apply append (map (lambda (x) (cond #;((symbol? x) (list x)) @@ -1936,7 +1856,7 @@ lst))) (dups (has-dups names))) (if dups - (error (string "field name \"" (car dups) "\" repeated in named tuple")))) + (error (dup-error-fn (car dups))))) (define (to-nt n v) (if (null? n) #f @@ -1956,7 +1876,7 @@ (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)) "\""))) + (error (string "invalid " name-str " name \"" (deparse (cadr el)) "\""))) (loop (cdr L) (cons (cadr el) current-names) (cons (caddr el) current-vals) @@ -1972,13 +1892,13 @@ (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) '() @@ -1988,7 +1908,7 @@ (merge current (cadr el)) `(call (top merge) (call (top NamedTuple)) ,(cadr el)))))) (else - (error (string "invalid named tuple element \"" (deparse el) "\"")))))))) + (error (string "invalid " syntax-str " \"" (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))) @@ -2317,9 +2237,7 @@ 'tuple (lambda (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) + (if (length= e 2) (expand-forms (lower-named-tuple (cdr (cadr e)))) (error "unexpected semicolon in tuple"))) ((any assignment? (cdr e)) diff --git a/src/julia.h b/src/julia.h index 09dfb6356f23d..241bd5b465c5d 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1239,7 +1239,6 @@ JL_DLLEXPORT void jl_array_grow_beg(jl_array_t *a, size_t inc); JL_DLLEXPORT void jl_array_del_beg(jl_array_t *a, size_t dec); JL_DLLEXPORT void jl_array_sizehint(jl_array_t *a, size_t sz); JL_DLLEXPORT void jl_array_ptr_1d_push(jl_array_t *a, jl_value_t *item); -JL_DLLEXPORT void jl_array_ptr_1d_push2(jl_array_t *a, jl_value_t *b, jl_value_t *c); JL_DLLEXPORT void jl_array_ptr_1d_append(jl_array_t *a, jl_array_t *a2); JL_DLLEXPORT jl_value_t *jl_apply_array_type(jl_value_t *type, size_t dim); // property access diff --git a/stdlib/DelimitedFiles/src/DelimitedFiles.jl b/stdlib/DelimitedFiles/src/DelimitedFiles.jl index 89066567aebfd..d5fdb8790832a 100644 --- a/stdlib/DelimitedFiles/src/DelimitedFiles.jl +++ b/stdlib/DelimitedFiles/src/DelimitedFiles.jl @@ -354,7 +354,7 @@ const valid_opt_types = [Bool, Bool, Bool, Bool, Bool, NTuple{2,Integer}, Char, function val_opts(opts) d = Dict{Symbol,Union{Bool,NTuple{2,Integer},Char,Integer}}() - for (opt_name, opt_val) in opts + for (opt_name, opt_val) in pairs(opts) in(opt_name, valid_opts) || throw(ArgumentError("unknown option $opt_name")) opt_typ = valid_opt_types[findfirst(equalto(opt_name), valid_opts)] diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 18ef230090cb8..3e22e49a58053 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -265,6 +265,7 @@ end pop!(need_to_handle_undef_sparam, which(Core.Inference.eltype, Tuple{Type{Tuple{Any}}})) @test_broken need_to_handle_undef_sparam == Set() pop!(need_to_handle_undef_sparam, which(Core.Inference.cat, Tuple{Any, AbstractArray})) + pop!(need_to_handle_undef_sparam, first(methods(Core.Inference.same_names))) @test need_to_handle_undef_sparam == Set() end let need_to_handle_undef_sparam = diff --git a/test/keywordargs.jl b/test/keywordargs.jl index e68c821d40e24..53b5070c97610 100644 --- a/test/keywordargs.jl +++ b/test/keywordargs.jl @@ -188,14 +188,14 @@ function test4974(;kwargs...) end end -@test test4974(a=1) == (2, [(:a, 1)]) +@test test4974(a=1) == (2, (a=1,)) @testset "issue #7704, computed keywords" begin - @test kwf1(1; (:tens, 2)) == 21 + @test kwf1(1; :tens => 2) == 21 let p = (:hundreds, 3), q = (:tens, 1) - @test kwf1(0; p, q) == 310 - @test kwf1(0; q, hundreds=4) == 410 + @test kwf1(0; p[1]=>p[2], q[1]=>q[2]) == 310 + @test kwf1(0; q[1]=>q[2], hundreds=4) == 410 end end @testset "with anonymous functions, issue #2773" begin @@ -304,7 +304,7 @@ end @test f(get_next(), a=get_next(), get_next(), b=get_next(), get_next(), [get_next(), get_next()]...; c=get_next(), - [(:d, get_next()), (:f, get_next())]...) == + (d = get_next(), f = get_next())...) == ((1,3,5,6,7), - Any[(:a,2), (:b,4), (:c,8), (:d,9), (:f,10)]) + (a = 2, b = 4, c = 8, d = 9, f = 10)) end diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 4c0076f3e7b85..01eeb671ba1c4 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -114,6 +114,9 @@ end @test Meta.lower(Main, Meta.parse("(a=1,b=0,a=2)")) == Expr(:error, "field name \"a\" repeated in named tuple") @test Meta.lower(Main, Meta.parse("(c=1,a=1,b=0,a=2)")) == Expr(:error, "field name \"a\" repeated in named tuple") +@test Meta.lower(Main, Meta.parse("(; f(x))")) == Expr(:error, "invalid named tuple element \"f(x)\"") +@test Meta.lower(Main, Meta.parse("(;1=0)")) == Expr(:error, "invalid named tuple field name \"1\"") + @test Meta.parse("(;)") == quote end @test Meta.lower(Main, Meta.parse("(1,;2)")) == Expr(:error, "unexpected semicolon in tuple") @@ -123,6 +126,14 @@ let d = [:a=>1, :b=>2, :c=>3] # use an array to preserve order @test (d..., a=10) == (a=10, b=2, c=3) @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 (a=0, (b=2,a=1)..., c=3) == (a=1, b=2, c=3) + + t = (x=1, y=20) + @test (;d...) == (a=1, b=2, c=3) + @test (;d..., :z=>20) == (a=1, b=2, c=3, z=20) + @test (;a=10, d..., :c=>30) == (a=1, b=2, c=30) + 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 # inference tests @@ -189,3 +200,11 @@ function abstr_nt_22194_3() end abstr_nt_22194_3() @test Base.return_types(abstr_nt_22194_3, ()) == Any[Any] + +@test Base.structdiff((a=1, b=2), (b=3,)) == (a=1,) +@test Base.structdiff((a=1, b=2, z=20), (b=3,)) == (a=1, z=20) +@test Base.structdiff((a=1, b=2, z=20), (b=3, q=20, z=1)) == (a=1,) +@test Base.structdiff((a=1, b=2, z=20), (b=3, q=20, z=1, a=0)) == NamedTuple() +@test Base.structdiff((a=1, b=2, z=20), NamedTuple{(:b,)}) == (a=1, z=20) +@test typeof(Base.structdiff(NamedTuple{(:a, :b), Tuple{Int32, Union{Int32, Void}}}((1, Int32(2))), + (a=0,))) === NamedTuple{(:b,), Tuple{Union{Int32, Void}}} diff --git a/test/replutil.jl b/test/replutil.jl index a8b5723b4697d..9104a52d3f798 100644 --- a/test/replutil.jl +++ b/test/replutil.jl @@ -148,12 +148,12 @@ end c7line = @__LINE__() + 1 method_c7(a, b; kargs...) = a -Base.show_method_candidates(buf, MethodError(method_c7, (1, 1)), [(:x, 1), (:y, 2)]) +Base.show_method_candidates(buf, MethodError(method_c7, (1, 1)), (x = 1, y = 2)) test_have_color(buf, "\e[0m\nClosest candidates are:\n method_c7(::Any, ::Any; kargs...)$cfile$c7line\e[0m", "\nClosest candidates are:\n method_c7(::Any, ::Any; kargs...)$cfile$c7line") c8line = @__LINE__() + 1 method_c8(a, b; y=1, w=1) = a -Base.show_method_candidates(buf, MethodError(method_c8, (1, 1)), [(:x, 1), (:y, 2), (:z, 1), (:w, 1)]) +Base.show_method_candidates(buf, MethodError(method_c8, (1, 1)), (x = 1, y = 2, z = 1, w = 1)) test_have_color(buf, "\e[0m\nClosest candidates are:\n method_c8(::Any, ::Any; y, w)$cfile$c8line\e[1m\e[31m got unsupported keyword arguments \"x\", \"z\"\e[0m\e[0m", "\nClosest candidates are:\n method_c8(::Any, ::Any; y, w)$cfile$c8line got unsupported keyword arguments \"x\", \"z\"") @@ -161,7 +161,7 @@ ac15639line = @__LINE__ addConstraint_15639(c::Int32) = c addConstraint_15639(c::Int64; uncset=nothing) = addConstraint_15639(Int32(c), uncset=uncset) -Base.show_method_candidates(buf, MethodError(addConstraint_15639, (Int32(1),)), [(:uncset, nothing)]) +Base.show_method_candidates(buf, MethodError(addConstraint_15639, (Int32(1),)), (uncset = nothing,)) test_have_color(buf, "\e[0m\nClosest candidates are:\n addConstraint_15639(::Int32)$cfile$(ac15639line + 1)\e[1m\e[31m got unsupported keyword argument \"uncset\"\e[0m\n addConstraint_15639(\e[1m\e[31m::Int64\e[0m; uncset)$cfile$(ac15639line + 2)\e[0m", "\nClosest candidates are:\n addConstraint_15639(::Int32)$cfile$(ac15639line + 1) got unsupported keyword argument \"uncset\"\n addConstraint_15639(!Matched::Int64; uncset)$cfile$(ac15639line + 2)") @@ -536,7 +536,7 @@ foo_9965(x::Int) = 2x end @test typeof(ex) == MethodError io = IOBuffer() - Base.show_method_candidates(io, ex, [(:w,true)]) + Base.show_method_candidates(io, ex, (w = true,)) @test contains(String(take!(io)), "got unsupported keyword argument \"w\"") end diff --git a/test/syntax.jl b/test/syntax.jl index 3dcb9d12e3a90..73a94495f5a47 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -827,7 +827,7 @@ let f = function (x; kw...) g = function (x; a = 2) return (x, a) end - @test f(1) == (1, Any[]) + @test f(1) == (1, NamedTuple()) @test g(1) == (1, 2) end @@ -1226,3 +1226,6 @@ end @test raw"x \\\" y" == "x \\\" y" @test raw"x \\\ y" == "x \\\\\\ y" end + +# issue #9972 +@test Meta.lower(@__MODULE__, :(f(;3))) == Expr(:error, "invalid keyword argument syntax \"3\"")