diff --git a/NEWS.md b/NEWS.md index 242d0df86f115..b2aa3420c9364 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,10 @@ New language features * Local variables can be tested for being defined using the new `@isdefined variable` macro ([#22281]). + * Destructuring in function arguments: when an expression such as `(x, y)` is used as + a function argument name, the argument is unpacked into local variables `x` and `y` + as in the assignment `(x, y) = arg` ([#6614]). + Language changes ---------------- diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index a5d28296daed5..eaee1c912cf23 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -267,6 +267,25 @@ end This has the exact same effect as the previous definition of `foo`. +## Argument destructuring + +The destructuring feature can also be used within a function argument. +If a function argument name is written as a tuple (e.g. `(x, y)`) instead of just +a symbol, then an assignment `(x, y) = argument` will be inserted for you: + +```julia +julia> minmax(x, y) = (y < x) ? (y, x) : (x, y) + +julia> range((min, max)) = max - min + +julia> range(minmax(10, 2)) +8 +``` + +Notice the extra set of parentheses in the definition of `range`. +Without those, `range` would be a two-argument function, and this example would +not work. + ## Varargs Functions It is often convenient to be able to write functions taking an arbitrary number of arguments. diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 7b3321c59e8d0..d3f10feb07f28 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -993,6 +993,32 @@ (loop (cadr ex) (append! (reverse (cddr ex)) vars)) `(where ,ex ,.(reverse! vars))))) +(define (lower-destructuring-args argl) + (define (check-lhs a) + (if (expr-contains-p (lambda (e) (or (decl? e) (assignment? e) (kwarg? e))) + a) + (error (string "invalid argument destructuring syntax \"" (deparse a) "\"")) + a)) + (define (transform-arg a) + (cond ((and (pair? a) (eq? (car a) 'tuple)) + (let ((a2 (gensy))) + (cons a2 `(local (= ,(check-lhs a) ,a2))))) + ((or (and (decl? a) (length= a 3)) (kwarg? a)) + (let ((x (transform-arg (cadr a)))) + (cons `(,(car a) ,(car x) ,(caddr a)) (cdr x)))) + ((vararg? a) + (let ((x (transform-arg (cadr a)))) + (cons `(... ,(car x)) (cdr x)))) + (else (cons a #f)))) + (let loop ((argl argl) + (newa '()) + (stmts '())) + (if (null? argl) + (cons (reverse newa) (reverse stmts)) + (let ((a (transform-arg (car argl)))) + (loop (cdr argl) (cons (car a) newa) + (if (cdr a) (cons (cdr a) stmts) stmts)))))) + (define (expand-function-def- e) (let* ((name (cadr e)) (where (if (and (pair? name) (eq? (car name) 'where)) @@ -1040,6 +1066,9 @@ (farg (if (decl? name) (adj-decl name) `(|::| |#self#| (call (core Typeof) ,name)))) + (argl-stmts (lower-destructuring-args argl)) + (argl (car argl-stmts)) + (body (insert-after-meta body (cdr argl-stmts))) (argl (fix-arglist (arglist-unshift argl farg) (and (not (any kwarg? argl)) (not (and (pair? argl) diff --git a/test/core.jl b/test/core.jl index 9f528d6aef7cb..bc0d8c0d6328d 100644 --- a/test/core.jl +++ b/test/core.jl @@ -5508,3 +5508,29 @@ for U in unboxedunions end end # module UnionOptimizations + +# issue #6614, argument destructuring +f6614((x, y)) = [x, y] +@test f6614((4, 3)) == [4, 3] +g6614((x, y), (z,), (a, b)) = (x,y,z,a,b) +@test g6614((1, 2), (3,), (4, 5)) === (1,2,3,4,5) +@test_throws MethodError g6614(1, 2) +@test_throws MethodError g6614((1, 2), (3,)) +@test_throws BoundsError g6614((1, 2), (3,), (1,)) +h6614((x, y) = (5, 6)) = (y, x) +@test h6614() == (6, 5) +@test h6614((4, 5)) == (5, 4) +ff6614((x, y)::Tuple{Int, String}) = (x, y) +@test ff6614((1, "")) == (1, "") +@test_throws MethodError ff6614((1, 1)) +gg6614((x, y)::Tuple{Int, String} = (2, " ")) = (x, y) +@test gg6614() == (2, " ") +function hh6614() + x, y = 1, 2 + function g((x,y)) + # make sure x and y are local + end + g((4,5)) + x, y +end +@test hh6614() == (1, 2)