Forward properties: private API

Utility functions

PortfolioOptimisers.forward_nonnothing — Function
forward_nonnothing(v, _::Type{T}, pathstr, nodestr) -> Any

Guard one intermediate node of a @forward_properties nested path.

Return v unchanged when it is not nothing; otherwise throw a PropertyPathError naming the receiver type T, the full declared path pathstr, and the nodestr node that resolved to nothing. Called once per intermediate hop in the descent generated for a depth-≥2 locator.

Algorithm

  1. v is nothing: throw a PropertyPathError whose message names pathstr, the type T and the node nodestr.
  2. v is anything else: return v.

The guard runs on the intermediate hops only, so a path whose last hop gives nothing returns that nothing rather than raising. That is deliberate: an absent leaf is a value, and an absent intermediate is a path that cannot be walked.

Related

source
PortfolioOptimisers.forward_flatten_path — Function
forward_flatten_path(expr) -> Vector{Symbol}

Flatten a @forward_properties locator into its path of field symbols.

A bare identifier a becomes [:a]; a dotted expression a.b.c becomes [:a, :b, :c]. Any other expression raises an error.

Algorithm

  1. expr is a Symbol: return the one-element vector holding it.
  2. expr is an Expr with head :. and two arguments:
    1. Read leaf, its second argument, and unwrap a QuoteNode to its value.
    2. leaf is not a Symbol: raise an error naming the leaf.
    3. Call this function again on the first argument, and append leaf to the result.
  3. expr is anything else: raise an error naming the expression.

The recursion of step 2.3 is what makes the path any depth: a.b.c parses as (a.b).c, so the walk descends to the bare name and rebuilds the path from the left.

Related

source
PortfolioOptimisers.forward_walk_expr — Function
forward_walk_expr(
    path,
    struct_name,
    broadcast::Bool
) -> Expr

Build the expression that descends a @forward_properties path (a vector of field symbols) on the receiver x, returning the value at the path.

A depth-1 path is a single getfield. A depth-≥2 path descends hop by hop, guarding every intermediate with forward_nonnothing (keyed on the receiver type struct_name) so a nothing node throws a path-naming PropertyPathError. When broadcast is true, the final hop maps over the penultimate value if it is an AbstractVector (the scalar-or-vector solution case), otherwise it is a plain access.

Algorithm

  1. The path holds one name: return getfield(x, name) and stop. getfield is used rather than getproperty, so the generated Base.getproperty never re-enters itself.
  2. Build pathstr, the whole path joined by dots, for the error message.
  3. Start stmts with __v = getfield(x, first_name).
  4. For each further hop k of the path:
    1. Push __v = forward_nonnothing(__v, struct_name, pathstr, nodestr), where nodestr names the part of the path walked so far.
    2. k is the last hop and broadcast is true: push an assignment that reads the leaf with getproperty. when __v is an AbstractVector, and with getproperty otherwise.
    3. Otherwise: push __v = getproperty(__v, leaf).
  5. Push __v as the value of the block.
  6. Return the statements wrapped in a let block, so __v never escapes into the caller.

Step 4.1 runs before every hop after the first, so the guard covers each intermediate exactly once and never the leaf. Only the leaf of step 4.2 broadcasts, so an intermediate vector is still a path error rather than a silent map.

Related

source