Skip to content

Commit 2019180

Browse files
committed
disallow usage of dynamic lenses in VarName
1 parent 3a854e2 commit 2019180

File tree

1 file changed

+73
-27
lines changed

1 file changed

+73
-27
lines changed

src/varname.jl

Lines changed: 73 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,28 @@ x[:,1][2]
3232
struct VarName{sym,T<:Lens}
3333
indexing::T
3434

35-
VarName{sym}(indexing = IdentityLens()) where {sym} =
36-
new{sym,typeof(indexing)}(indexing)
35+
function VarName{sym}(indexing=IdentityLens()) where {sym}
36+
# TODO: Should we completely disallow or just `@warn`?
37+
# TODO: Does this affect performance?
38+
if !is_static_lens(indexing)
39+
error("attempted to construct `VarName` with dynamic lens of type $(nameof(typeof(indexing)))")
40+
end
41+
return new{sym,typeof(indexing)}(indexing)
42+
end
43+
end
44+
45+
"""
46+
is_static_lens(l::Lens)
47+
48+
Return `true` if `l` does not require runtime information to be resolved.
49+
50+
In particular it returns `false` for `Setfield.DynamicLens` and `Setfield.FunctionLens`.
51+
"""
52+
is_static_lens(l::Lens) = is_static_lens(typeof(l))
53+
is_static_lens(::Type{<:Lens}) = false
54+
is_static_lens(::Type{<:Union{PropertyLens, IndexLens, IdentityLens}}) = true
55+
function is_static_lens(::Type{ComposedLens{LO, LI}}) where {LO, LI}
56+
return is_static_lens(LO) && is_static_lens(LI)
3757
end
3858

3959
# A bit of backwards compatibility.
@@ -408,39 +428,52 @@ end
408428
Return `vn` instantiated on `x`, i.e. any runtime information evaluated using `x`.
409429
410430
# Examples
411-
```jldoctest
412-
julia> x = (a = [1.0 2.0;], );
413-
414-
julia> vn = @varname(x.a[1, :])
415-
x.a[1,:]
416-
417-
julia> AbstractPPL.concretize(vn, x)
418-
x.a[1,:]
431+
```jldoctest; setup=:(using Setfield)
419432
420-
julia> vn = @varname(x.a[1, end][:]);
433+
julia> x = (a = [1.0 2.0;], );
421434
422-
julia> AbstractPPL.concretize(vn, x)
435+
julia> AbstractPPL.concretize(@lens(_.a[1, end][:]), x)
423436
x.a[1,2][:]
424437
```
425438
"""
426439
concretize(vn::VarName, x) = VarName(vn, concretize(vn.indexing, x))
427440

428441
"""
429-
@varname(expr[, concretize])
442+
@varname(expr)
430443
431444
A macro that returns an instance of [`VarName`](@ref) given a symbol or indexing expression `expr`.
432445
433446
If `concretize` is `true`, the resulting expression will be wrapped in a [`concretize`](@ref) call.
434-
This is useful if you for example want to ensure that no `Setfield.DynamicLens` is used.
435447
436-
The `sym` value is taken from the actual variable name, and the index values are put appropriately
437-
into the constructor (and resolved at runtime).
448+
Note that expressions involving dynamic indexing, i.e. `begin` and/or `end`, will need to be
449+
resolved as `VarName` only supports non-dynamic indexing as determined by
450+
[`is_static_index`](@ref). See examples below.
438451
439452
## Examples
453+
### Dynamic indexing
454+
```jldoctest
455+
julia> # Dynamic indexing is not allowed in `VarName`
456+
@varname(x[end])
457+
ERROR: UndefVarError: x not defined
458+
[...]
459+
460+
julia> # To be able to resolve `end` we need `x` to be available.
461+
x = randn(2); @varname(x[end])
462+
x[2]
463+
464+
julia> # Note that "dynamic" here refers to usage of `begin` and/or `end`,
465+
# _not_ "information only available at runtime", i.e. the following works.
466+
[@varname(x[i]) for i = 1:length(x)][end]
467+
x[2]
468+
```
469+
470+
### General indexing
471+
472+
Under the hood Setfield.jl's `Lens` are used for the indexing:
440473
441474
```jldoctest
442475
julia> @varname(x).indexing
443-
()
476+
(@lens _)
444477
445478
julia> @varname(x[1]).indexing
446479
(@lens _[1])
@@ -455,29 +488,42 @@ julia> @varname(x[1,2][1+5][45][3]).indexing
455488
(@lens _[1, 2][6][45][3])
456489
```
457490
491+
This also means that we support property access:
492+
493+
```jldoctest
494+
julia> @varname(x.a).indexing
495+
(@lens _.a)
496+
497+
julia> @varname(x.a[1]).indexing
498+
(@lens _.a[1])
499+
500+
julia> x = (a = [(b = rand(2), )], ); @varname(x.a[1].b[end]).indexing
501+
(@lens _.a[1].b[2])
502+
```
503+
458504
!!! compat "Julia 1.5"
459505
Using `begin` in an indexing expression to refer to the first index requires at least
460506
Julia 1.5.
461507
"""
462-
macro varname(expr::Union{Expr,Symbol}, concretize::Bool = false)
463-
return varname(expr, concretize)
508+
macro varname(expr::Union{Expr,Symbol})
509+
return varname(expr)
464510
end
465511

466-
varname(sym::Symbol, concretize::Bool = false) =
467-
:($(AbstractPPL.VarName){$(QuoteNode(sym))}())
468-
function varname(expr::Expr, concretize::Bool = false)
512+
varname(sym::Symbol) = :($(AbstractPPL.VarName){$(QuoteNode(sym))}())
513+
function varname(expr::Expr)
469514
if Meta.isexpr(expr, :ref) || Meta.isexpr(expr, :.)
470515
# Split into object/base symbol and lens.
471516
sym_escaped, lens = Setfield.parse_obj_lens(expr)
472517
# Setfield.jl escapes the return symbol, so we need to unescape
473518
# to call `QuoteNode` on it.
474519
sym = drop_escape(sym_escaped)
475520

476-
return if concretize && Setfield.need_dynamic_lens(expr)
477-
:($(AbstractPPL.concretize)(
478-
$(AbstractPPL.VarName){$(QuoteNode(sym))}($lens),
479-
$sym_escaped,
480-
))
521+
return if Setfield.need_dynamic_lens(expr)
522+
:(
523+
$(AbstractPPL.VarName){$(QuoteNode(sym))}(
524+
$(AbstractPPL.concretize)($lens, $sym_escaped)
525+
)
526+
)
481527
else
482528
:($(AbstractPPL.VarName){$(QuoteNode(sym))}($lens))
483529
end

0 commit comments

Comments
 (0)