diff --git a/NEWS.md b/NEWS.md index cba1367941640..d0af55c0f3a4a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -136,6 +136,8 @@ Language changes backslashes and the end of the literal while 2n+1 backslashes followed by a quote encodes n backslashes followed by a quote character ([#22926]). + * The syntax `(x...)` for constructing a tuple is deprecated; use `(x...,)` instead (#24452). + Breaking changes ---------------- diff --git a/base/abstractarraymath.jl b/base/abstractarraymath.jl index fb978e2c77002..5d8cfc5607ad8 100644 --- a/base/abstractarraymath.jl +++ b/base/abstractarraymath.jl @@ -401,7 +401,7 @@ function repeat(A::AbstractArray; end rep_kw2tup(n::Integer) = (n,) -rep_kw2tup(v::AbstractArray{<:Integer}) = (v...) +rep_kw2tup(v::AbstractArray{<:Integer}) = (v...,) rep_kw2tup(t::Tuple) = t rep_shapes(A, i, o) = _rshps((), (), size(A), i, o) diff --git a/base/dates/io.jl b/base/dates/io.jl index fdff3d9204386..cd29bc7dcddff 100644 --- a/base/dates/io.jl +++ b/base/dates/io.jl @@ -363,7 +363,7 @@ function DateFormat(f::AbstractString, locale::DateLocale=ENGLISH) push!(tokens, Delim(length(tran) == 1 ? first(tran) : tran)) end - tokens_tuple = (tokens...) + tokens_tuple = (tokens...,) return DateFormat{Symbol(f),typeof(tokens_tuple)}(tokens_tuple, locale) end diff --git a/base/libgit2/gitcredential.jl b/base/libgit2/gitcredential.jl index 5c38941341f8d..99f6737fd639d 100644 --- a/base/libgit2/gitcredential.jl +++ b/base/libgit2/gitcredential.jl @@ -173,7 +173,7 @@ function Base.parse(::Type{GitCredentialHelper}, helper::AbstractString) cmd_str = "git credential-$helper" end - GitCredentialHelper(`$(Base.shell_split(cmd_str)...)`) + GitCredentialHelper(`$(Base.shell_split(cmd_str))`) end function Base.:(==)(a::GitCredentialHelper, b::GitCredentialHelper) diff --git a/base/linalg/rowvector.jl b/base/linalg/rowvector.jl index cc59066a0670b..1c5472b1faf0a 100644 --- a/base/linalg/rowvector.jl +++ b/base/linalg/rowvector.jl @@ -124,7 +124,7 @@ IndexStyle(::Type{<:RowVector}) = IndexLinear() # helper function for below @inline to_vec(rowvec::RowVector) = map(transpose, transpose(rowvec)) @inline to_vec(x::Number) = x -@inline to_vecs(rowvecs...) = (map(to_vec, rowvecs)...) +@inline to_vecs(rowvecs...) = (map(to_vec, rowvecs)...,) # map: Preserve the RowVector by un-wrapping and re-wrapping, but note that `f` # expects to operate within the transposed domain, so to_vec transposes the elements diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 598dd36ff7391..8df892a1e3fe4 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -417,7 +417,7 @@ index_ndims() = () # combined dimensionality of all indices # rather than returning N, it returns an NTuple{N,Bool} so the result is inferrable -@inline index_dimsum(i1, I...) = (index_dimsum(I...)...) +@inline index_dimsum(i1, I...) = (index_dimsum(I...)...,) @inline index_dimsum(::Colon, I...) = (true, index_dimsum(I...)...) @inline index_dimsum(::AbstractArray{Bool}, I...) = (true, index_dimsum(I...)...) @inline function index_dimsum(::AbstractArray{<:Any,N}, I...) where N diff --git a/base/subarray.jl b/base/subarray.jl index 608a2395d0963..1e62e6abb3f75 100644 --- a/base/subarray.jl +++ b/base/subarray.jl @@ -255,7 +255,7 @@ strides(V::SubArray) = substrides(V.parent, V.indexes) substrides(parent, I::Tuple) = substrides(1, parent, 1, I) substrides(s, parent, dim, ::Tuple{}) = () -substrides(s, parent, dim, I::Tuple{ScalarIndex, Vararg{Any}}) = (substrides(s*size(parent, dim), parent, dim+1, tail(I))...) +substrides(s, parent, dim, I::Tuple{ScalarIndex, Vararg{Any}}) = (substrides(s*size(parent, dim), parent, dim+1, tail(I))...,) substrides(s, parent, dim, I::Tuple{Slice, Vararg{Any}}) = (s, substrides(s*size(parent, dim), parent, dim+1, tail(I))...) substrides(s, parent, dim, I::Tuple{AbstractRange, Vararg{Any}}) = (s*step(I[1]), substrides(s*size(parent, dim), parent, dim+1, tail(I))...) substrides(s, parent, dim, I::Tuple{Any, Vararg{Any}}) = throw(ArgumentError("strides is invalid for SubArrays with indices of type $(typeof(I[1]))")) diff --git a/base/tuple.jl b/base/tuple.jl index 72f13b1b82aed..d211cf16028ce 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -20,7 +20,7 @@ endof(t::Tuple) = length(t) size(t::Tuple, d) = (d == 1) ? length(t) : throw(ArgumentError("invalid tuple dimension $d")) @eval getindex(t::Tuple, i::Int) = getfield(t, i, $(Expr(:boundscheck))) @eval getindex(t::Tuple, i::Real) = getfield(t, convert(Int, i), $(Expr(:boundscheck))) -getindex(t::Tuple, r::AbstractArray{<:Any,1}) = ([t[ri] for ri in r]...) +getindex(t::Tuple, r::AbstractArray{<:Any,1}) = ([t[ri] for ri in r]...,) getindex(t::Tuple, b::AbstractArray{Bool,1}) = length(b) == length(t) ? getindex(t, find(b)) : throw(BoundsError(t, b)) # returns new tuple; N.B.: becomes no-op if i is out-of-bounds @@ -137,7 +137,7 @@ end function _ntuple(f, n) @_noinline_meta (n >= 0) || throw(ArgumentError(string("tuple length should be ≥0, got ", n))) - ([f(i) for i = 1:n]...) + ([f(i) for i = 1:n]...,) end # inferrable ntuple (enough for bootstrapping) @@ -163,7 +163,7 @@ function map(f, t::Any16) for i=1:n A[i] = f(t[i]) end - (A...) + (A...,) end # 2 argument function map(f, t::Tuple{}, s::Tuple{}) = () @@ -179,7 +179,7 @@ function map(f, t::Any16, s::Any16) for i = 1:n A[i] = f(t[i], s[i]) end - (A...) + (A...,) end # n argument function heads(ts::Tuple...) = map(t -> t[1], ts) @@ -195,7 +195,7 @@ function map(f, t1::Any16, t2::Any16, ts::Any16...) for i = 1:n A[i] = f(t1[i], t2[i], map(t -> t[i], ts)...) end - (A...) + (A...,) end diff --git a/src/ast.scm b/src/ast.scm index 5f724c34d3998..94c857c2e51de 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -54,7 +54,9 @@ (string (car e) (deparse (cadr e))) (string (deparse (cadr e)) " " (car e) " " (deparse (caddr e))))) ((memq (car e) '($ &)) - (string (car e) (deparse (cadr e)))) + (if (pair? (cadr e)) + (string (car e) "(" (deparse (cadr e)) ")") + (string (car e) (deparse (cadr e))))) ((eq? (car e) '|::|) (if (length> e 2) (string (deparse (cadr e)) (car e) (deparse (caddr e))) diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 0636b5483af43..cfcfcdf3cda5a 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -177,6 +177,8 @@ (define whitespace-newline #f) ; enable parsing `where` with high precedence (define where-enabled #t) +; allow (x...), parsed as (... x). otherwise a deprecation warning is given (#24452) +(define accept-dots-without-comma #f) (define current-filename 'none) @@ -601,7 +603,9 @@ (if *deperror* "ERROR:" "WARNING:") " deprecated syntax \"" what "\"" (if (or (not s) (eq? current-filename 'none)) "" - (string " at " current-filename ":" (input-port-line (if (port? s) s (ts:port s))))) + (string " at " current-filename ":" (if (number? s) + s + (input-port-line (if (port? s) s (ts:port s)))))) "." (if (equal? instead "") "" @@ -1044,7 +1048,8 @@ ((not un) (error (string "\"" op "\" is not a unary operator"))) (else - (let* ((arg (parse-unary s)) + (let* ((arg (with-bindings ((accept-dots-without-comma #t)) + (parse-unary s))) (args (if (and (pair? arg) (eq? (car arg) 'tuple)) (cons op (cdr arg)) (list op arg)))) @@ -1131,11 +1136,14 @@ (or (closing-token? next) (newline? next)))) op) ((memq op '(& |::|)) (list op (parse-where s parse-call))) - (else (list op (parse-unary-prefix s))))) + (else (list op (with-bindings + ((accept-dots-without-comma #t)) + (parse-unary-prefix s)))))) (parse-atom s)))) -(define (parse-def s is-func) - (let* ((ex (parse-unary-prefix s)) +(define (parse-def s is-func anon) + (let* ((ex (with-bindings ((accept-dots-without-comma anon)) + (parse-unary-prefix s))) (sig (if (or (and is-func (reserved-word? ex)) (initial-reserved-word? ex)) (error (string "invalid name \"" ex "\"")) (parse-call-chain s ex #f))) @@ -1281,6 +1289,7 @@ (or (eq? (car sig) 'call) (eq? (car sig) 'tuple) (and paren (eq? (car sig) 'block)) + (and paren (eq? (car sig) '...)) (and (eq? (car sig) '|::|) (pair? (cadr sig)) (eq? (car (cadr sig)) 'call)) @@ -1413,7 +1422,7 @@ ((function macro) (let* ((paren (eqv? (require-token s) #\()) - (sig (parse-def s (not (eq? word 'macro))))) + (sig (parse-def s (eq? word 'function) paren))) (if (and (not paren) (symbol-or-interpolate? sig)) (begin (if (not (eq? (require-token s) 'end)) (error (string "expected \"end\" in definition of " word " \"" sig "\""))) @@ -2265,10 +2274,15 @@ (t (require-token s))) (cond ((eqv? t #\) ) (take-token s) + ;; value in parentheses (x) (if (and (pair? ex) (eq? (car ex) '...)) - ;; (ex...) - `(tuple ,ex) - ;; value in parentheses (x) + (let ((lineno (input-port-line (ts:port s)))) + (if (or accept-dots-without-comma (eq? (peek-token s) '->)) + ex + (begin (syntax-deprecation lineno + (string "(" (deparse (cadr ex)) "...)") + (string "(" (deparse (cadr ex)) "...,)")) + `(tuple ,ex)))) ex)) ((eq? t 'for) (expect-space-before s 'for) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 2411f5eda7cc8..984219814218e 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1019,7 +1019,7 @@ (define (expand-function-def e) ;; handle function definitions (define (just-arglist? ex) (and (pair? ex) - (or (memq (car ex) '(tuple block)) + (or (memq (car ex) '(tuple block ...)) (and (eq? (car ex) 'where) (just-arglist? (cadr ex)))))) (let ((name (cadr e))) diff --git a/src/macroexpand.scm b/src/macroexpand.scm index 9f99d4e23917f..1eed5da6835c0 100644 --- a/src/macroexpand.scm +++ b/src/macroexpand.scm @@ -20,10 +20,9 @@ ((atom? x) x) ((and (= d 0) (eq? (car x) '$)) (if (length= x 2) - (if (and (length= (cadr x) 2) (eq? (caadr x) 'tuple) - (vararg? (cadadr x))) - ;; splice expr ($ (tuple (... x))) - `(... ,(cadr (cadr (cadr x)))) + (if (vararg? (cadr x)) + ;; splice expr ($ (... x)) + `(... ,(cadr (cadr x))) ;; otherwise normal interpolation (cadr x)) ;; in e.g. `quote quote $$(x...) end end` multiple expressions can be diff --git a/test/core.jl b/test/core.jl index 6a84a56d9e0b2..154476f9c7509 100644 --- a/test/core.jl +++ b/test/core.jl @@ -3184,7 +3184,7 @@ mutable struct D11597{T} <: C11597{T} d::T end @test_throws TypeError repr(D11597(1.0)) # issue #11772 -@test_throws UndefRefError (Vector{Any}(5)...) +@test_throws UndefRefError (Vector{Any}(5)...,) # issue #11813 let a = UInt8[1, 107, 66, 88, 2, 99, 254, 13, 0, 0, 0, 0] @@ -3204,8 +3204,8 @@ let a = UInt8[0, 0, 0, 0, 0x66, 99, 254, 13, 0, 0, 0, 0] f11813(p) = ((Int32(3),UInt8(0x66)),Int32(0)) === unsafe_load(convert(Ptr{Tuple{Tuple{Int32,UInt8},Int32}},p)) @test f11813(p) === true # redundant comparison test seems to make this test more reliable, don't remove end -let a = (1:1000...), - b = (1:1000...) +let a = (1:1000...,), + b = (1:1000...,) @test a == b @test a === b @test (a == b) === true diff --git a/test/inference.jl b/test/inference.jl index c04c6b8c6dcda..f28923498cab5 100644 --- a/test/inference.jl +++ b/test/inference.jl @@ -285,7 +285,7 @@ const NInt1{N} = Tuple{Int, Vararg{Int, N}} @test Base.eltype(NInt{1}) === Int @test Base.eltype(NInt1{0}) === Int @test Base.eltype(NInt1{1}) === Int -fNInt(x::NInt) = (x...) +fNInt(x::NInt) = (x...,) gNInt() = fNInt(x) @test Base.return_types(gNInt, ()) == Any[NInt] @test Base.return_types(eltype, (NInt,)) == Any[Union{Type{Int}, Type{Union{}}}] # issue 21763 diff --git a/test/reflection.jl b/test/reflection.jl index 87eb59d8344bf..0af98d038865d 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -22,7 +22,7 @@ end function test_code_reflection(freflect, f, types, tester) tester(freflect, f, types) - tester(freflect, f, (types.parameters...)) + tester(freflect, f, (types.parameters...,)) nothing end diff --git a/test/syntax.jl b/test/syntax.jl index 8899421d81376..9151941bfcbb6 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -1150,6 +1150,11 @@ let xs = [:(1+2), :(3+4), :(5+6)] @test ex2.args[2:end] == [3,7,11] end +let x = [3,2,1] + @test :( $(x...,) ) == (3, 2, 1) + @test :( $(x...), ) == Expr(:tuple, 3, 2, 1) +end + # issue #23519 @test Meta.parse("@foo[1]") == Meta.parse("@foo([1])") @test Meta.parse("@foo[1 2; 3 4]") == Meta.parse("@foo([1 2; 3 4])")