A way to remove string context

The string context is the references in a string to derivations. For example, a derivation out path refers to the derivation, and thus any string that interpolates this out path refers to this derivation, too. This is useful because Nix uses it to track derivation dependencies. However, it can sometimes be annoying because functions like builtins.fromJSON does not accept a string with a context:

let
  drv = fetchTarball "https://github.com/Lxtharia/minegrub-theme/tarball/main";
  str = "\"${drv}\"";
in builtins.fromJSON str
Error
error:
       … while calling the 'fromJSON' builtin
         at «string»:4:4:
            3|   str = "\"${drv}\"";
            4| in builtins.fromJSON str
             |    ^

       … while evaluating the first argument passed to builtins.fromJSON

       error: the string '"/nix/store/ypk21myg6psifx2nn58wn446x0bqa4ng-source"' is not allowed to refer to a store path (such as 'ypk21myg6psifx2nn58wn446x0bqa4ng-source')

In order to do this, one has to convert str to a string that has the same content but does not refer to drv. In other words, we need to clear its context or—by what I would like to call it—“purify” the string. Probably unexpectedly, there is indeed a way to purify a string in Nix!

This PR made it so that builtins.readFile somePath does not refer to drv even if somePath refers to drv, as long as it does not explicitly include the store path of drv. Therefore, builtins.readFile has the capability of purifying a path. We can then devise this way to purify the string str:

  1. Slice str into substrings, each of which does not contain the out path of drv. Because the hash part of the store path is 32-character long, making each substring shorter than that is safe enough.
  2. For each substring s, it still refers to drv, but we can get a purified version by builtins.readFile (builtins.toFile "_" s).
  3. Concatenate all the purified substrings to get the purified str.
let
  purify = let maxLength = 31; in str:
    let length = builtins.stringLength str; in
    if length <= maxLength then
      builtins.readFile (builtins.toFile "_" str)
    else
      "${purify (builtins.substring 0 maxLength str)}${purify (builtins.substring maxLength (length - maxLength) str)}";

  drv = fetchTarball "https://github.com/Lxtharia/minegrub-theme/tarball/main";
  str = purify "\"${drv}\"";
in builtins.fromJSON str

This gives the result without an error.

1 Like

You know about builtins.unsafeDiscardStringContext, right?

1 Like

I indeed did not know. Turns out the time I spent on working around this thing is wasted.

1 Like

There’s also builtins.appendContext and builtins.getContext for more fine-grained manipulation if needed.

2 Likes