Why does `import` work on a set representing a flake?

From Flakes - NixOS Wiki I’ve learned that import builtin function can be called not just on a path but also on a set representing a flake.

I’d like to ask: By what kind of sorcery does that work?!
Is there some implicit coercion from flake set to path?

And it really does work. For example here is my little unremarkable flake which really works:

  description = "A very basic flake";

  inputs = {
    flake-utils.url = "github:numtide/flake-utils";
    python-clang.url = "/home/michal/Projects/NixOS/python-clang-overlay";

  outputs = { self, nixpkgs, flake-utils, python-clang }:
    flake-utils.lib.eachDefaultSystem (system:
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ python-clang.overlay ];
      rec {
        packages = {
          cymbal = pkgs.callPackage ./cymbal.nix { };
          pywrap = pkgs.callPackage ./pywrap.nix { cymbal = packages.cymbal; };
        defaultPackage = packages.pywrap;

        devShells.installed = import ./shell-installed.nix { inherit pkgs; pywrap = packages.pywrap; };

But the nixpkgs is a set and by all my experience and by what is written in documentation Built-in Functions the import takes a path not a set. :scream:

I can also do nix repl in the flake’s directory:

nix-repl> :lf .
Added 11 variables.
nix-repl> x = import inputs.flake-utils

without error. So it really really works. But why?

1 Like

If an attribute set has an outPath attribute, it will be used when something tries to stringify it:

nix-repl> inputs.nixpkgs.outPath          

nix-repl> "${inputs.nixpkgs}"

nix-repl> builtins.toString { outPath = ./foo.nix; }

I didn’t know that import can also take a string but I did know that sets with outPath can be used as strings. So import’s ability to take a string was the missing link. Thank you.