Infinite recursion with lib.lists.optionals

Sorry, but I’m not very good at nix so I couldn’t figure this out on my own.

I am encountering ‘infinite recursion’ whenever i use lib.lists.optionals in a default.nix.

It is fixed when I comment out the lines in which I use lib.lists.optionals.

Commenting out the contents of the files that are called doesn’t fix it either.

Commenting out one lib.lists.optionals or the other doesn’t help either.

default.nix:

{
  lib,
  config,
  ...
}: {
  imports = [
    ./pkgsMain.nix
    ./pkgsOptions.nix
  ]
  ++ lib.lists.optionals config.fontPackages.enable [ ./fonts.nix ]
  ++ lib.lists.optionals config.autoUpgrade.enable [ ./autoUpgrade.nix ]; 
}

fonts.nix:

{
  config,
  pkgs,
  ...
}: {
  fonts.packages = (with pkgs; [
    nerdfonts
    noto-fonts
    noto-fonts-cjk
    noto-fonts-emoji
    liberation_ttf
    fira-code
    fira-code-symbols
    mplus-outline-fonts.githubRelease
    dina-font
    proggyfonts
    aileron
  ]) ++ config.fontPackages.extraFonts;
}

autoUpgrade.nix:

{
  config,
  ...
}: {
  system.autoUpgrade = {
    enable = true;
    flake = config.flakePath;
    flags = [
      "--update-input"
      "nixpkgs"
      "-L"
    ];
    dates = "daily";
    operation = "boot";
  };
}

Don’t conditionally import.
Always unconditionally add to imports, and create .enable options if you want to toggle things on and off in a module.
NixOS Manual should help you get started in that respect.

1 Like

Also the module system tutorial:

https://nix.dev/tutorials/module-system/

The reason conditional imports don’t work is because imports are evaluated eagerly by the module system, so you can’t rely on the config argument for its value – that hasn’t been filled with anything else when imports tries to access it, so you get the infinite recursion.

If you have no intuition for how it works, imagine that when evaluating the module, you always want to know the final value of imports before doing anything else. So you look at it, and in your example you‘ll have to get the value of config to continue. But that is supposed to be the final value of evaluating all modules combined. We don’t need to know any details here, except that the rule for evaluating each module is to figure out its imports first. In the process of obtaining config you’ll end up looking at your flawed module and run into the loop.

1 Like

This comes up enough that I’ve decided to create a FAQ entry for it.

1 Like