diff --git a/NEWS.md b/NEWS.md index b0682f084af4e..488f07c21b193 100644 --- a/NEWS.md +++ b/NEWS.md @@ -124,6 +124,7 @@ New library features * `accumulate`, `cumsum`, and `cumprod` now support `Tuple` ([#34654]) and arbitrary iterators ([#34656]). * In `splice!` with no replacement, values to be removed can now be specified with an arbitrary iterable (instead of a `UnitRange`) ([#34524]). +* The `@view` and `@views` macros now support the `a[begin]` syntax that was introduced in Julia 1.4 ([#35289]). * `open` for files now accepts a keyword argument `lock` controlling whether file operations will acquire locks for safe multi-threaded access. Setting it to `false` provides better performance when only one thread will access the file. diff --git a/base/views.jl b/base/views.jl index b1be0dc0962a8..eebd1f31b6980 100644 --- a/base/views.jl +++ b/base/views.jl @@ -1,11 +1,11 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license """ - replace_ref_end!(ex) + replace_ref_begin_end!(ex) -Recursively replace occurrences of the symbol :end in a "ref" expression (i.e. A[...]) `ex` -with the appropriate function calls (`lastindex` or `size`). Replacement uses -the closest enclosing ref, so +Recursively replace occurrences of the symbols `:begin` and `:end` in a "ref" expression +(i.e. `A[...]`) `ex` with the appropriate function calls (`firstindex` or `lastindex`). +Replacement uses the closest enclosing ref, so A[B[end]] @@ -14,16 +14,21 @@ should transform to A[B[lastindex(B)]] """ -replace_ref_end!(ex) = replace_ref_end_!(ex, nothing)[1] -# replace_ref_end_!(ex,withex) returns (new ex, whether withex was used) -function replace_ref_end_!(ex, withex) +replace_ref_begin_end!(ex) = replace_ref_begin_end_!(ex, nothing)[1] +# replace_ref_begin_end_!(ex,withex) returns (new ex, whether withex was used) +function replace_ref_begin_end_!(ex, withex) used_withex = false - if isa(ex,Symbol) && ex === :end - withex === nothing && error("Invalid use of end") - return withex, true + if isa(ex,Symbol) + if ex === :begin + withex === nothing && error("Invalid use of begin") + return withex[1], true + elseif ex === :end + withex === nothing && error("Invalid use of end") + return withex[2], true + end elseif isa(ex,Expr) if ex.head === :ref - ex.args[1], used_withex = replace_ref_end_!(ex.args[1],withex) + ex.args[1], used_withex = replace_ref_begin_end_!(ex.args[1], withex) S = isa(ex.args[1],Symbol) ? ex.args[1]::Symbol : gensym(:S) # temp var to cache ex.args[1] if needed used_S = false # whether we actually need S # new :ref, so redefine withex @@ -32,12 +37,12 @@ function replace_ref_end_!(ex, withex) return ex, used_withex elseif nargs == 1 # replace with lastindex(S) - ex.args[2], used_S = replace_ref_end_!(ex.args[2],:($lastindex($S))) + ex.args[2], used_S = replace_ref_begin_end_!(ex.args[2], (:($firstindex($S)),:($lastindex($S)))) else n = 1 J = lastindex(ex.args) for j = 2:J - exj, used = replace_ref_end_!(ex.args[j],:($lastindex($S,$n))) + exj, used = replace_ref_begin_end_!(ex.args[j], (:($firstindex($S)),:($lastindex($S,$n)))) used_S |= used ex.args[j] = exj if isa(exj,Expr) && exj.head === :... @@ -61,7 +66,7 @@ function replace_ref_end_!(ex, withex) else # recursive search for i = eachindex(ex.args) - ex.args[i], used = replace_ref_end_!(ex.args[i],withex) + ex.args[i], used = replace_ref_begin_end_!(ex.args[i], withex) used_withex |= used end end @@ -77,6 +82,10 @@ reference expression (e.g. `@view A[1,2:end]`), and should *not* be used as the an assignment (e.g. `@view(A[1,2:end]) = ...`). See also [`@views`](@ref) to switch an entire block of code to use views for slicing. +!!! compat "Julia 1.5" + Using `begin` in an indexing expression to refer to the first index requires at least + Julia 1.5. + # Examples ```jldoctest julia> A = [1 2; 3 4] @@ -102,7 +111,7 @@ julia> A """ macro view(ex) if Meta.isexpr(ex, :ref) - ex = replace_ref_end!(ex) + ex = replace_ref_begin_end!(ex) if Meta.isexpr(ex, :ref) ex = Expr(:call, view, ex.args...) else # ex replaced by let ...; foo[...]; end @@ -129,7 +138,7 @@ end # _views implements the transformation for the @views macro. # @views calls esc(_views(...)) to work around #20241, # so any function calls we insert (to maybeview, or to -# lastindex in replace_ref_end!) must be interpolated +# firstindex and lastindex in replace_ref_begin_end!) must be interpolated # as values rather than as symbols to ensure that they are called # from Base rather than from the caller's scope. _views(x) = x @@ -194,6 +203,10 @@ unaffected. that appear explicitly in the given `expression`, not array slicing that occurs in functions called by that code. +!!! compat "Julia 1.5" + Using `begin` in an indexing expression to refer to the first index requires at least + Julia 1.5. + # Examples ```jldoctest julia> A = zeros(3, 3); @@ -211,5 +224,5 @@ julia> A ``` """ macro views(x) - esc(_views(replace_ref_end!(x))) + esc(_views(replace_ref_begin_end!(x))) end diff --git a/test/subarray.jl b/test/subarray.jl index 1fb928891d6c6..e43b3361efbaf 100644 --- a/test/subarray.jl +++ b/test/subarray.jl @@ -490,7 +490,7 @@ end @test Array(view(view(reshape(1:13^3, 13, 13, 13), 3:7, 6:6, :), 1:2:5, :, 1:2:5)) == cat([68,70,72],[406,408,410],[744,746,748]; dims=3) -# tests @view (and replace_ref_end!) +# tests @view (and replace_ref_begin_end!) @test_throws ArgumentError( "Invalid use of @view macro: argument must be a reference expression A[...]." @@ -501,16 +501,16 @@ Y = 4:-1:1 @test isa(@view(X[1:3]), SubArray) -@test X[1:end] == @.(@view X[1:end]) # test compatibility of @. and @view -@test X[1:end-3] == @view X[1:end-3] -@test X[1:end,2,2] == @view X[1:end,2,2] -@test X[1,1:end-2,1] == @view X[1,1:end-2,1] -@test X[1,2,1:end-2] == @view X[1,2,1:end-2] -@test X[1,2,Y[2:end]] == @view X[1,2,Y[2:end]] -@test X[1:end,2,Y[2:end]] == @view X[1:end,2,Y[2:end]] +@test X[begin:end] == @.(@view X[begin:end]) # test compatibility of @. and @view +@test X[begin:end-3] == @view X[begin:end-3] +@test X[1:end,2,begin+1] == @view X[1:end,2,begin+1] +@test X[begin,1:end-2,1] == @view X[begin,1:end-2,1] +@test X[begin,begin+1,begin:end-2] == @view X[begin,begin+1,begin:end-2] +@test X[begin,2,Y[2:end]] == @view X[begin,2,Y[2:end]] +@test X[begin:end,2,Y[begin+1:end]] == @view X[begin:end,2,Y[begin+1:end]] u = (1,2:3) -@test X[u...,2:end] == @view X[u...,2:end] +@test X[u...,begin+1:end] == @view X[u...,begin+1:end] @test X[(1,)...,(2,)...,2:end] == @view X[(1,)...,(2,)...,2:end] # test macro hygiene @@ -525,7 +525,7 @@ let foo = [X] end # test @views macro -@views let f!(x) = x[1:end-1] .+= x[2:end].^2 +@views let f!(x) = x[begin:end-1] .+= x[begin+1:end].^2 x = [1,2,3,4] f!(x) @test x == [5,11,19,4] @@ -552,13 +552,13 @@ end @test x == [5,8,0,0] end @views @test isa(X[1:3], SubArray) -@test X[1:end] == @views X[1:end] -@test X[1:end-3] == @views X[1:end-3] -@test X[1:end,2,2] == @views X[1:end,2,2] -@test X[1,2,1:end-2] == @views X[1,2,1:end-2] -@test X[1,2,Y[2:end]] == @views X[1,2,Y[2:end]] -@test X[1:end,2,Y[2:end]] == @views X[1:end,2,Y[2:end]] -@test X[u...,2:end] == @views X[u...,2:end] +@test X[begin:end] == @views X[begin:end] +@test X[begin:end-3] == @views X[begin:end-3] +@test X[1:end,2,begin+1] == @views X[1:end,2,begin+1] +@test X[begin,2,1:end-2] == @views X[begin,2,1:end-2] +@test X[begin,2,Y[2:end]] == @views X[begin,2,Y[2:end]] +@test X[begin:end,2,Y[begin+1:end]] == @views X[begin:end,2,Y[begin+1:end]] +@test X[u...,begin+1:end] == @views X[u...,begin+1:end] @test X[(1,)...,(2,)...,2:end] == @views X[(1,)...,(2,)...,2:end] # @views for zero dimensional arrays