diff --git a/NEWS.md b/NEWS.md index fac73c4bd3352..226959caa8371 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,8 +4,8 @@ Julia v1.2 Release Notes New language features --------------------- -* The `extrema` function now accepts a function argument in the same manner as `minimum` and - `maximum` ([#30323]). + * Argument splatting (`x...`) can now be used in calls to the `new` pseudo-function in + constructors ([#30577]). Multi-threading changes ----------------------- @@ -25,11 +25,14 @@ Command-line option changes New library functions --------------------- -* `getipaddrs()` function returns all the IP addresses of the local machine ([#30349]) + * `getipaddrs()` function returns all the IP addresses of the local machine ([#30349]) Standard library changes ------------------------ + * The `extrema` function now accepts a function argument in the same manner as `minimum` and + `maximum` ([#30323]). + #### LinearAlgebra * Added keyword arguments `rtol`, `atol` to `pinv` and `nullspace` ([#29998]). @@ -44,7 +47,7 @@ Standard library changes #### Dates -* Fixed `repr` such that it displays `DateTime` as it would be entered in Julia ([#30200]). + * Fixed `repr` such that it displays `DateTime` as it would be entered in Julia ([#30200]). #### Miscellaneous diff --git a/base/essentials.jl b/base/essentials.jl index 984ef41ff0129..e8922f978dca6 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -280,6 +280,13 @@ convert(::Type{Tuple{Vararg{V}}}, x::Tuple{Vararg{V}}) where {V} = x convert(T::Type{Tuple{Vararg{V}}}, x::Tuple) where {V} = (convert(tuple_type_head(T), x[1]), convert(T, tail(x))...) +# used for splatting in `new` +convert_prefix(::Type{Tuple{}}, x::Tuple) = x +convert_prefix(::Type{<:AtLeast1}, x::Tuple{}) = x +convert_prefix(::Type{T}, x::T) where {T<:AtLeast1} = x +convert_prefix(::Type{T}, x::AtLeast1) where {T<:AtLeast1} = + (convert(tuple_type_head(T), x[1]), convert_prefix(tuple_type_tail(T), tail(x))...) + # TODO: the following definitions are equivalent (behaviorally) to the above method # I think they may be faster / more efficient for inference, # if we could enable them, but are they? diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index bee5ac2528ef2..36f6abf7400e0 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -696,8 +696,6 @@ (call (curly ,name ,@params) ,@field-names))))) (define (new-call Tname type-params params args field-names field-types) - (if (any vararg? args) - (error "... is not supported inside \"new\"")) (if (any kwarg? args) (error "\"new\" does not accept keyword arguments")) (if (length> params (length type-params)) @@ -706,8 +704,22 @@ `(outerref ,Tname) `(curly (outerref ,Tname) ,@type-params)))) - (cond ((length> args (length field-names)) - `(call (top error) "new: too many arguments")) + (cond ((length> (filter (lambda (a) (not (vararg? a))) args) (length field-names)) + `(call (core throw) (call (top ArgumentError) + ,(string "new: too many arguments (expected " (length field-names) ")")))) + ((any vararg? args) + (if (every (lambda (ty) (equal? ty '(core Any))) + field-types) + `(splatnew ,Texpr (call (core tuple) ,@args)) + (let ((tn (make-ssavalue))) + `(block + (= ,tn ,Texpr) + (splatnew ,tn (call (top convert_prefix) + (curly (core Tuple) + ,@(map (lambda (fld) + `(call (core fieldtype) ,tn (quote ,fld))) + field-names)) + (call (core tuple) ,@args))))))) (else (if (equal? type-params params) `(new ,Texpr ,@(map (lambda (fty val) diff --git a/test/core.jl b/test/core.jl index 095991516e293..4f51418bfed65 100644 --- a/test/core.jl +++ b/test/core.jl @@ -6816,3 +6816,33 @@ f29828() = 2::String g29828() = 2::Any[String][1] @test_throws TypeError(:typeassert, String, 2) f29828() @test_throws TypeError(:typeassert, String, 2) g29828() + +# splatting in `new` +struct SplatNew{T} + x::Int8 + y::T + SplatNew{T}(args...) where {T} = new(0,args...,1) + SplatNew(args...) = new{Float32}(args...) + SplatNew{Any}(args...) = new(args...) + SplatNew{Tuple{Int16}}(args...) = new([2]..., args...) + SplatNew{Int8}() = new(1,2,3) +end +let x = SplatNew{Int16}() + @test x.x === Int8(0) + @test x.y === Int16(1) +end +@test_throws ArgumentError SplatNew{Int16}(1) +let x = SplatNew(3,2) + @test x.x === Int8(3) + @test x.y === 2.0f0 +end +@test_throws ArgumentError SplatNew(1,2,3) +let x = SplatNew{Any}(1) + @test x.x === Int8(1) + @test !isdefined(x, :y) +end +let x = SplatNew{Tuple{Int16}}((1,)) + @test x.x === Int8(2) + @test x.y === (Int16(1),) +end +@test_throws ArgumentError SplatNew{Int8}()