stencilproject/Stencil

Adding dynamic properties on top of the Mirror-values for context variables / Improved support for computed properties

ralfebert opened this issue · 0 comments

I just digged into the topic of how to use computed properties with Stencil because I had this issue in a project of mine.

This was already discussed here:

This bug report was closed because this feature was added:

I was looking for something better compared to the workaround "don't use computed properties, always set regular properties instead". For example, if you want to pass a struct to Stencil like this and use the sum property:

struct Prices {
    
    let prices: [Decimal]
    
    var sum: Decimal {
        prices.reduce(0, +)
    }
    
}

you could use something like this:

struct Prices {
    
    let prices: [Decimal]
    
    var sum: Decimal = 0
    
    mutating func updateSum() {
        self.sum = prices.reduce(0, +)
    }
    
}

I didn't like this solution too much, thus this bug report. The only clean alternative would be manually mapping the data types to Dictionary, which would mean a lot of manual busywork for complex types.

I tinkered with DynamicMemberLookup, you could add the sum property yourself, but then you loose the support for accessing other properties via the Mirror approach. One could copy the Mirror#getValue method from Stencil and use that as fallback, but looking at that method and the nil-handling I thought: no, better not.

I wondered:

Would it make sense to fall back to the Mirror-resolving if DynamicMemberLookup doesn't resolve a value? That way one could add code to add computed properties by manually resolving them.

So you could add something like this:

extension Prices: DynamicMemberLookup {
    subscript(dynamicMember member: String) -> Any? {
        if member == "sum" {
            return self.sum
        }
        return nil
    }
}

and could still access the prices property without manually resolving it.

This currently is not possible, if it implements DynamicMemberLookup, it will not resolve via Mirror anymore:

https://github.com/stencilproject/Stencil/blob/4f222ac85d673f35df29962fc4c36ccfdaf9da5b/Sources/Stencil/Variable.swift#L121:L124

Or maybe an extra protocol like DynamicMemberLookup that allows to add dynamic properties on top of the mirrored ones like that?