libexpr/builtins: Document scopedImport

This also allows removing a static initialization of a primop.

Co-authored-by: Eelco Dolstra <edolstra@gmail.com>
Change-Id: I74da68205630f658b239e4327bf5e27bfc1b91da
This commit is contained in:
Tom Hubrecht
2025-11-20 09:25:10 +00:00
co-authored by Eelco Dolstra
parent 2a308bca00
commit f01555105a
3 changed files with 81 additions and 7 deletions
+80
View File
@@ -0,0 +1,80 @@
---
name: scopedImport
implementation: "[](EvalState & state, Value ** args, Value & v) { import(state, *args[1], args[0], v); }"
args: [scope, path]
renameInGlobalScope: false
---
Functions like [`import`](#builtins-import) with the exceptions that
it takes a `scope`, which is a set of attributes to be added to the
lexical scope of the expression.
This essentially allows overriding the ambient builtin variables.
For example, if `foo.nix` is a file containing the following content:
```nix
x
```
then the following expression
```nix
scopedImport { x = 1; } ./foo.nix
```
will evaluate to `1`.
This allows removing function arguments specifications in nix expressions,
for example, a package definition `bar.nix`:
```nix
{ stdenv, fetchurl, libfoo }:
stdenv.mkDerivation { ... buildInputs = [ libfoo ]; }
```
can be rewritten as:
```nix
stdenv.mkDerivation { ... buildInputs = [ libfoo ]; }
```
and imported via:
```nix
bar = scopedImport pkgs ./bar.nix;
```
which remove some duplication of code.
Another application is overriding builtin functions or constants, e.g. to
trace all calls to `map`, one can do:
```nix
let
overrides = {
map = f: xs: builtins.trace "map called!" (map f xs);
# Ensure that our override gets propagated by calls to
# import/scopedImport.
import = fn: scopedImport overrides fn;
scopedImport = attrs: fn: scopedImport (overrides // attrs) fn;
# Also update builtins.
builtins = builtins // overrides;
};
in scopedImport overrides ./bla.nix
```
Similarly, one can simply extend the set of builtin functions.
> **Warning**
>
> A downside of using `scopedImport` is that it bypasses the evaluation cache.
> This means that importing a file multiple times will lead to multiple parsings
> and evaluations.
>
> Please note also that the files imported via `scopedImport` contain free variables
> and thus cannot be imported using the regular `import`.
+1
View File
@@ -125,6 +125,7 @@ builtin_definitions = files(
'builtins/readFileType.md',
'builtins/removeAttrs.md',
'builtins/replaceStrings.md',
'builtins/scopedImport.md',
'builtins/seq.md',
'builtins/sort.md',
'builtins/split.md',
-7
View File
@@ -251,13 +251,6 @@ static void import(EvalState & state, Value & vPath, Value * vScope, Value & v)
}
}
static RegisterPrimOp primop_scopedImport(PrimOp{
{.name = "scopedImport",
.arity = 2,
.fun = [](EvalState & state, Value ** args, Value & v) { import(state, *args[1], args[0], v); }
}
});
static void prim_import(EvalState & state, Value * * args, Value & v)
{
import(state, *args[0], nullptr, v);