optionalAttrs in module -> infinite recursion with config

optionalAttrs cond as is defined as the following Nix expression:

As a result, the module system does not see the attrs when cond evaluates to false.

You got infinite recursion because in order to get the value of config, you would need to evaluate the optionalAttrs but that depends on the value of config. Nix is a lazy language so you might expect it to still work unless you do something like:

(optionalAttrs cfg.isLinux {
  services.use_builder.isLinux = false;
})

However, Nix is strict in names of attributes in attribute set, so even something like the following will trigger an infinite recursion:

let c = (if builtins.isAttrs c then { bar = 2; } else {}) // { foo = 5; }; in c.foo

On the other hand, lib.mkIf is a module system primitive and will preserve the content passed to it in the Nix value and will only resolve it in the module system evaluation:

nix-repl> :p lib.mkIf false { foo = true; }
{ _type = "if"; condition = false; content = { foo = true; }; }

nix-repl> (lib.modules.evalModules {
  modules = [
    { options.foo = lib.mkEnableOption "test"; }
    (lib.mkIf false { foo = true; })
  ];
}).config
{ foo = false; }

The benefit is that the module system is aware of the content argument of mkIf even when the condition is false but the consequence is that the options defined within need to be declared in the module system and can only have valid values set.

4 Likes