Skip to content

Commit

Permalink
add implicit named tuple and keyword argument names
Browse files Browse the repository at this point in the history
- `x` implies `x=x`
- `a.b` implies `b=a.b`

closes #29333
  • Loading branch information
JeffBezanson committed Mar 19, 2020
1 parent d33c5a5 commit cf34a11
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 6 deletions.
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]).

Language changes
----------------

Expand Down
16 changes: 16 additions & 0 deletions base/namedtuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions doc/src/manual/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/ast.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
9 changes: 4 additions & 5 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -1758,10 +1758,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)))
Expand Down Expand Up @@ -1799,18 +1799,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)
'()
Expand Down
6 changes: 5 additions & 1 deletion src/macroexpand.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions test/keywordargs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions test/namedtuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions test/syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down

0 comments on commit cf34a11

Please sign in to comment.