From b013403a5427a861e5b3ed38fea2f68b0ae9a19f Mon Sep 17 00:00:00 2001 From: Martin Holters Date: Fri, 31 Aug 2018 16:30:34 +0200 Subject: [PATCH] Remove compat at-dot, at-view, and at-views macros (#627) These were added in #316 for Julia versions older than 0.6. The at-dotcompat macro is kept to avoid breakage. --- README.md | 12 --- src/arraymacros.jl | 206 ++------------------------------------------- test/runtests.jl | 97 +-------------------- 3 files changed, 10 insertions(+), 305 deletions(-) diff --git a/README.md b/README.md index cbea8303a..1da1280a2 100644 --- a/README.md +++ b/README.md @@ -120,15 +120,6 @@ Currently, the `@compat` macro supports the following syntaxes: ## New functions, macros, and methods -* `@views` takes an expression and converts all slices to views ([#20164]), while - `@view` ([#16564]) converts a single array reference to a view ([#20164]). - -* `@__dot__` takes an expression and converts all assignments, function calls, - and operators to their broadcasting "dot-call" equivalents ([#20321]). In Julia 0.6, this - can be abbreviated `@.`, but that macro name does not parse in earlier Julia versions. - For this to work in older versions of Julia (prior to 0.5) that don't have dot calls, - you should instead use `@dotcompat`, which combines the `@__dot__` and `@compat` macros. - * [`normalize`](http://docs.julialang.org/en/latest/stdlib/linalg/?highlight=normalize#Base.normalize) and [`normalize!`](http://docs.julialang.org/en/latest/stdlib/linalg/?highlight=normalize#Base.normalize!), normalizes a vector with respect to the p-norm ([#13681]) * `redirect_stdout`, `redirect_stderr`, and `redirect_stdin` take an optional function as a first argument, `redirect_std*(f, stream)`, so that one may use `do` block syntax (as first available for Julia 0.6) @@ -497,7 +488,6 @@ includes this fix. Find the minimum version from there. `Compat ` [#13681]: https://github.com/JuliaLang/julia/issues/13681 -[#16564]: https://github.com/JuliaLang/julia/issues/16564 [#16986]: https://github.com/JuliaLang/julia/issues/16986 [#17302]: https://github.com/JuliaLang/julia/issues/17302 [#17323]: https://github.com/JuliaLang/julia/issues/17323 @@ -517,8 +507,6 @@ includes this fix. Find the minimum version from there. [#19950]: https://github.com/JuliaLang/julia/issues/19950 [#20005]: https://github.com/JuliaLang/julia/issues/20005 [#20022]: https://github.com/JuliaLang/julia/issues/20022 -[#20164]: https://github.com/JuliaLang/julia/issues/20164 -[#20321]: https://github.com/JuliaLang/julia/issues/20321 [#20407]: https://github.com/JuliaLang/julia/issues/20407 [#20974]: https://github.com/JuliaLang/julia/issues/20974 [#21197]: https://github.com/JuliaLang/julia/issues/21197 diff --git a/src/arraymacros.jl b/src/arraymacros.jl index 7435ddda7..570e8a489 100644 --- a/src/arraymacros.jl +++ b/src/arraymacros.jl @@ -1,201 +1,7 @@ -# Julia 0.6 macros to aid in vectorization: @view, @views, @__dot__ (@.), -# backported from Julia 0.6. - -# prior to julia#20247, the replace_ref_end! macro had hygiene bugs -if VERSION < v"0.6.0-dev.2406" - function trailingsize(A, n) - s = 1 - for i=n:ndims(A) - s *= size(A,i) - end - return s - end - 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) - used_withex = false - if isa(ex,Symbol) && ex == :end - withex === nothing && error("Invalid use of end") - return withex, true - elseif isa(ex,Expr) - if ex.head == :ref - ex.args[1], used_withex = replace_ref_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 - nargs = length(ex.args)-1 - if nargs == 0 - return ex, used_withex - elseif nargs == 1 - # replace with endof(S) - ex.args[2], used_S = replace_ref_end_!(ex.args[2],:($endof($S))) - else - n = 1 - J = endof(ex.args) - for j = 2:J-1 - exj, used = replace_ref_end_!(ex.args[j],:($size($S,$n))) - used_S |= used - ex.args[j] = exj - if isa(exj,Expr) && exj.head == :... - # splatted object - exjs = exj.args[1] - n = :($n + length($exjs)) - elseif isa(n, Expr) - # previous expression splatted - n = :($n + 1) - else - # an integer - n += 1 - end - end - ex.args[J], used = replace_ref_end_!(ex.args[J],:($trailingsize($S,$n))) - used_S |= used - end - if used_S && S !== ex.args[1] - S0 = ex.args[1] - ex.args[1] = S - ex = Expr(:let, ex, :($S = $S0)) - end - else - # recursive search - for i = eachindex(ex.args) - ex.args[i], used = replace_ref_end_!(ex.args[i],withex) - used_withex |= used - end - end - end - ex, used_withex - end -end - -if !isdefined(Base, Symbol("@view")) - macro view(ex) - if Meta.isexpr(ex, :ref) - ex = replace_ref_end!(ex) - if Meta.isexpr(ex, :ref) - ex = Expr(:call, view, ex.args...) - else # ex replaced by let ...; foo[...]; end - assert(Meta.isexpr(ex, :let) && Meta.isexpr(ex.args[1], :ref)) - ex.args[1] = Expr(:call, view, ex.args[1].args...) - end - Expr(:&&, true, esc(ex)) - else - throw(ArgumentError("Invalid use of @view macro: argument must be a reference expression A[...].")) - end - end - export @view -end - -if !isdefined(Base, Symbol("@views")) - maybeview(A, args...) = getindex(A, args...) - maybeview(A::AbstractArray, args...) = view(A, args...) - maybeview(A::AbstractArray, args::Number...) = getindex(A, args...) - maybeview(A) = getindex(A) - maybeview(A::AbstractArray) = getindex(A) - - _views(x) = x - function _views(ex::Expr) - if ex.head in (:(=), :(.=)) - # don't use view for ref on the lhs of an assignment, - # but still use views for the args of the ref: - lhs = ex.args[1] - Expr(ex.head, Meta.isexpr(lhs, :ref) ? - Expr(:ref, map(_views, lhs.args)...) : _views(lhs), - _views(ex.args[2])) - elseif ex.head == :ref - Expr(:call, maybeview, map(_views, ex.args)...) - else - h = string(ex.head) - # don't use view on the lhs of an op-assignment a[i...] += ... - if last(h) == '=' && Meta.isexpr(ex.args[1], :ref) - lhs = ex.args[1] - - # temp vars to avoid recomputing a and i, - # which will be assigned in a let block: - a = gensym(:a) - i = [gensym(:i) for k = 1:length(lhs.args)-1] - - # for splatted indices like a[i, j...], we need to - # splat the corresponding temp var. - I = similar(i, Any) - for k = 1:length(i) - if Meta.isexpr(lhs.args[k+1], :...) - I[k] = Expr(:..., i[k]) - lhs.args[k+1] = lhs.args[k+1].args[1] # unsplat - else - I[k] = i[k] - end - end - - Expr(:let, - Expr(first(h) == '.' ? :(.=) : :(=), :($a[$(I...)]), - Expr(:call, Symbol(h[1:end-1]), - :($maybeview($a, $(I...))), - map(_views, ex.args[2:end])...)), - :($a = $(_views(lhs.args[1]))), - [:($(i[k]) = $(_views(lhs.args[k+1]))) for k=1:length(i)]...) - else - Expr(ex.head, map(_views, ex.args)...) - end - end - end - - macro views(x) - esc(_views(replace_ref_end!(x))) - end - export @views -end - -# we can't define @. because that doesn't parse in Julia < 0.6, but -# we can define @__dot__, which is what @. is sugar for: -if !isdefined(Base, Symbol("@__dot__")) - dottable(x) = false # avoid dotting spliced objects (e.g. view calls inserted by @view) - dottable(x::Symbol) = !Base.isoperator(x) || first(string(x)) != '.' || x == :.. # don't add dots to dot operators - dottable(x::Expr) = x.head != :$ - undot(x) = x - function undot(x::Expr) - if x.head == :.= - Expr(:(=), x.args...) - elseif x.head == :block # occurs in for x=..., y=... - Expr(:block, map(undot, x.args)...) - else - x - end - end - __dot__(x) = x - function __dot__(x::Expr) - dotargs = map(__dot__, x.args) - if x.head == :call && dottable(x.args[1]) - Expr(:., dotargs[1], Expr(:tuple, dotargs[2:end]...)) - elseif x.head == :$ - x.args[1] - elseif x.head == :let # don't add dots to "let x=... assignments - Expr(:let, dotargs[1], map(undot, dotargs[2:end])...) - elseif x.head == :for # don't add dots to for x=... assignments - Expr(:for, undot(dotargs[1]), dotargs[2]) - elseif (x.head == :(=) || x.head == :function || x.head == :macro) && - Meta.isexpr(x.args[1], :call) # function or macro definition - Expr(x.head, x.args[1], dotargs[2]) - else - head = string(x.head) - if last(head) == '=' && first(head) != '.' - Expr(Symbol('.',head), dotargs...) - else - Expr(x.head, dotargs...) - end - end - end - macro __dot__(x) - esc(__dot__(x)) - end - macro dotcompat(x) - esc(_compat(__dot__(x))) - end - export @__dot__, @dotcompat -else - # in 0.6, use the __dot__ function from Base.Broadcast - macro dotcompat(x) - esc(_compat(Base.Broadcast.__dot__(x))) - end - export @dotcompat +# TODO deprecate +# this was defined for use with Julia versions prior to 0.5 +# (see https://github.com/JuliaLang/Compat.jl/pull/316) +macro dotcompat(x) + esc(_compat(Base.Broadcast.__dot__(x))) end +export @dotcompat diff --git a/test/runtests.jl b/test/runtests.jl index b82eb6123..d3c376306 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -297,106 +297,17 @@ eval(Expr( @test !isabstracttype(ConcreteFoo200061) @test !isabstracttype(StridedArray) -# @view and @views tests copied from Base -let X = reshape(1:24,2,3,4), Y = 4:-1:1 - @test isa(@view(X[1:3]), SubArray) - +# TODO remove these tests when deprecating @dotcompat +let X = reshape(1:24,2,3,4) @test X[1:end] == @dotcompat (@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 reshape(X[1,2,1:end-2],2) == @view X[1,2,1:end-2] - @test reshape(X[1,2,Y[2:end]],3) == @view X[1,2,Y[2:end]] - @test reshape(X[1:end,2,Y[2:end]],2,3) == @view X[1:end,2,Y[2:end]] - - u = (1,2:3) - @test reshape(X[u...,2:end],2,3) == @view X[u...,2:end] - @test reshape(X[(1,)...,(2,)...,2:end],3) == @view X[(1,)...,(2,)...,2:end] - - # the following tests fail on 0.5 because of bugs in the 0.5 Base.@view - # macro (a bugfix is scheduled to be backported from 0.6: julia#20247) - if !isdefined(Base, Symbol("@view")) || VERSION ≥ v"0.6.0-dev.2406" - # test macro hygiene - let size=(x,y)-> error("should not happen"), Base=nothing - @test X[1:end,2,2] == @view X[1:end,2,2] - end - - # test that side effects occur only once - let foo = typeof(X)[X] - @test X[2:end-1] == @view (push!(foo,X)[1])[2:end-1] - @test foo == typeof(X)[X, X] - end - end - - # test @views macro - @views @compat let f!(x) = x[1:end-1] .+= x[2:end].^2 - x = [1,2,3,4] - f!(x) - @test x == [5,11,19,4] - @test isa(x[1:3],SubArray) - @test x[2] === 11 - @test Dict((1:3) => 4)[1:3] === 4 - x[1:2] .= 0 - @test x == [0,0,19,4] - x[1:2] .= 5:6 - @test x == [5,6,19,4] - f!(x[3:end]) - @test x == [5,6,35,4] - x[Y[2:3]] .= 7:8 - @test x == [5,8,7,4] + @views let + x = [5,8,7,4] @dotcompat x[([3],)..., ()...] += 3 # @. should convert to .+=, test compatibility with @views @test x == [5,8,10,4] - i = Int[] - # test that lhs expressions in update operations are evaluated only once: - x[push!(i,4)[1]] += 5 - @test x == [5,8,10,9] && i == [4] - x[push!(i,3)[end]] += 2 - @test x == [5,8,12,9] && i == [4,3] @dotcompat x[3:end] = 0 # make sure @. works with end expressions in @views @test x == [5,8,0,0] end - # same tests, but make sure we can switch the order of @compat and @views - @compat @views let f!(x) = x[1:end-1] .+= x[2:end].^2 - x = [1,2,3,4] - f!(x) - @test x == [5,11,19,4] - @test isa(x[1:3],SubArray) - @test x[2] === 11 - @test Dict((1:3) => 4)[1:3] === 4 - x[1:2] .= 0 - @test x == [0,0,19,4] - x[1:2] .= 5:6 - @test x == [5,6,19,4] - f!(x[3:end]) - @test x == [5,6,35,4] - x[Y[2:3]] .= 7:8 - @test x == [5,8,7,4] - @dotcompat x[([3],)..., ()...] += 3 # @. should convert to .+=, test compatibility with @views - @test x == [5,8,10,4] - i = Int[] - # test that lhs expressions in update operations are evaluated only once: - x[push!(i,4)[1]] += 5 - @test x == [5,8,10,9] && i == [4] - x[push!(i,3)[end]] += 2 - @test x == [5,8,12,9] && i == [4,3] - @dotcompat x[3:end] = 0 # make sure @. works with end expressions in @views - @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 reshape(X[1,2,1:end-2],2) == @views X[1,2,1:end-2] - @test reshape(X[1,2,Y[2:end]],3) == @views X[1,2,Y[2:end]] - @test reshape(X[1:end,2,Y[2:end]],2,3) == @views X[1:end,2,Y[2:end]] - @test reshape(X[u...,2:end],2,3) == @views X[u...,2:end] - @test reshape(X[(1,)...,(2,)...,2:end],3) == @views X[(1,)...,(2,)...,2:end] - - # test macro hygiene - let size=(x,y)-> error("should not happen"), Base=nothing - @test X[1:end,2,2] == @views X[1:end,2,2] - end end - # @. (@__dot__) tests, from base: let x = [4, -9, 1, -16] @test [2, 3, 4, 5] == @dotcompat(1 + sqrt($sort(abs(x))))