In overlays, when to use self vs super?

My conclusion from writing overlays for some years is that the only good reason for using super is to access attributes you want to override, to avoid infinite recursion. This effectively means that the resulting attribute set can be represented with a single fixed-point function self: { ... } and everything can be further overridden without any hidden values being unchangeable. This also makes a lot of sense if you think of overlays as just a way to extend such a fixed-point function (which they are, see also lib.makeExtensible).

Some examples (untested):

self: super: {
  # We override hello here, so `super.hello`
  hello = super.hello.override {
    # We don't override fetchurl here, so `self.fetchurl`
    fetchurl = args: builtins.trace "fetchurl" self.fetchurl args;
  };

  fetchurl = args:
    # We don't override hello here, so `self.hello`
    builtins.trace "fetchurl args, here's hello: ${self.hello}"
    # We override fetchurl here, so `super.fetchurl`
    super.fetchurl args;
}
self: super: {
  # Super because we override `unixtools`
  unixtools = super.unixtools // {
    # We override `hostname`, so `super`
    hostname = super.unixtools.hostname.overrideAttrs (old: {
      # old here is effectively also from super!
      name = "my-${old.name}";
      
      # We don't override `name` here, so `self`
      DERIVATION_NAME = self.unixtools.hostname.name;
    });
  };
}
self: super: {
  # `self` because we don't override `callPackage`
  myPackage = self.callPackage ./some/file {
    src = self.fetchurl { ... };
  };
 
  # `super` because we override `callPackage`
  callPackage = super.callPackage;

  # `super` because we override lib
  lib = super.lib.extend (libself: libsuper: {
    # Also `super` because we override `lib.trivial`
    trivial = libsuper.trivial // {
      # `self` because we don't override `warn`
      id = libself.warn "lib.id accessed"
        # `super` because we override `lib.trivial.id`
        libsuper.trivial.id;
  });
}
6 Likes