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 Jan 10, 2020
1 parent 45a0381 commit ec6fb36
Show file tree
Hide file tree
Showing 9 changed files with 53 additions and 6 deletions.
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------
Expand Down
12 changes: 12 additions & 0 deletions base/namedtuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

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 @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/ast.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
9 changes: 4 additions & 5 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down Expand Up @@ -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)
'()
Expand Down
6 changes: 5 additions & 1 deletion src/macroexpand.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
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 @@ -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
4 changes: 4 additions & 0 deletions test/syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit ec6fb36

Please sign in to comment.