How to fix infinite recursion error?

I’m writing module networking.nix and I have infinite recursion error. Any help on how to fix it and additional information on what can I improve in my code would be nice.

hosts/modules/desktop/networking.nix

{ config, lib, ... }:

{
  options = {
    nixosConfig.networking.wired.enable = lib.mkEnableOption "wired interface";
    nixosConfig.networking.wired.interface = lib.mkOption {
      type = lib.types.str;
      description = "Wired interface name.";
      example = "enp5s0";
    };

    nixosConfig.networking.wireless.enable =
      lib.mkEnableOption "wireless interface";
    nixosConfig.networking.wireless.interface = lib.mkOption {
      type = lib.types.str;
      description = "Wireless interface name.";
      example = "wlo1";
    };

    nixosConfig.networking.bond.enable = lib.mkOption {
      type = lib.types.bool;
      description = "Whether enable bond interface.";
    };
    nixosConfig.networking.bond.interface = lib.mkOption {
      type = lib.types.str;
      default = "bond0";
      description = "Bond interface name.";
      example = "bond0";
    };
  };

  config = let
    enableWired = config.nixosConfig.networking.wired.enable;
    wiredInterface = config.nixosConfig.networking.wired.interface;

    enableWireless = config.nixosConfig.networking.wireless.enable;
    wirelessInteface = config.nixosConfig.networking.wireless.interface;

    bondInterface = config.nixosConfig.networking.bond.interface;
    enableBonding = enableWired && enableWireless;
  in lib.mkMerge [
    {
      # i'm not going to use networkmanager
      networking.networkmanager.enable = lib.mkForce false;
      networking.useDHCP = false;
      systemd.network.enable = true;
    }
    (lib.mkIf enableWireless {
      networking.supplicant.${wirelessInteface} = {
        userControlled.enable = true;
        driver = "nl80211";
        configFile.writable = true;
        configFile.path = config.sops.secrets.wpaSupplicant.path;
      };
    })
    (if enableBonding then {
      systemd.network.wait-online = {
        anyInterface = true;
        timeout = 0;
      };
      systemd.network.netdevs."10-${bondInterface}" = {
        netdevConfig = {
          Name = bondInterface;
          Kind = "bond";
        };
        bondConfig = {
          Mode = "active-backup";
          PrimaryReselectPolicy = "always";
          MIIMonitorSec = "1s";
        };
      };
      systemd.network.networks."10-${bondInterface}" = {
        matchConfig.Name = bondInterface;
        linkConfig.RequiredForOnline = "routable";
        networkConfig.BindCarrier = [ wiredInterface wirelessInteface ];
        networkConfig.DHCP = "yes";
        networkConfig.UseDomains = "yes";
      };
      systemd.network.networks."20-${bondInterface}-wired" = {
        matchConfig.Name = wiredInterface;
        networkConfig = {
          Bond = bondInterface;
          PrimarySlave = "yes";
        };
      };
      systemd.network.networks."20-${bondInterface}-wireless" = {
        matchConfig.Name = wirelessInteface;
        networkConfig.Bond = bondInterface;
      };
    } else if enableWired then {
      systemd.network.networks."20-wired" = {
        matchConfig.Name = wiredInterface;
        linkConfig.RequiredForOnline = "routable";
        networkConfig.DHCP = "yes";
        networkConfig.UseDomains = "yes";
      };
    } else if enableWireless then {
      systemd.network.networks."25-wireless" = {
        matchConfig.Name = wirelessInteface;
        linkConfig.RequiredForOnline = "routable";
        networkConfig.DHCP = "yes";
        networkConfig.UseDomains = "yes";
      };
    } else {
      systemd.wait-online = {
        anyInterface = true;
        timeout = 0;
      };
    })
  ];
}

hosts/<my-hostname>/default.nix

{
  imports = [
    ../modules/laptop
    ./hardware-configuration.nix
    ./xorg.nix
  ];

  networking.hostName = "<my-hostname>";
  nixosConfig.networking = {
    wired.interface = "enp5s0";
    wireless.interface = "wlo1";
  };
}

sudo nixos-rebuild switch --flake .#<my-hostname> --show-trace

error:
       … while calling the 'seq' builtin
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:359:18:
          358|         options = checked options;
          359|         config = checked (removeAttrs config [ "_module" ]);
             |                  ^
          360|         _module = checked (config._module);

       … while evaluating a branch condition
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:295:9:
          294|       checkUnmatched =
          295|         if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [ ] then
             |         ^
          296|           let

       … in the left operand of the AND (&&) operator
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:295:72:
          294|       checkUnmatched =
          295|         if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [ ] then
             |                                                                        ^
          296|           let

       … in the left operand of the AND (&&) operator
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:295:33:
          294|       checkUnmatched =
          295|         if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [ ] then
             |                                 ^
          296|           let

       … while evaluating a branch condition
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:286:9:
          285|         in
          286|         if declaredConfig._module.freeformType == null then
             |         ^
          287|           declaredConfig

       … from call site
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:286:12:
          285|         in
          286|         if declaredConfig._module.freeformType == null then
             |            ^
          287|           declaredConfig

       … while calling anonymous lambda
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/attrsets.nix:1182:17:
         1181|         mapAttrs (
         1182|           name: value:
             |                 ^
         1183|           if isAttrs value && cond value then recurse (path ++ [ name ]) value else f (path ++ [ name ]) value

       … from call site
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/attrsets.nix:1183:85:
         1182|           name: value:
         1183|           if isAttrs value && cond value then recurse (path ++ [ name ]) value else f (path ++ [ name ]) value
             |                                                                                     ^
         1184|         );

       … while calling anonymous lambda
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:273:71:
          272|           # For definitions that have an associated option
          273|           declaredConfig = mapAttrsRecursiveCond (v: !isOption v) (_: v: v.value) options;
             |                                                                       ^
          274|

       … while evaluating the attribute 'value'
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:1083:7:
         1082|     // {
         1083|       value = addErrorContext "while evaluating the option `${showOption loc}':" value;
             |       ^
         1084|       inherit (res.defsFinal') highestPrio;

       … while evaluating the option `_module.freeformType':

       … while evaluating the attribute 'mergedValue'
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:1130:5:
         1129|     # Type-check the remaining definitions, and merge them. Or throw if no definitions.
         1130|     mergedValue =
             |     ^
         1131|       if isDefined then

       … while evaluating a branch condition
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:1131:7:
         1130|     mergedValue =
         1131|       if isDefined then
             |       ^
         1132|         if all (def: type.check def.value) defsFinal then

       … while evaluating the attribute 'values'
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:1124:9:
         1123|       {
         1124|         values = defs''';
             |         ^
         1125|         inherit (defs'') highestPrio;

       … while evaluating a branch condition
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:1118:11:
         1117|           # Avoid sorting if we don't have to.
         1118|           if any (def: def.value._type or "" == "order") defs''.values then
             |           ^
         1119|             sortProperties defs''.values

       … while calling the 'any' builtin
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:1118:14:
         1117|           # Avoid sorting if we don't have to.
         1118|           if any (def: def.value._type or "" == "order") defs''.values then
             |              ^
         1119|             sortProperties defs''.values

       … while evaluating the attribute 'values'
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:1251:7:
         1250|     {
         1251|       values = concatMap (def: if getPrio def == highestPrio then [ (strip def) ] else [ ]) defs;
             |       ^
         1252|       inherit highestPrio;

       … while calling the 'concatMap' builtin
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:1251:16:
         1250|     {
         1251|       values = concatMap (def: if getPrio def == highestPrio then [ (strip def) ] else [ ]) defs;
             |                ^
         1252|       inherit highestPrio;

       … while calling the 'concatMap' builtin
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:1098:17:
         1097|         # Process mkMerge and mkIf properties.
         1098|         defs' = concatMap (
             |                 ^
         1099|           m:

       … while calling the 'zipAttrsWith' builtin
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:765:30:
          764|       # extract the definitions for each loc
          765|       rawDefinitionsByName = zipAttrsWith (n: v: v) (
             |                              ^
          766|         map (

       … while calling the 'map' builtin
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:766:9:
          765|       rawDefinitionsByName = zipAttrsWith (n: v: v) (
          766|         map (
             |         ^
          767|           module:

       … in the condition of the assert statement
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:733:9:
          732|       checkedConfigs =
          733|         assert all (
             |         ^
          734|           c:

       … while calling the 'all' builtin
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:733:16:
          732|       checkedConfigs =
          733|         assert all (
             |                ^
          734|           c:

       … while calling the 'zipAttrsWith' builtin
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:752:37:
          751|       # an attrset 'name' => list of submodules that define ‘name’.
          752|       pushedDownDefinitionsByName = zipAttrsWith (n: concatLists) (
             |                                     ^
          753|         map (

       … while calling the 'map' builtin
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:753:9:
          752|       pushedDownDefinitionsByName = zipAttrsWith (n: concatLists) (
          753|         map (
             |         ^
          754|           module:

       … in the condition of the assert statement
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:733:9:
          732|       checkedConfigs =
          733|         assert all (
             |         ^
          734|           c:

       … while calling the 'all' builtin
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:733:16:
          732|       checkedConfigs =
          733|         assert all (
             |                ^
          734|           c:

       … while calling the 'concatMap' builtin
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:697:7:
          696|     mergeModules' prefix modules (
          697|       concatMap (
             |       ^
          698|         m:

       … while calling anonymous lambda
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:698:9:
          697|       concatMap (
          698|         m:
             |         ^
          699|         map (config: {

       … while calling the 'map' builtin
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:699:9:
          698|         m:
          699|         map (config: {
             |         ^
          700|           file = m._file;

       … from call site
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:702:13:
          701|           inherit config;
          702|         }) (pushDownProperties m.config)
             |             ^
          703|       ) modules

       … while calling 'pushDownProperties'
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:1173:5:
         1172|   pushDownProperties =
         1173|     cfg:
             |     ^
         1174|     if cfg._type or "" == "merge" then

       … while calling the 'concatMap' builtin
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:1175:7:
         1174|     if cfg._type or "" == "merge" then
         1175|       concatMap pushDownProperties cfg.contents
             |       ^
         1176|     else if cfg._type or "" == "if" then

       … while calling 'pushDownProperties'
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:1173:5:
         1172|   pushDownProperties =
         1173|     cfg:
             |     ^
         1174|     if cfg._type or "" == "merge" then

       … while evaluating a branch condition
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:1174:5:
         1173|     cfg:
         1174|     if cfg._type or "" == "merge" then
             |     ^
         1175|       concatMap pushDownProperties cfg.contents

       … while evaluating a branch condition
         at /nix/store/cxkvq0aj5ipl438gh3hhj65acc8shyb1-source/hosts/modules/desktop/networking.nix:56:6:
           55|     })
           56|     (if enableBonding then {
             |      ^
           57|       systemd.network.wait-online = {

       … in the left operand of the AND (&&) operator
         at /nix/store/cxkvq0aj5ipl438gh3hhj65acc8shyb1-source/hosts/modules/desktop/networking.nix:40:33:
           39|     bondInterface = config.nixosConfig.networking.bond.interface;
           40|     enableBonding = enableWired && enableWireless;
             |                                 ^
           41|   in lib.mkMerge [

       … from call site
         at /nix/store/cxkvq0aj5ipl438gh3hhj65acc8shyb1-source/hosts/modules/desktop/networking.nix:33:19:
           32|   config = let
           33|     enableWired = config.nixosConfig.networking.wired.enable;
             |                   ^
           34|     wiredInterface = config.nixosConfig.networking.wired.interface;

       … while calling anonymous lambda
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:652:15:
          651|       extraArgs = mapAttrs (
          652|         name: _: addErrorContext (context name) (args.${name} or config._module.args.${name})
             |               ^
          653|       ) (functionArgs f);

       … while evaluating the module argument `config' in "/nix/store/cxkvq0aj5ipl438gh3hhj65acc8shyb1-source/hosts/modules/desktop/networking.nix":

       … while evaluating the attribute 'config'
         at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:257:21:
          256|                     options
          257|                     config
             |                     ^
          258|                     specialArgs

       error: infinite recursion encountered
       at /nix/store/hr5pl7m833kkr0zv98iydy7mxyl10qld-source/lib/modules.nix:257:21:
          256|                     options
          257|                     config
             |                     ^
          258|                     specialArgs

You used mkIf correctly everywhere except for enableBonding. That’s the source of the infinite recursion error. The reason for these is that the module system delays the condition evaluation to avoid recursion issues (which in this case requires evaluating config while defining it)

The stack trace tells you even though it’s buried somewhere in the frames:

       … while evaluating a branch condition
         at /nix/store/cxkvq0aj5ipl438gh3hhj65acc8shyb1-source/hosts/modules/desktop/networking.nix:56:6:
           55|     })
           56|     (if enableBonding then {
             |      ^
           57|       systemd.network.wait-online = {

In terms of code style, most modules I’ve seen tend to use a let binding for cfg so they don’t have to repeat config.nixosConfig.networking everywhere. For the options you can also wrap it in nixosConfig.networking = { }; to simplify.

The reason why I used if instead of lib.mkIf for enableBonding is I want to enable bonding only if condition enableWired && enableWireless is meet and prevent evaluating the rest of if conditions if enableBonding is true. If only one interface is enabled, systemd-networkd should be configured differently. lib.mkIf doesn’t have else clause, that’s the reason why I used plain if.

lib.mkIf doesn’t have else argument, I don’t know how to replace if with elegant solution.

Maybe someone knows a more elegant solution, but this is still valid:

lib.mkMerge [
  (lib.mkIf cond { })
  (lib.mkIf (!cond) { })
]
1 Like

This is basically how I implemented my local mkIfElse.

Needs parens but yes. (Function application precedes negation: Operators - Nix 2.32.2 Reference Manual)

1 Like