From 8617860b2cf3604b1ff365fa5f3f5d00f3ca47cc Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Mon, 23 Mar 2020 17:50:14 -0400 Subject: [PATCH] add implicit named tuple and keyword argument names (#34331) - `x` implies `x=x` - `a.b` implies `b=a.b` closes #29333 --- NEWS.md | 5 +++++ base/namedtuple.jl | 16 ++++++++++++++++ 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 | 3 +++ 9 files changed, 57 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5f00cd97877f6..936a7c932c7ed 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,11 @@ New language features macros and matrix constructors, which are whitespace sensitive, because expressions like `[a ±b]` now get parsed as `[a ±(b)]` instead of `[±(a, b)]`. ([#34200]) +* 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]). + * Packages can now provide custom hints to help users resolve errors by using the `register_error_hint` function. Packages that define custom exception types can support hints by calling `show_error_hints` from their `showerror` method. ([#35094]) diff --git a/base/namedtuple.jl b/base/namedtuple.jl index bf8e26b78cab8..38765a5a15b21 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -60,6 +60,22 @@ 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,) +``` + +!!! compat "Julia 1.5" + Implicit names from identifiers and dot expressions are available as of Julia 1.5. """ Core.NamedTuple diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index ccbd9c21d70f2..0132ada8a444f 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -614,6 +614,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 c0c7bc14dff40..663e845a309a6 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -283,6 +283,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 991a2b5b9614d..c644e409723e6 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1760,10 +1760,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))) @@ -1801,18 +1801,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 a1371bb0c2e0a..12b1399bf1a22 100644 --- a/src/macroexpand.scm +++ b/src/macroexpand.scm @@ -360,7 +360,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 aaf8a6d237997..907d0320c1fef 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -288,3 +288,14 @@ end @test_throws LoadError include_string(Main, "@NamedTuple{a::Int, b, 3}") @test_throws LoadError include_string(Main, "@NamedTuple(a::Int, b)") 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 bc46f96dd16b3..8cf82e361c8e8 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -1994,6 +1994,9 @@ end @test i0xb23hG() == 2 @test i0xb23hG(x=10) == 10 +accepts__kwarg(;z1) = z1 +@test (@id_for_kwarg let z1 = 41; accepts__kwarg(; z1); end) == 41 + @test @eval let (z,)->begin $(Expr(:inbounds, true))