From ec6fb3685b6d19dc650ffa04b4090e672ecb55aa Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Thu, 9 Jan 2020 17:49:50 -0500 Subject: [PATCH] add implicit named tuple and keyword argument names - `x` implies `x=x` - `a.b` implies `b=a.b` closes #29333 --- NEWS.md | 4 ++++ base/namedtuple.jl | 12 ++++++++++++ doc/src/manual/functions.md | 4 ++++ src/ast.scm | 4 ++++ src/julia-syntax.scm | 9 ++++----- src/macroexpand.scm | 6 +++++- test/keywordargs.jl | 5 +++++ test/namedtuple.jl | 11 +++++++++++ test/syntax.jl | 4 ++++ 9 files changed, 53 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 15a35f09a0a48..1815678d67cb0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,10 @@ Julia v1.5 Release Notes New language features --------------------- +* Passing an identifier `x` by itself as a keyword argument or named tuple element + is equivalent to `x=x`, implicitly using the name of the variable as the keyword + or named tuple field name. + Similarly, passing an `a.b` expression uses `b` as the keyword or field name ([#29333]). Language changes ---------------- diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 965466c71489e..97ef56499078a 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -58,6 +58,18 @@ julia> keys = (:a, :b, :c); values = (1, 2, 3); julia> (; zip(keys, values)...) (a = 1, b = 2, c = 3) ``` + +As in keyword arguments, identifiers and dot expressions imply names: + +```jldoctest +julia> x = 0 +0 + +julia> t = (; x) +(x = 0,) + +julia> (; t.x) +(x = 0,) """ Core.NamedTuple diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index 47da94ec9b265..3c4b1d6aaf1ec 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -609,6 +609,10 @@ One can also pass `key => value` expressions after a semicolon. For example, `pl is equivalent to `plot(x, y, width=2)`. This is useful in situations where the keyword name is computed at runtime. +When a bare identifier or dot expression occurs after a semicolon, the keyword argument name is +implied by the identifier or field name. For example `plot(x, y; width)` is equivalent to +`plot(x, y; width=width)` and `plot(x, y; options.width)` is equivalent to `plot(x, y; width=options.width)`. + 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 also contains a value for `width`. In such a case the rightmost occurrence takes precedence; in diff --git a/src/ast.scm b/src/ast.scm index 3ca6e652cd626..a76a3af57dd47 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -280,6 +280,10 @@ (cadr e) e)) +(define (quoted-sym? e) + (and (length= e 2) (memq (car e) '(quote inert)) + (symbol? (cadr e)))) + (define (lam:args x) (cadr x)) (define (lam:vars x) (llist-vars (lam:args x))) (define (lam:vinfo x) (caddr x)) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 7ae508c59d601..709cd5bd9fd5e 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1756,10 +1756,10 @@ (syntax-str "named tuple element")) (let* ((names (apply append (map (lambda (x) - (cond #;((symbol? x) (list 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) '|.|)) + ((and (length= x 3) (eq? (car x) '|.|)) (list (cadr (caddr x)))) (else '()))) lst))) @@ -1797,18 +1797,17 @@ (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 + ((and (length= el 3) (eq? (car el) '|.|) ;; a.x => x = a.x + (quoted-sym? (caddr el))) (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) '() diff --git a/src/macroexpand.scm b/src/macroexpand.scm index cffae76a680b1..97606237ee7a9 100644 --- a/src/macroexpand.scm +++ b/src/macroexpand.scm @@ -361,7 +361,11 @@ ((parameters) (cons 'parameters (map (lambda (x) - (resolve-expansion-vars- x env m parent-scope #f)) + ;; `x` by itself after ; means `x=x` + (let ((x (if (and (not inarg) (symbol? x)) + `(kw ,x ,x) + x))) + (resolve-expansion-vars- x env m parent-scope #f))) (cdr e)))) ((= function) diff --git a/test/keywordargs.jl b/test/keywordargs.jl index e4bb848ffe758..b2ae129ae0e01 100644 --- a/test/keywordargs.jl +++ b/test/keywordargs.jl @@ -7,6 +7,11 @@ kwf1(ones; tens=0, hundreds=0) = ones + 10*tens + 100*hundreds @test kwf1(2, tens=6) == 62 @test kwf1(1, hundreds=2, tens=7) == 271 @test kwf1(3, tens=7, hundreds=2) == 273 + let tens = 2, hundreds = 4 + @test kwf1(8; tens, hundreds) == 428 + nt = (hundreds = 5,) + @test kwf1(7; nt.hundreds) == 507 + end @test_throws MethodError kwf1() # no method, too few args @test_throws MethodError kwf1(1, z=0) # unsupported keyword diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 2a865973a3b66..743a2b16ba33f 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -275,3 +275,14 @@ let nt0 = NamedTuple(), nt1 = (a=33,), nt2 = (a=0, b=:v) @test Base.setindex(nt1, "value", :a) == (a="value",) @test Base.setindex(nt1, "value", :a) isa NamedTuple{(:a,),<:Tuple{AbstractString}} end + +# issue #29333, implicit names +let x = 1, y = 2 + @test (;y) === (y = 2,) + a = (; x, y) + @test a === (x=1, y=2) + @test (; a.y, a.x) === (y=2, x=1) + y = 3 + @test Meta.lower(Main, Meta.parse("(; a.y, y)")) == Expr(:error, "field name \"y\" repeated in named tuple") + @test (; a.y, x) === (y=2, x=1) +end diff --git a/test/syntax.jl b/test/syntax.jl index 0a2c44c5ef936..c62b3112d6aa1 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -1971,3 +1971,7 @@ end # issue #33987 f33987(args::(Vararg{Any, N} where N); kwargs...) = args @test f33987(1,2,3) === (1,2,3) + +macro id_for_kwarg(x); x; end +accepts__kwarg(;z1) = z1 +@test (@id_for_kwarg let z1 = 41; accepts__kwarg(; z1); end) == 41