Skip to content

Commit f225d82

Browse files
committed
fix hygiene and side-effects problems in at-view
1 parent 876549f commit f225d82

File tree

2 files changed

+38
-14
lines changed

2 files changed

+38
-14
lines changed

base/subarray.jl

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -333,25 +333,32 @@ should transform to
333333
A[B[endof(B)]]
334334
335335
"""
336-
function replace_ref_end!(ex,withex=nothing)
336+
replace_ref_end!(ex) = replace_ref_end_!(ex, nothing)[1]
337+
# replace_ref_end_!(ex,withex) returns (new ex, whether withex was used)
338+
function replace_ref_end_!(ex, withex)
339+
used_withex = false
337340
if isa(ex,Symbol) && ex == :end
338341
withex === nothing && error("Invalid use of end")
339-
return withex
342+
return withex, true
340343
elseif isa(ex,Expr)
341344
if ex.head == :ref
342-
S = ex.args[1] = replace_ref_end!(ex.args[1],withex)
345+
ex.args[1], used_withex = replace_ref_end_!(ex.args[1],withex)
346+
S = isa(ex.args[1],Symbol) ? ex.args[1]::Symbol : gensym(:S) # temp var to cache ex.args[1] if needed
347+
used_S = false # whether we actually need S
343348
# new :ref, so redefine withex
344349
nargs = length(ex.args)-1
345350
if nargs == 0
346-
return ex
351+
return ex, used_withex
347352
elseif nargs == 1
348353
# replace with endof(S)
349-
ex.args[2] = replace_ref_end!(ex.args[2],:(Base.endof($S)))
354+
ex.args[2], used_S = replace_ref_end_!(ex.args[2],:($endof($S)))
350355
else
351356
n = 1
352357
J = endof(ex.args)
353358
for j = 2:J-1
354-
exj = ex.args[j] = replace_ref_end!(ex.args[j],:(Base.size($S,$n)))
359+
exj, used = replace_ref_end_!(ex.args[j],:($size($S,$n)))
360+
used_S |= used
361+
ex.args[j] = exj
355362
if isa(exj,Expr) && exj.head == :...
356363
# splatted object
357364
exjs = exj.args[1]
@@ -364,16 +371,23 @@ function replace_ref_end!(ex,withex=nothing)
364371
n += 1
365372
end
366373
end
367-
ex.args[J] = replace_ref_end!(ex.args[J],:(Base.trailingsize($S,$n)))
374+
ex.args[J], used = replace_ref_end_!(ex.args[J],:($trailingsize($S,$n)))
375+
used_S |= used
376+
end
377+
if used_S && S !== ex.args[1]
378+
S0 = ex.args[1]
379+
ex.args[1] = S
380+
ex = Expr(:let, ex, :($S = $S0))
368381
end
369382
else
370383
# recursive search
371384
for i = eachindex(ex.args)
372-
ex.args[i] = replace_ref_end!(ex.args[i],withex)
385+
ex.args[i], used = replace_ref_end_!(ex.args[i],withex)
386+
used_withex |= used
373387
end
374388
end
375389
end
376-
ex
390+
ex, used_withex
377391
end
378392

379393
"""
@@ -385,9 +399,15 @@ an assignment (e.g. `@view(A[1,2:end]) = ...`). See also [`@views`](@ref)
385399
to switch an entire block of code to use views for slicing.
386400
"""
387401
macro view(ex)
388-
if isa(ex, Expr) && ex.head == :ref
402+
if Meta.isexpr(ex, :ref)
389403
ex = replace_ref_end!(ex)
390-
Expr(:&&, true, esc(Expr(:call,:(Base.view),ex.args...)))
404+
if Meta.isexpr(ex, :ref)
405+
ex = Expr(:call, view, ex.args...)
406+
else # ex replaced by let ...; foo[...]; end
407+
assert(Meta.isexpr(ex, :let) && Meta.isexpr(ex.args[1], :ref))
408+
ex.args[1] = Expr(:call, view, ex.args[1].args...)
409+
end
410+
Expr(:&&, true, esc(ex))
391411
else
392412
throw(ArgumentError("Invalid use of @view macro: argument must be a reference expression A[...]."))
393413
end

test/subarray.jl

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -473,8 +473,6 @@ end
473473
@test collect(view(view(reshape(1:13^3, 13, 13, 13), 3:7, 6:6, :), 1:2:5, :, 1:2:5)) ==
474474
cat(3,[68,70,72],[406,408,410],[744,746,748])
475475

476-
477-
478476
# tests @view (and replace_ref_end!)
479477
X = reshape(1:24,2,3,4)
480478
Y = 4:-1:1
@@ -494,10 +492,16 @@ u = (1,2:3)
494492
@test X[(1,)...,(2,)...,2:end] == @view X[(1,)...,(2,)...,2:end]
495493

496494
# test macro hygiene
497-
let size=(x,y)-> error("should not happen")
495+
let size=(x,y)-> error("should not happen"), Base=nothing
498496
@test X[1:end,2,2] == @view X[1:end,2,2]
499497
end
500498

499+
# test that side effects occur only once
500+
let foo = [X]
501+
@test X[2:end-1] == @view (push!(foo,X)[1])[2:end-1]
502+
@test foo == [X, X]
503+
end
504+
501505
# test @views macro
502506
@views let f!(x) = x[1:end-1] .+= x[2:end].^2
503507
x = [1,2,3,4]

0 commit comments

Comments
 (0)