• she/her

Principal engineer at Mercury. I've authored the Dhall configuration language, the Haskell for all blog, and countless packages and keynote presentations.

I'm a midwife to the hidden beauty in everything.

💖 @wiredaemon


discord
Gabriella439
discord server
discord.gg/XS5ZDZ8nnp
location
bay area
private page
cohost.org/newmoon

It all started when I made a small refactor of one our files which I've simplified this to smaller reproduction:

# ./release.nix
{ werror ? true }@args: import ./default.nix args
# ./default.nix
{ werror ? false}: …

… where werror is a flag that controls whether or not we build with -Werror.

The refactor I made was to change this:

# ./release.nix
{ werror ? true }@args: import ./default.nix args

… to something like this:

{ werror ? true }: import ./default.nix { inherit werror; }

… which I would soon learn was NOT the same thing.

To understand why, consider this sample nix repl session:

nix-repl> defaultFile = { werror ? false }: werror

nix-repl> releaseFile = { werror ? true }@args: defaultFile args

nix-repl> releaseFile { }
false

Most people (myself included) would expect the final result to be true, but Nix disagrees! This section of the manual explains how this cursed behavior was apparently working as designed:

Warning

The args@ expression is bound to the argument passed to the function which means that attributes with defaults that aren't explicitly specified in the function call won't cause an evaluation error, but won't exist in args.

For instance

let
  function = args@{ a ? 23, ... }: args;
in
  function {}

will evaluate to an empty attribute set.

Yikes!

So it turned out that our production build was actually building release.nix with -Werror OFF (because it would not explicitly specify werror as an argument, which led it to trigger the above cursed behavior).


You must log in to comment.