Modular, context-aware and aspect-oriented dendritic Nix configurations. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ den.oeiuwq.com
configurations den dendritic nix aspect oriented
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: parametric - preserve meta on materialized parametric results (#440)

Parametric sub-aspects defined as bare functions
(`foo._.sub = { host, ... }: { nixos = ...; }`) lost their `foo`
provider prefix in the resolved aspect tree: when the functor was
invoked by applyDeep, the raw user return (`{ nixos = ...; }`) didn't
carry the sub's `meta.provider = ["foo"]`, so downstream
aspectPath-based queries saw the aspect as `["sub"]` instead of
`["foo","sub"]`.

This was invisible as long as consumers only extracted classModule via
`aspect.${class}` without caring about aspectPath — see
deadbugs/issue-413-provider-sub-aspect-function, still passes. It
surfaces the moment anything compares aspect references by their full
`meta.provider ++ [name]` identity.

Fix: add a small `carryMeta fn result` helper in applyDeep that copies
`meta` from the originating fn onto the materialized result when the
result doesn't already carry its own. Applied to both:

- the outer `take` result (direct-include path)
- the inner re-recursion on bare-result.includes (parametric-parent
path, where a functor returns `{ includes = [foo._.sub] }`)

`isBareResult` is checked against the pre-carryMeta value so the
bare-result recursion branch still fires — carrying meta would otherwise
mask it and skip the sub-recursion.

Scope kept narrow to applyDeep rather than touching `take.carryAttrs`
globally: a broader fix there affects every take.* call site (including
statics.nix and resolve.apply) and breaks unrelated tests that depend on
take results being metaless.

authored by

Jason Bowman and committed by
GitHub
5870c478 8a34e922

+20 -5
+20 -5
nix/lib/parametric.nix
··· 20 20 } 21 21 // extra; 22 22 23 + # Copy fn's meta onto a materialized functor result. Preserves 24 + # meta.provider so provider sub-aspects keep their full aspectPath 25 + # (e.g. ["foo","sub"]) after the user fn is invoked and returns a 26 + # raw attrset that would otherwise drop it. 27 + carryMeta = 28 + fn: result: 29 + if builtins.isAttrs result && fn ? meta && !(result ? meta) then 30 + result // { inherit (fn) meta; } 31 + else 32 + result; 33 + 23 34 # When takeFn succeeds and returns a result with sub-includes, 24 35 # also try to resolve those sub-includes with takeFn. This handles 25 36 # provider sub-aspect functions nested inside include results: ··· 28 39 applyDeep = 29 40 takeFn: ctx: fn: 30 41 let 31 - r = takeFn fn ctx; 42 + rRaw = takeFn fn ctx; 43 + r = carryMeta fn rRaw; 32 44 # Bare provider results carry only includes (+ name from carryAttrs). 33 45 # Results from withOwn/withIdentity have meta; deferred deepRecurse 34 46 # wrappers have __functor. Re-resolving either would double-apply 35 47 # context and duplicate modules. 36 - isBareResult = builtins.isAttrs r && r ? includes && !(r ? meta) && !(r ? __functor); 48 + # Checked against rRaw (pre-carryMeta) so the recursion branch still fires. 49 + isBareResult = builtins.isAttrs rRaw && rRaw ? includes && !(rRaw ? meta) && !(rRaw ? __functor); 37 50 in 38 - if r == { } then 39 - r 51 + if rRaw == { } then 52 + rRaw 40 53 else if isBareResult then 41 54 r 42 55 // { ··· 46 59 # class configs must be picked up later by the static resolve pass. 47 60 # A user-provided provider fn (e.g. { host, ... }: { nixos = ...; }) 48 61 # has host in functionArgs; canTake.upTo fires and we materialize it. 49 - includes = map (sub: if canTake.upTo ctx sub then take.upTo sub ctx else sub) r.includes; 62 + includes = map ( 63 + sub: if canTake.upTo ctx sub then carryMeta sub (take.upTo sub ctx) else sub 64 + ) rRaw.includes; 50 65 } 51 66 else 52 67 r;