From cb6112bb8668dd385af6d915ec3c878dffa6a14c Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Fri, 3 Feb 2017 15:27:52 -0500 Subject: [PATCH 1/4] add at-dot, at-view, and at-views macros; improve at-compat for .= and .+= --- README.md | 17 +++- src/Compat.jl | 25 ++++- src/arraymacros.jl | 221 +++++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 93 ++++++++++++++++++- 4 files changed, 348 insertions(+), 8 deletions(-) create mode 100644 src/arraymacros.jl diff --git a/README.md b/README.md index 1213bbdf5..8992c01bd 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,8 @@ Currently, the `@compat` macro supports the following syntaxes: * `@compat x .= y` converts to an in-place assignment to `x` (via `broadcast!`) ([#17510]). However, beware that `.=` in Julia 0.4 has the precedence of `==`, not of assignment `=`, so if the right-hand-side `y` includes expressions with lower precedence than `==` you should enclose it in parentheses `x .= (y)` to ensure the - correct order of evaluation. + correct order of evaluation. Also, `x .+= y` converts to `x .= (x .+ y)`, and similarly for the other updating + assignment operators (`.*=` and so on). * `@compat Array{<:Real}` and similar uses of `<:T` to define a set of parameterized types ([#20414]). In 0.4 and 0.5, this only works for non-nested usages (e.g. you can't define `Array{<:Array{<:Real}}`). @@ -80,7 +81,16 @@ Currently, the `@compat` macro supports the following syntaxes: * `bytestring` has been replaced in most cases with additional `String` construction methods; for 0.4 compatibility, the usage involves replacing `bytestring(args...)` with `Compat.String(args...)`. However, for converting a `Ptr{UInt8}` to a string, use the new `unsafe_string(...)` method to make a copy or `unsafe_wrap(String, ...)` to avoid a copy. -## New functions and methods +## 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]). Using `@views` + automatically implies `@compat`. + +* `@__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. + Using `@__dot__` automatically implies `@compat`. * `foreach`, similar to `map` but when the return value is not needed ([#13744]) @@ -277,6 +287,7 @@ includes this fix. Find the minimum version from there. [#16154]: https://github.com/JuliaLang/julia/issues/16154 [#16219]: https://github.com/JuliaLang/julia/issues/16219 [#16563]: https://github.com/JuliaLang/julia/issues/16563 +[#16564]: https://github.com/JuliaLang/julia/issues/16564 [#16603]: https://github.com/JuliaLang/julia/issues/16603 [#16972]: https://github.com/JuliaLang/julia/issues/16972 [#17155]: https://github.com/JuliaLang/julia/issues/17155 @@ -293,4 +304,6 @@ includes this fix. Find the minimum version from there. [#19841]: https://github.com/JuliaLang/julia/issues/19841 [#19950]: https://github.com/JuliaLang/julia/issues/19950 [#20022]: https://github.com/JuliaLang/julia/issues/20022 +[#20164]: https://github.com/JuliaLang/julia/issues/20164 +[#20321]: https://github.com/JuliaLang/julia/issues/20321 [#20414]: https://github.com/JuliaLang/julia/issues/20414 diff --git a/src/Compat.jl b/src/Compat.jl index 60a473773..54a260e7c 100644 --- a/src/Compat.jl +++ b/src/Compat.jl @@ -715,11 +715,22 @@ function _compat(ex::Expr) import Base.writemime end end - elseif VERSION < v"0.5.0-dev+5575" && isexpr(ex, :comparison) #17510 - if length(ex.args) == 3 && ex.args[2] == :.= - return :(Base.broadcast!(Base.identity, $(_compat(ex.args[1])), $(_compat(ex.args[3])))) - elseif length(ex.args) > 3 && ex.args[2] == :.= - return :(Base.broadcast!(Base.identity, $(_compat(ex.args[1])), $(_compat(Expr(:comparison, ex.args[3:end]...))))) + elseif VERSION < v"0.5.0-dev+5575" #17510 + if isexpr(ex, :comparison) + if length(ex.args) == 3 && ex.args[2] == :.= + return :(Base.broadcast!(Base.identity, $(_compat(todotview(ex.args[1]))), $(_compat(ex.args[3])))) + elseif length(ex.args) > 3 && ex.args[2] == :.= + return :(Base.broadcast!(Base.identity, $(_compat(todotview(ex.args[1]))), $(_compat(Expr(:comparison, ex.args[3:end]...))))) + end + elseif ex.head == :.= && length(ex.args) == 2 # may arise in macro-constructed expressions + return :(Base.broadcast!(Base.identity, $(_compat(todotview(ex.args[1]))), $(_compat(ex.args[2])))) + end + end + if VERSION < v"0.5.0-dev+5575" #17510 + # transform e.g. x .+= y into x .= x .+ y: + shead = string(ex.head) + if first(shead) == '.' && length(shead) > 2 && last(shead) == '=' && length(ex.args) == 2 + return _compat(Expr(:comparison, ex.args[1], :.=, Expr(:call, Symbol(shead[1:end-1]), ex.args...))) end end return Expr(ex.head, map(_compat, ex.args)...) @@ -1788,4 +1799,8 @@ module TypeUtils end export isabstract, parameter_upper_bound, typename end # module TypeUtils + +# @view, @views, @__dot__ +include("arraymacros.jl") + end # module Compat diff --git a/src/arraymacros.jl b/src/arraymacros.jl new file mode 100644 index 000000000..b686d84fb --- /dev/null +++ b/src/arraymacros.jl @@ -0,0 +1,221 @@ +# Julia 0.6 macros to aid in vectorization: @view, @views, @__dot__ (@.), +# backported from Julia 0.6. + +if isdefined(Base, :replace_ref_end!) + import Base.replace_ref_end! +else + 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 + +# convert x[...] on lhs of .= to a view in broadcast! call +if VERSION < v"0.5.0-dev+5575" #17510 + dotview(args...) = getindex(args...) + dotview(A::AbstractArray, args...) = view(A, args...) + dotview{T<:AbstractArray}(A::AbstractArray{T}, args::Integer...) = getindex(A, args...) + todotview(x) = x + function todotview(ex::Expr) + if ex.head == :ref + ex = replace_ref_end!(ex) + if Meta.isexpr(ex, :ref) + ex = Expr(:call, dotview, 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, dotview, ex.args[1].args...) + end + end + 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 VERSION < v"0.5.0-dev+5575" && isexpr(ex, :comparison) && ex.args[2] == :.= #17510 + # as above, but in Julia 0.4 a .= produces a comparison expression + lhs_ = ex.args[1] + lhs = Meta.isexpr(lhs_, :ref) ? Expr(:ref, map(_views, lhs_.args)...) : _views(lhs_) + if length(ex.args) == 3 + Expr(:.=, lhs, _views(ex.args[3])) + else + Expr(:.=, lhs, _views(Expr(:comparison, ex.args[3:end]...))) + end + 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(_compat(_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(_compat(__dot__(x))) + end + export @__dot__ +end diff --git a/test/runtests.jl b/test/runtests.jl index 76bf52cf7..c600d80d9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1603,9 +1603,12 @@ A = view(rand(5,5), 1:3, 1:3) @test A*D == copy(A) * Diagonal(copy(x)) # julia#17623 -@static if VERSION >= v"0.5.0-dev+5509" # To work around unsupported syntax on Julia 0.4 +if VERSION >= v"0.5.0-dev+5509" +# Use include_string to work around unsupported syntax on Julia 0.4 +include_string(""" @test [true, false] .& [true, true] == [true, false] @test [true, false] .| [true, true] == [true, true] +""") end # julia#20022 @@ -1619,3 +1622,91 @@ end @test Compat.TypeUtils.isabstract(AbstractFoo20006) @test Compat.TypeUtils.parameter_upper_bound(ConcreteFoo20006, 1) == Int @test isa(Compat.TypeUtils.typename(Array), TypeName) + +# @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) + + @test X[1:end] == @__dot__ (@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] + + # 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 + + # test @views macro + @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] + @__dot__ 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] + @__dot__ 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] == @__dot__(1 + sqrt($sort(abs(x)))) + @test @__dot__(x^2) == x.^2 + @__dot__ x = 2 + @test x == [2,2,2,2] +end +@test [1,4,9] == @__dot__ let x = [1,2,3]; x^2; end +let x = [1,2,3], y = x + @__dot__ for i = 1:3 + y = y^2 # should convert to y .= y.^2 + end + @test x == [1,256,6561] +end +let x = [1,2,3] + @__dot__ f(x) = x^2 + @test f(x) == [1,4,9] +end From 643984cca0cef21ecb1663e4ba61a021cb857e7b Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Fri, 3 Feb 2017 16:11:02 -0500 Subject: [PATCH 2/4] work around julia#20247 bug in Julia 0.5 --- src/arraymacros.jl | 5 ++--- test/runtests.jl | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/arraymacros.jl b/src/arraymacros.jl index b686d84fb..19f5ae534 100644 --- a/src/arraymacros.jl +++ b/src/arraymacros.jl @@ -1,9 +1,8 @@ # Julia 0.6 macros to aid in vectorization: @view, @views, @__dot__ (@.), # backported from Julia 0.6. -if isdefined(Base, :replace_ref_end!) - import Base.replace_ref_end! -else +# 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) diff --git a/test/runtests.jl b/test/runtests.jl index c600d80d9..54e7f7b26 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1638,15 +1638,19 @@ let X = reshape(1:24,2,3,4), Y = 4:-1:1 @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] - # 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 + # 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) + if VERSION < v"0.5" + # 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] + # 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 From fcb78ecb2efa0ce32c876e4f595692b3a1a45aa7 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 4 Feb 2017 11:35:27 -0500 Subject: [PATCH 3/4] don't call at-compat from at-views, add at-dotcompat to combine at-dot and at-compat --- README.md | 6 +++--- src/arraymacros.jl | 13 +++++++++++-- test/runtests.jl | 47 ++++++++++++++++++++++++++++++++++++---------- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8992c01bd..c77662c68 100644 --- a/README.md +++ b/README.md @@ -84,13 +84,13 @@ 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]). Using `@views` - automatically implies `@compat`. + `@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. - Using `@__dot__` automatically implies `@compat`. + 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. * `foreach`, similar to `map` but when the return value is not needed ([#13744]) diff --git a/src/arraymacros.jl b/src/arraymacros.jl index 19f5ae534..0bd32c425 100644 --- a/src/arraymacros.jl +++ b/src/arraymacros.jl @@ -169,7 +169,7 @@ if !isdefined(Base, Symbol("@views")) end macro views(x) - esc(_compat(_views(replace_ref_end!(x)))) + esc(_views(replace_ref_end!(x))) end export @views end @@ -214,7 +214,16 @@ if !isdefined(Base, Symbol("@__dot__")) end end macro __dot__(x) + esc(__dot__(x)) + end + macro dotcompat(x) esc(_compat(__dot__(x))) end - export @__dot__ + 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 end diff --git a/test/runtests.jl b/test/runtests.jl index 54e7f7b26..73093c0f9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1627,7 +1627,7 @@ end let X = reshape(1:24,2,3,4), Y = 4:-1:1 @test isa(@view(X[1:3]), SubArray) - @test X[1:end] == @__dot__ (@view X[1:end]) # test compatibility of @. and @view + @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] @@ -1654,7 +1654,7 @@ let X = reshape(1:24,2,3,4), Y = 4:-1:1 end # test @views macro - @views let f!(x) = x[1:end-1] .+= x[2:end].^2 + @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] @@ -1669,7 +1669,7 @@ let X = reshape(1:24,2,3,4), Y = 4:-1:1 @test x == [5,6,35,4] x[Y[2:3]] .= 7:8 @test x == [5,8,7,4] - @__dot__ x[(3,)..., ()...] += 3 # @. should convert to .+=, test compatibility with @views + @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: @@ -1677,7 +1677,34 @@ let X = reshape(1:24,2,3,4), Y = 4:-1:1 @test x == [5,8,10,9] && i == [4] x[push!(i,3)[end]] += 2 @test x == [5,8,12,9] && i == [4,3] - @__dot__ x[3:end] = 0 # make sure @. works with end expressions in @views + @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) @@ -1698,19 +1725,19 @@ end # @. (@__dot__) tests, from base: let x = [4, -9, 1, -16] - @test [2, 3, 4, 5] == @__dot__(1 + sqrt($sort(abs(x)))) - @test @__dot__(x^2) == x.^2 - @__dot__ x = 2 + @test [2, 3, 4, 5] == @dotcompat(1 + sqrt($sort(abs(x)))) + @test @dotcompat(x^2) == x.^2 + @dotcompat x = 2 @test x == [2,2,2,2] end -@test [1,4,9] == @__dot__ let x = [1,2,3]; x^2; end +@test [1,4,9] == @dotcompat let x = [1,2,3]; x^2; end let x = [1,2,3], y = x - @__dot__ for i = 1:3 + @dotcompat for i = 1:3 y = y^2 # should convert to y .= y.^2 end @test x == [1,256,6561] end let x = [1,2,3] - @__dot__ f(x) = x^2 + @dotcompat f(x) = x^2 @test f(x) == [1,4,9] end From 3dac4db8fe17a971b66cbb122e21f55dd85a4783 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Sat, 4 Feb 2017 11:38:07 -0500 Subject: [PATCH 4/4] more precise check for buggy at-view --- test/runtests.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 73093c0f9..12a65db1f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1639,8 +1639,8 @@ let X = reshape(1:24,2,3,4), Y = 4:-1:1 @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) - if VERSION < v"0.5" + # 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]