mapAttrsRecursiveCond generates an attribute path while iterating, but
we completely ignore it. We also don't need to run the isAttrs check,
since isOption will check if `v._type == "option"`, and ? runs a
typecheck internally. These combined justify the inlining in my opinion.
isDisabled gets the list of disabledModules ready when it's only given
two attributes, so when it's passed the third argument, it just has to
call `elem` on its precomputed list. This was being used in the graph
right below, but now in module filtering.
Each value passed to a function or primop is passed as a thunk, so `head
defs` will only be evaluated twice if we're throwing an error. Validated
with `builtins.trace`.
The previous comment said "The let-bindings above are lazy and thus
never forced on this branch". Which is true, they're not forced as
values, but they still need to be allocated as thunks. We can avoid
creating them in the first place by moving them down.
throwIfNot sends our error message through a function call, even if the
error condition doesn't trigger. This requires a lot of thunk allocation
that can be easily avoided.
warnIf sends our warning message through a function call, even if the
warning condition doesn't trigger. This requires a lot of thunk
allocation that can be easily avoided.
- When `evalModules` is called without deprecated `args`/`check` parameters (the common case), avoid `modules ++ []` which copies the entire module list for no reason.
- `collectModules` was calling `collectStructuredModules` twice with identical arguments — once for `filterModules` and once for `toGraph`. Share the result via a `let` binding.
- Replace `zipAttrsWith (n: v: v)` with `zipAttrs`
- Replace `concatMap` with `filter`+`map` in `filterOverrides'`
NixOS minimal eval improvement: -5.0% list_concats, -1.2%
nrFunctionCalls, -0.7% nrThunks, -1.0% envs, -1.5% list_bytes.
Replace `(optional (opt ? default) { ... }) ++ defs` with a direct
if/then/else. When the option has no default, this skips both the
list allocation and the ++ concat entirely. Most options do have
defaults, but even then we avoid the `optional` function call overhead
(closure + env allocation).
On NixOS minimal eval: -45K function calls (-0.93%), -3.7K list
concats (-1.93%).
The vast majority of option definitions reaching mergeDefinitions are
singletons whose value carries no _type wrapper (mkIf/mkMerge/
mkOverride/mkOrder). For those, the dischargeProperties ->
filterOverrides' -> sortProperties pipeline is a no-op yet still costs
~125k function calls and ~70k thunks on a minimal NixOS system eval.
Detect this case up front and reuse the input list as
defsFinal'.values, keeping the addErrorContext around the value probe
so error traces still point at the defining file. highestPrio is
defaultOverridePriority by construction.
A single-fold filterOverrides' was prototyped but allocates an attrset
per definition and regressed gc.totalBytes; with the fast path in
place that function is no longer hot enough to matter.
NIX_SHOW_STATS, minimal `nix-instantiate ./nixos -A system`:
nrFunctionCalls 5490285 -> 5365172 (-125113)
nrThunks 8182569 -> 8112150 (-70419)
gc.totalBytes 640.0M -> 635.2M (-4.8M)
If an attrset option was given a definition like:
{ system = lib.mkIf true false; }
Before this change, we get the non-explanatory error message:
error: expected a set but found a Boolean: true
With the stack trace having nothing to do with the module involved.
After this change, we get the better:
error: In module `[...]', you're trying to define a value of type `bool' [...]
Which names the actual module involved.
- concrete types start with uppercase: Int, String, Bool, Derivation,
etc.
- type variables start with lowercase: a, b, etc.
- list:
- use `[x]` for homogeneous lists instead of `List x` or `[ x ]`
- use `List` for heterogeneous lists (not that common in `lib`)
- attr:
- use `AttrSet` for a generic attribute set type
- use `{ key1 :: Type1; key2 :: Type2; ... }` for adding signatures
for known attribute names and types
- use `{ key1 = value1; key2 = value2; ... }` for adding attributes
with known literals
- end with an ellipsis (`...`) if the set can contain unknown
attributes
- use `{ [String] :: x }` if all the attributes has the same type `x`
- prefer `Any` over `a` if the latter is not reused