Skip to content

Commit e0cf6b8

Browse files
authored
Add documentation for types.nix (#28)
1 parent d0a226c commit e0cf6b8

File tree

1 file changed

+122
-9
lines changed

1 file changed

+122
-9
lines changed

nix/types.nix

Lines changed: 122 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
11
lib:
22
let
3+
# Import the resolve function which handles aspect resolution and dependency injection
34
resolve = import ./resolve.nix lib;
45

6+
# Top-level aspects container type
7+
# This is the entry point for defining all aspects in a flake
8+
# Structure: aspects.<aspectName> = { ... }
9+
# Makes the entire aspects config available as 'aspects' in module args
10+
# allowing cross-referencing between aspects
511
aspectsType = lib.types.submodule (
612
{ config, ... }:
713
{
8-
freeformType = lib.types.attrsOf (lib.types.either aspectSubmoduleAttrs providerType);
14+
# Allow arbitrary aspect definitions as attributes
15+
# Each aspect can be either:
16+
# - An aspect submodule (aspectSubmoduleAttrs)
17+
# - A provider function (providerType)
18+
freeformType = lib.types.lazyAttrsOf (lib.types.either aspectSubmoduleAttrs providerType);
19+
# Inject the aspects config into _module.args for cross-referencing
920
config._module.args.aspects = config;
1021
}
1122
);
1223

13-
# checks the argument names to be those of a provider function:
24+
# Type checker for provider functions with specific argument patterns
25+
# Valid provider function signatures:
26+
# 1. { class } => aspect-object
27+
# 2. { aspect-chain } => aspect-object
28+
# 3. { class, aspect-chain } => aspect-object
29+
# 4. { class, ... } => aspect-object (with other ignored args)
30+
# 5. { aspect-chain, ... } => aspect-object (with other ignored args)
1431
#
15-
# { class, aspect-chain } => aspect-object
16-
# { class, ... } => aspect-object
17-
# { aspect-chain, ... } => aspect-object
32+
# This ensures that provider functions receive the proper context when invoked
1833
functionToAspect = lib.types.addCheck (lib.types.functionTo aspectSubmodule) (
1934
f:
2035
let
@@ -29,12 +44,30 @@ let
2944
classOnly || chainOnly || both
3045
);
3146

47+
# Provider functions can be:
48+
# 1. A function taking { class, aspect-chain } and returning an aspect (functionToAspect)
49+
# 2. A function taking parameters and returning another provider (curried)
50+
# This allows for parametric aspects and lazy evaluation
3251
functionProviderType = lib.types.either functionToAspect (lib.types.functionTo providerType);
52+
53+
# Provider type allows three forms:
54+
# 1. A function provider (functionProviderType)
55+
# 2. An aspect configuration (aspectSubmodule)
56+
# This enables both immediate aspect definitions and deferred/parametric ones
3357
providerType = lib.types.either functionProviderType aspectSubmodule;
3458

59+
# Additional validation for aspect submodules to ensure they're not mistyped functions
60+
# An aspectSubmoduleAttrs is either:
61+
# - Not a function at all (plain attribute set)
62+
# - A function with submodule-style arguments (lib, config, options, aspect)
63+
# This prevents accidentally treating provider functions as aspect configs
3564
aspectSubmoduleAttrs = lib.types.addCheck aspectSubmodule (
3665
m: (!builtins.isFunction m) || (isAspectSubmoduleFn m)
3766
);
67+
68+
# Helper to identify if a function is a submodule-style function
69+
# Submodule functions take args like { lib, config, options, aspect, ... }
70+
# Returns true if the function accepts at least one of these special args
3871
isAspectSubmoduleFn =
3972
m:
4073
lib.pipe m [
@@ -49,49 +82,103 @@ let
4982
(x: lib.length x > 0)
5083
];
5184

85+
# Special type that accepts any value but always merges to null
86+
# Used for internal computed values that shouldn't be serialized
87+
# This prevents type errors when values don't have proper types
5288
ignoredType = lib.types.mkOptionType {
5389
name = "ignored type";
5490
description = "ignored values";
5591
merge = _loc: _defs: null;
5692
check = _: true;
5793
};
5894

95+
# Core aspect definition type
96+
# Each aspect represents a reusable configuration module that can:
97+
# - Define configuration for multiple "classes" (e.g., nixos, home-manager, darwin)
98+
# - Include other aspects as dependencies
99+
# - Provide sub-aspects for selective composition
100+
# - Be parametrized via __functor
59101
aspectSubmodule = lib.types.submodule (
60102
{
61103
name,
62104
config,
63105
...
64106
}:
65107
{
108+
# Allow arbitrary class configurations (e.g., nixos, home-manager, etc.)
109+
# Each class maps to a deferred module that will be resolved later
66110
freeformType = lib.types.attrsOf lib.types.deferredModule;
111+
112+
# Make the aspect config available as 'aspect' in module args
113+
# This allows modules within the aspect to reference their own aspect
67114
config._module.args.aspect = config;
115+
116+
# Create "_" as a shorthand alias for "provides"
117+
# Allows writing: aspect._.foo instead of aspect.provides.foo
118+
# This improves ergonomics for the common case of defining sub-aspects
68119
imports = [ (lib.mkAliasOptionModule [ "_" ] [ "provides" ]) ];
120+
121+
# Human-readable aspect name, defaults to the attribute name
122+
# Used in aspect-chain tracking and for display purposes
69123
options.name = lib.mkOption {
70124
description = "Aspect name";
71125
default = name;
72126
type = lib.types.str;
73127
};
128+
129+
# Optional description for documentation purposes
130+
# Defaults to a generic description using the aspect name
74131
options.description = lib.mkOption {
75132
description = "Aspect description";
76133
default = "Aspect ${name}";
77134
type = lib.types.str;
78135
};
136+
137+
# Dependencies: list of other providers this aspect includes
138+
# During resolution, included aspects are merged with this aspect
139+
# Includes can be:
140+
# - Direct aspect references: aspects.otherAspect
141+
# - Parametrized providers: aspects.other.provides.foo "param"
142+
# - Functorized aspects: aspects.otherAspect { param = value; }
143+
# The resolution order matters for module merging semantics
79144
options.includes = lib.mkOption {
80145
description = "Providers to ask aspects from";
81146
type = lib.types.listOf providerType;
82147
default = [ ];
83148
};
149+
150+
# Sub-aspects that can be selectively included by other aspects
151+
# This allows aspects to expose multiple named variants or components
152+
# Creates a fixpoint where provides can reference the aspects in their scope
153+
# The provides scope gets its own 'aspects' arg for internal cross-referencing
84154
options.provides = lib.mkOption {
85155
description = "Providers of aspect for other aspects";
86156
default = { };
87157
type = lib.types.submodule (
88158
{ config, ... }:
89159
{
160+
# Allow arbitrary sub-aspect definitions
90161
freeformType = lib.types.attrsOf providerType;
162+
# Make the provides scope available as 'aspects' for fixpoint references
163+
# This enables provides.foo to reference provides.bar via aspects.bar
91164
config._module.args.aspects = config;
92165
}
93166
);
94167
};
168+
169+
# Functor enables aspects to be callable like functions
170+
# When defined, calling aspect { param = value; } invokes the functor
171+
# The functor receives:
172+
# 1. The aspect config itself
173+
# 2. The parameters passed by the caller (which must include class and aspect-chain)
174+
# This allows aspects to be parametrized and context-aware
175+
#
176+
# The default functor:
177+
# - Takes the aspect config
178+
# - Takes { class, aspect-chain } parameters
179+
# - Returns the aspect unchanged (identity function with parameter access)
180+
# - The weird `if true || (class aspect-chain) then` is to silence nixf-diagnose
181+
# about unused variables while ensuring they're in scope
95182
options.__functor = lib.mkOption {
96183
internal = true;
97184
visible = false;
@@ -100,17 +187,40 @@ let
100187
default =
101188
aspect:
102189
{ class, aspect-chain }:
103-
# silence nixf-diagnose :/
190+
# silence nixf-diagnose about unused variables
104191
if true || (class aspect-chain) then aspect else aspect;
105192
};
193+
194+
# Convenience accessor: aspect.modules.<class> automatically resolves
195+
# This is equivalent to calling aspect.resolve { class = "<class>"; }
196+
# Returns a map of all classes with their resolved modules
197+
#
198+
# For example: aspect.modules.nixos == aspect.resolve { class = "nixos"; }
199+
#
200+
# This is computed lazily and uses ignoredType to avoid serialization issues
106201
options.modules = lib.mkOption {
107202
internal = true;
108203
visible = false;
109204
readOnly = true;
110205
description = "resolved modules from this aspect";
111206
type = ignoredType;
207+
# For each class in the aspect, resolve it with empty aspect-chain
112208
apply = _: lib.mapAttrs (class: _: config.resolve { inherit class; }) config;
113209
};
210+
211+
# Main resolution function that converts an aspect into a nixpkgs module
212+
# Takes { class, aspect-chain } and returns a resolved module
213+
# - class: The target configuration class (e.g., "nixos", "home-manager")
214+
# - aspect-chain: List of aspects traversed so far (for tracking dependencies)
215+
#
216+
# The resolution process:
217+
# 1. Invokes the aspect config with class and aspect-chain parameters
218+
# This triggers the __functor if defined, allowing parametrization
219+
# 2. Calls resolve.nix to recursively resolve includes
220+
# 3. Returns a module with imports from the aspect and its dependencies
221+
#
222+
# The aspect-chain parameter allows aspects to introspect their dependency tree
223+
# This is useful for debugging and for aspects that need to know their context
114224
options.resolve = lib.mkOption {
115225
internal = true;
116226
visible = false;
@@ -123,6 +233,9 @@ let
123233
class,
124234
aspect-chain ? [ ],
125235
}:
236+
# Invoke config (the aspect) with class and aspect-chain parameters
237+
# This works because config is wrapped with __functor via the submodule system
238+
# Then pass the result to resolve for dependency resolution
126239
resolve class aspect-chain (config {
127240
inherit class aspect-chain;
128241
});
@@ -133,8 +246,8 @@ let
133246
in
134247
{
135248
inherit
136-
aspectsType
137-
aspectSubmodule
138-
providerType
249+
aspectsType # Main entry point for flake.aspects
250+
aspectSubmodule # Individual aspect definition type
251+
providerType # Type for provider expressions
139252
;
140253
}

0 commit comments

Comments
 (0)