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: handle mixed function+attrset defs at provides sub-aspects (#464)

## Summary
- When a sub-aspect (`_.sub`) received both a parametric function and a
plain attrset from different modules, `providerType`'s either merge fell
back to `aspectType` which evaluated the function as a NixOS module —
failing with "attribute 'host' missing"
- Add a custom merge on `providerType` that detects mixed defs and
coerces parametric functions to `{ includes = [fn]; }` before merging as
aspects
- Single-def functions keep their identity (preserving `functionArgs`
for namespace export/import per #352)

## Test plan
- [x] `deadbugs-issue-448` tests: mixed function+attrset merge on both
fx and legacy pipelines
- [x] `deadbugs-issue-352` tests still pass (function arg reflection
preserved)
- [x] Full CI suite passes (491/491)

Fixes #448

Co-authored-by: Victor Borja <vborja@apache.org>

authored by

Jason Bowman
Victor Borja
and committed by
GitHub
6310761f 57677768

+124 -1
+37 -1
nix/lib/aspects/types.nix
··· 40 40 ]; 41 41 }; 42 42 43 - providerType = cnf: lib.types.either (providerFnType cnf) (aspectType cnf); 43 + providerType = 44 + cnf: 45 + let 46 + pft = providerFnType cnf; 47 + at = aspectType cnf; 48 + eth = lib.types.either pft at; 49 + in 50 + eth 51 + // { 52 + merge = 53 + loc: defs: 54 + let 55 + hasFns = builtins.any (d: lib.isFunction d.value) defs; 56 + hasNonFns = builtins.any (d: !lib.isFunction d.value) defs; 57 + isMixed = hasFns && hasNonFns; 58 + in 59 + if isMixed then 60 + # Mixed function + attrset defs: coerce parametric functions to 61 + # { includes = [fn]; } so they merge as aspects instead of being 62 + # evaluated as NixOS modules (which would fail on missing host/user args). 63 + at.merge loc ( 64 + map ( 65 + d: 66 + if lib.isFunction d.value && !isSubmoduleFn d.value then 67 + d 68 + // { 69 + value = { 70 + includes = [ d.value ]; 71 + }; 72 + } 73 + else 74 + d 75 + ) defs 76 + ) 77 + else 78 + eth.merge loc defs; 79 + }; 44 80 45 81 aspectType = 46 82 cnf:
+87
templates/ci/modules/features/deadbugs/issue-448-mixed-merge.nix
··· 1 + # Issue #448: attribute 'host' missing when a sub-aspect is defined across 2 + # multiple modules that mix bare parametric functions with plain attrsets. 3 + # 4 + # Root cause: providerType's either merge fell back to aspectType.merge 5 + # for mixed defs, which evaluated the parametric function as a NixOS module 6 + # (wrong context — host isn't in _module.args). 7 + { denTest, ... }: 8 + { 9 + flake.tests.deadbugs-issue-448 = { 10 + 11 + # Mixed function + attrset at same sub-aspect path. 12 + test-mixed-merge-fx = denTest ( 13 + { 14 + den, 15 + lib, 16 + igloo, 17 + ... 18 + }: 19 + { 20 + den.hosts.x86_64-linux.igloo.users.tux = { }; 21 + 22 + imports = [ 23 + { 24 + den.aspects.bar._.sub = 25 + { host, ... }: 26 + { 27 + nixos.networking.hostName = host.hostName; 28 + }; 29 + } 30 + { 31 + den.aspects.bar._.sub.nixos.programs.vim.enable = true; 32 + } 33 + ]; 34 + 35 + den.aspects.igloo.includes = [ den.aspects.bar._.sub ]; 36 + 37 + expr = { 38 + hostname = igloo.networking.hostName; 39 + vim = igloo.programs.vim.enable; 40 + }; 41 + expected = { 42 + hostname = "igloo"; 43 + vim = true; 44 + }; 45 + } 46 + ); 47 + 48 + # Same on legacy pipeline. 49 + test-mixed-merge-legacy = denTest ( 50 + { 51 + den, 52 + lib, 53 + igloo, 54 + ... 55 + }: 56 + { 57 + den.fxPipeline = false; 58 + den.hosts.x86_64-linux.igloo.users.tux = { }; 59 + 60 + imports = [ 61 + { 62 + den.aspects.bar._.sub = 63 + { host, ... }: 64 + { 65 + nixos.networking.hostName = host.hostName; 66 + }; 67 + } 68 + { 69 + den.aspects.bar._.sub.nixos.programs.vim.enable = true; 70 + } 71 + ]; 72 + 73 + den.aspects.igloo.includes = [ den.aspects.bar._.sub ]; 74 + 75 + expr = { 76 + hostname = igloo.networking.hostName; 77 + vim = igloo.programs.vim.enable; 78 + }; 79 + expected = { 80 + hostname = "igloo"; 81 + vim = true; 82 + }; 83 + } 84 + ); 85 + 86 + }; 87 + }