diff --git a/NEWS.md b/NEWS.md index 824d730ed3c3c..05b87e6d4da2b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,8 @@ New language features difference between `public` and `export` is that `public` names do not become available when `using` a package/module. ([#50105]) * `ScopedValue` implement dynamic scope with inheritance across tasks ([#50958]). +* The new macro `Base.Cartesian.@ncallkw` is analogous to `Base.Cartesian.@ncall`, + but allows to add keyword arguments to the function call ([#51501]). Language changes ---------------- diff --git a/base/cartesian.jl b/base/cartesian.jl index 5f96a2061880f..decc01cbc3e5f 100644 --- a/base/cartesian.jl +++ b/base/cartesian.jl @@ -2,7 +2,7 @@ module Cartesian -export @nloops, @nref, @ncall, @nexprs, @nextract, @nall, @nany, @ntuple, @nif +export @nloops, @nref, @ncall, @ncallkw, @nexprs, @nextract, @nall, @nany, @ntuple, @nif ### Cartesian-specific macros @@ -104,10 +104,38 @@ while `@ncall 2 func a b i->c[i]` yields macro ncall(N::Int, f, args...) pre = args[1:end-1] ex = args[end] - vars = Any[ inlineanonymous(ex,i) for i = 1:N ] + vars = (inlineanonymous(ex, i) for i = 1:N) Expr(:escape, Expr(:call, f, pre..., vars...)) end +""" + @ncallkw N f kw sym... + +Generate a function call expression with keyword arguments `kw...`. As +in the case of [`@ncall`](@ref), `sym` represents any number of function arguments, the +last of which may be an anonymous-function expression and is expanded into `N` arguments. + +# Example +```jldoctest +julia> using Base.Cartesian + +julia> f(x...; a, b = 1, c = 2, d = 3) = +(x..., a, b, c, d); + +julia> x_1, x_2 = (-1, -2); b = 0; kw = (c = 0, d = 0); + +julia> @ncallkw 2 f (; a = 0, b, kw...) x +-3 + +``` +""" +macro ncallkw(N::Int, f, kw, args...) + pre = args[1:end-1] + ex = args[end] + vars = (inlineanonymous(ex, i) for i = 1:N) + param = Expr(:parameters, Expr(:(...), kw)) + Expr(:escape, Expr(:call, f, param, pre..., vars...)) +end + """ @nexprs N expr @@ -374,6 +402,8 @@ function exprresolve_conditional(ex::Expr) return true, exprresolve_cond_dict[callee](ex.args[2], ex.args[3]) end end + elseif Meta.isexpr(ex, :block, 2) && ex.args[1] isa LineNumberNode + return exprresolve_conditional(ex.args[2]) end false, false end @@ -402,10 +432,16 @@ function exprresolve(ex::Expr) return ex.args[1][ex.args[2:end]...] end # Resolve conditionals - if ex.head === :if + if ex.head === :if || ex.head === :elseif can_eval, tf = exprresolve_conditional(ex.args[1]) if can_eval - ex = tf ? ex.args[2] : ex.args[3] + if tf + return ex.args[2] + elseif length(ex.args) == 3 + return ex.args[3] + else + return nothing + end end end ex diff --git a/doc/src/devdocs/cartesian.md b/doc/src/devdocs/cartesian.md index 1d338cbd8fab3..604f04f2a39e5 100644 --- a/doc/src/devdocs/cartesian.md +++ b/doc/src/devdocs/cartesian.md @@ -133,6 +133,7 @@ Base.Cartesian.@nref Base.Cartesian.@nextract Base.Cartesian.@nexprs Base.Cartesian.@ncall +Base.Cartesian.@ncallkw Base.Cartesian.@ntuple Base.Cartesian.@nall Base.Cartesian.@nany diff --git a/test/cartesian.jl b/test/cartesian.jl index ed33f2c1035f7..0f41318954eca 100644 --- a/test/cartesian.jl +++ b/test/cartesian.jl @@ -542,3 +542,35 @@ end inds2 = (1, CI(1, 2), 1, CI(1, 2), 1, CI(1, 2), 1) @test (@inferred CI(inds2)) == CI(1, 1, 2, 1, 1, 2, 1, 1, 2, 1) end + +@testset "@ncallkw" begin + f(x...; a, b = 1, c = 2, d = 3) = +(x..., a, b, c, d) + x_1, x_2 = (-1, -2) + kw = (a = 0, c = 0, d = 0) + @test x_1 + x_2 + 1 + 4 == Base.Cartesian.@ncallkw 2 f kw 4 x + b = 0 + kw = (c = 0, d = 0) + @test x_1 + x_2 + 4 == Base.Cartesian.@ncallkw 2 f (; a = 0, b, kw...) 4 x +end + +@testset "if with and without else branch" begin + t1 = Base.Cartesian.@ntuple 3 i -> i == 1 ? 1 : 0 + t2 = Base.Cartesian.@ntuple 3 i -> begin + m = 0 + if i == 1 + m = 1 + end + m + end + @test t1 == t2 + t3 = Base.Cartesian.@ntuple 3 i -> begin + m = 0 + if i == 1 + m = 1 + elseif i == 2 + m = 2 + end + m + end + @test t3 == (1, 2, 0) +end