Infinite recursion, when using `super` (`prev`)

Consider the following example:

let
  nixpkgs = import <nixpkgs> {};
  main = nixpkgs.lib;
  res = main.extend ext-1;

  # Works:
  ext-1 = _: super: main.id {result = "123";};

  # Recursion error:
  # ext-1 = _: super: super.id {result = "123";};
in
  res.result

The first ext-1 definition works just fine, but the second one fails with

error: infinite recursion encountered

       at /nix/store/9mxzi6ckgb2wwghqq1xcbddf7q8kacbp-nixos-22.05.2320.3c8a5fa9a69/nixos/lib/default.nix:67:14:

           66|       stringLength sub substring tail trace;
           67|     inherit (self.trivial) id const pipe concat or and bitAnd bitOr bitXor
             |              ^
           68|       bitNot boolToString mergeAttrs flip mapNullable inNixShell isFloat min max
(use '--show-trace' to show detailed location information)

I understand, that you can easily get infinite recursion, if you access self (aka final) because of fixed point evaluation, but I am using super (aka prev), not self here, so I don’t understand, where does the infinite recursion come from.

Looking at this image from the Overlays wiki page, I would expect the main object and the super argument that is passed to ext-1 to be the same exact object, so I don’t understand, why are these two lines different.

Can somebody explain, what have I missed (and how to avoid this problem)?

Hey I actually drew that thing back when self/super was popular.

The main problem is that nixpkgs.lib.id is actually (essentially) self.trivial.id, so super.id depends on self.trivial.id, which eventually depends on self.trivial, which ends up depending on super.id { ... }

The most common way of using overlays like

self: super: {
  foo = ...;
}

doesn’t have this problem because it doesn’t mess up accessing other attributes from self

1 Like

Dramforever explained what is happening, so let me also tell you my rule of thumb about how I decide what thing to use:

  1. Always use final.
  2. Unless you want to overlay what you change into the same attribute name.

So:

final: prev: { myFirefox = final.firefox.override { … }; }

But:

final: prev: { firefox = prev.firefox.override { … }; }

If that is true, why doesn’t the first line also fail? If super is synonymous with main and main is synonymous with nixpkgs.lib, why does replacing super with main fix the infinite recursion.

Yeah, unfortunately in this case I don’t know the list of top level attributes that I want to overwrite ahead of time, so I can’t just list them explicitly.

Because main is already fixed, and not changed by the overlay anymore.

1 Like