Skip to content

Commit 7ebcab5

Browse files
JeffBezansonmfasi
authored andcommitted
fix JuliaLang#17785, process all keyword args left-to-right
Whatever comes later, whether splatted or not, takes precedence.
1 parent c724db6 commit 7ebcab5

File tree

4 files changed

+75
-43
lines changed

4 files changed

+75
-43
lines changed

NEWS.md

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ This section lists changes that do not have deprecation warnings.
2121

2222
* Operations between `Float16` and `Integers` now return `Float16` instead of `Float32`. ([#17261])
2323

24+
* Keyword arguments are processed left-to-right: if the same keyword is specified more than
25+
once, the rightmost occurrence takes precedence ([#17785]).
26+
2427
Library improvements
2528
--------------------
2629

@@ -616,6 +619,7 @@ Language tooling improvements
616619
[#17037]: https://github.com/JuliaLang/julia/issues/17037
617620
[#17075]: https://github.com/JuliaLang/julia/issues/17075
618621
[#17132]: https://github.com/JuliaLang/julia/issues/17132
622+
[#17261]: https://github.com/JuliaLang/julia/issues/17261
619623
[#17266]: https://github.com/JuliaLang/julia/issues/17266
620624
[#17300]: https://github.com/JuliaLang/julia/issues/17300
621625
[#17323]: https://github.com/JuliaLang/julia/issues/17323
@@ -626,3 +630,4 @@ Language tooling improvements
626630
[#17510]: https://github.com/JuliaLang/julia/issues/17510
627631
[#17546]: https://github.com/JuliaLang/julia/issues/17546
628632
[#17668]: https://github.com/JuliaLang/julia/issues/17668
633+
[#17785]: https://github.com/JuliaLang/julia/issues/17785

doc/manual/functions.rst

+7
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,13 @@ tuple, explicitly after a semicolon. For example, ``plot(x, y;
510510
``plot(x, y, width=2)``. This is useful in situations where the
511511
keyword name is computed at runtime.
512512

513+
The nature of keyword arguments makes it possible to specify the same
514+
argument more than once. For example, in the call
515+
``plot(x, y; options..., width=2)`` it is possible that the ``options``
516+
structure also contains a value for ``width``. In such a case the
517+
rightmost occurrence takes precedence; in this example, ``width``
518+
is certain to have the value ``2``.
519+
513520
.. _man-evaluation-scope-default-values:
514521

515522
Evaluation Scope of Default Values

src/julia-syntax.scm

+54-43
Original file line numberDiff line numberDiff line change
@@ -1358,49 +1358,60 @@
13581358

13591359
;; lower function call containing keyword arguments
13601360
(define (lower-kw-call f kw pa)
1361-
(if (any (lambda (x) (and (pair? x) (eq? (car x) 'parameters)))
1362-
kw)
1363-
(error "more than one semicolon in argument list"))
1364-
(receive
1365-
(keys restkeys) (separate kwarg? kw)
1366-
(let ((keyargs (apply append
1367-
(map (lambda (a)
1368-
(if (not (symbol? (cadr a)))
1369-
(error (string "keyword argument is not a symbol: \""
1370-
(deparse (cadr a)) "\"")))
1371-
(if (vararg? (caddr a))
1372-
(error "splicing with \"...\" cannot be used for a keyword argument value"))
1373-
`((quote ,(cadr a)) ,(caddr a)))
1374-
keys))))
1375-
(if (null? restkeys)
1376-
`(call (call (core kwfunc) ,f) (call (top vector_any) ,@keyargs) ,f ,@pa)
1377-
(let ((container (make-ssavalue)))
1378-
`(block
1379-
(= ,container (call (top vector_any) ,@keyargs))
1380-
,@(map (lambda (rk)
1381-
(let* ((k (make-ssavalue))
1382-
(v (make-ssavalue))
1383-
(push-expr `(ccall 'jl_array_ptr_1d_push2 Void
1384-
(tuple Any Any Any)
1385-
,container
1386-
(|::| ,k (core Symbol))
1387-
,v)))
1388-
(if (vararg? rk)
1389-
`(for (= (tuple ,k ,v) ,(cadr rk))
1390-
,push-expr)
1391-
`(block (= (tuple ,k ,v) ,rk)
1392-
,push-expr))))
1393-
restkeys)
1394-
,(if (not (null? keys))
1395-
`(call (call (core kwfunc) ,f) ,container ,f ,@pa)
1396-
(let* ((expr_stmts (remove-argument-side-effects `(call ,f ,@pa)))
1397-
(pa (cddr (car expr_stmts)))
1398-
(stmts (cdr expr_stmts)))
1399-
`(block
1400-
,@stmts
1401-
(if (call (top isempty) ,container)
1402-
(call ,f ,@pa)
1403-
(call (call (core kwfunc) ,f) ,container ,f ,@pa)))))))))))
1361+
(let ((container (make-ssavalue)))
1362+
(let loop ((kw kw)
1363+
(initial-kw '()) ;; keyword args before any splats
1364+
(stmts '())
1365+
(has-kw #f)) ;; whether there are definitely >0 kwargs
1366+
(if (null? kw)
1367+
(if (null? stmts)
1368+
`(call (call (core kwfunc) ,f) (call (top vector_any) ,@(reverse initial-kw)) ,f ,@pa)
1369+
`(block
1370+
(= ,container (call (top vector_any) ,@(reverse initial-kw)))
1371+
,@(reverse stmts)
1372+
,(if has-kw
1373+
`(call (call (core kwfunc) ,f) ,container ,f ,@pa)
1374+
(let* ((expr_stmts (remove-argument-side-effects `(call ,f ,@pa)))
1375+
(pa (cddr (car expr_stmts)))
1376+
(stmts (cdr expr_stmts)))
1377+
`(block
1378+
,@stmts
1379+
(if (call (top isempty) ,container)
1380+
(call ,f ,@pa)
1381+
(call (call (core kwfunc) ,f) ,container ,f ,@pa)))))))
1382+
(let ((arg (car kw)))
1383+
(cond ((and (pair? arg) (eq? (car arg) 'parameters))
1384+
(error "more than one semicolon in argument list"))
1385+
((kwarg? arg)
1386+
(if (not (symbol? (cadr arg)))
1387+
(error (string "keyword argument is not a symbol: \""
1388+
(deparse (cadr arg)) "\"")))
1389+
(if (vararg? (caddr arg))
1390+
(error "splicing with \"...\" cannot be used for a keyword argument value"))
1391+
(if (null? stmts)
1392+
(loop (cdr kw) (list* (caddr arg) `(quote ,(cadr arg)) initial-kw) stmts #t)
1393+
(loop (cdr kw) initial-kw
1394+
(cons `(ccall 'jl_array_ptr_1d_push2 Void (tuple Any Any Any)
1395+
,container
1396+
(|::| (quote ,(cadr arg)) (core Symbol))
1397+
,(caddr arg))
1398+
stmts)
1399+
#t)))
1400+
(else
1401+
(loop (cdr kw) initial-kw
1402+
(cons (let* ((k (make-ssavalue))
1403+
(v (make-ssavalue))
1404+
(push-expr `(ccall 'jl_array_ptr_1d_push2 Void (tuple Any Any Any)
1405+
,container
1406+
(|::| ,k (core Symbol))
1407+
,v)))
1408+
(if (vararg? arg)
1409+
`(for (= (tuple ,k ,v) ,(cadr arg))
1410+
,push-expr)
1411+
`(block (= (tuple ,k ,v) ,arg)
1412+
,push-expr)))
1413+
stmts)
1414+
(or has-kw (not (vararg? arg)))))))))))
14041415

14051416
;; convert e.g. A'*B to Ac_mul_B(A,B)
14061417
(define (expand-transposed-op e ops)

test/keywordargs.jl

+9
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,12 @@ end
218218
@test f9948(x=5) == 5
219219
@test_throws UndefVarError f9948()
220220
@test getx9948() == 3
221+
222+
# issue #17785 - handle all sources of kwargs left-to-right
223+
g17785(; a=1, b=2) = (a, b)
224+
let opts = (:a=>3, :b=>4)
225+
@test g17785(; a=5, opts...) == (3, 4)
226+
@test g17785(; opts..., a=5) == (5, 4)
227+
@test g17785(; opts..., a=5, b=6) == (5, 6)
228+
@test g17785(; b=0, opts..., a=5) == (5, 4)
229+
end

0 commit comments

Comments
 (0)