Restore ambiguous value printer for nix-instantiate

The Nix team has requested that this output format remain unchanged.
I've added a warning to the man page explaining that `nix-instantiate
--eval` output will not parse correctly in many situations.
This commit is contained in:
Rebecca Turner 2024-01-09 11:13:45 -08:00
parent 0fa08b4516
commit df84dd4d8d
No known key found for this signature in database
6 changed files with 189 additions and 26 deletions

View file

@ -35,13 +35,50 @@ standard input.
- `--parse`\ - `--parse`\
Just parse the input files, and print their abstract syntax trees on Just parse the input files, and print their abstract syntax trees on
standard output in ATerm format. standard output as a Nix expression.
- `--eval`\ - `--eval`\
Just parse and evaluate the input files, and print the resulting Just parse and evaluate the input files, and print the resulting
values on standard output. No instantiation of store derivations values on standard output. No instantiation of store derivations
takes place. takes place.
> **Warning**
>
> This option produces ambiguous output which is not suitable for machine
> consumption. For example, these two Nix expressions print the same result
> despite having different types:
>
> ```console
> $ nix-instantiate --eval --expr '{ a = {}; }'
> { a = <CODE>; }
> $ nix-instantiate --eval --expr '{ a = <CODE>; }'
> { a = <CODE>; }
> ```
>
> For human-readable output, `nix eval` (experimental) is more informative:
>
> ```console
> $ nix-instantiate --eval --expr 'a: a'
> <LAMBDA>
> $ nix eval --expr 'a: a'
> «lambda @ «string»:1:1»
> ```
>
> For machine-readable output, the `--xml` option produces unambiguous
> output:
>
> ```console
> $ nix-instantiate --eval --xml --expr '{ foo = <CODE>; }'
> <?xml version='1.0' encoding='utf-8'?>
> <expr>
> <attrs>
> <attr column="3" line="1" name="foo">
> <unevaluated />
> </attr>
> </attrs>
> </expr>
> ```
- `--find-file`\ - `--find-file`\
Look up the given files in Nixs search path (as specified by the Look up the given files in Nixs search path (as specified by the
`NIX_PATH` environment variable). If found, print the corresponding `NIX_PATH` environment variable). If found, print the corresponding
@ -61,11 +98,11 @@ standard input.
- `--json`\ - `--json`\
When used with `--eval`, print the resulting value as an JSON When used with `--eval`, print the resulting value as an JSON
representation of the abstract syntax tree rather than as an ATerm. representation of the abstract syntax tree rather than as a Nix expression.
- `--xml`\ - `--xml`\
When used with `--eval`, print the resulting value as an XML When used with `--eval`, print the resulting value as an XML
representation of the abstract syntax tree rather than as an ATerm. representation of the abstract syntax tree rather than as a Nix expression.
The schema is the same as that used by the [`toXML` The schema is the same as that used by the [`toXML`
built-in](../language/builtins.md). built-in](../language/builtins.md).
@ -133,28 +170,29 @@ $ nix-instantiate --eval --xml --expr '1 + 2'
The difference between non-strict and strict evaluation: The difference between non-strict and strict evaluation:
```console ```console
$ nix-instantiate --eval --xml --expr 'rec { x = "foo"; y = x; }' $ nix-instantiate --eval --xml --expr '{ x = {}; }'
... <?xml version='1.0' encoding='utf-8'?>
<attr name="x"> <expr>
<string value="foo" /> <attrs>
</attr> <attr column="3" line="1" name="x">
<attr name="y">
<unevaluated /> <unevaluated />
</attr> </attr>
... </attrs>
</expr>
``` ```
Note that `y` is left unevaluated (the XML representation doesnt Note that `y` is left unevaluated (the XML representation doesnt
attempt to show non-normal forms). attempt to show non-normal forms).
```console ```console
$ nix-instantiate --eval --xml --strict --expr 'rec { x = "foo"; y = x; }' $ nix-instantiate --eval --xml --strict --expr '{ x = {}; }'
... <?xml version='1.0' encoding='utf-8'?>
<attr name="x"> <expr>
<string value="foo" /> <attrs>
<attr column="3" line="1" name="x">
<attrs>
</attrs>
</attr> </attr>
<attr name="y"> </attrs>
<string value="foo" /> </expr>
</attr>
...
``` ```

View file

@ -0,0 +1,100 @@
#include "print-ambiguous.hh"
#include "print.hh"
#include "signals.hh"
namespace nix {
// See: https://github.com/NixOS/nix/issues/9730
void printAmbiguous(
Value &v,
const SymbolTable &symbols,
std::ostream &str,
std::set<const void *> *seen,
int depth)
{
checkInterrupt();
if (depth <= 0) {
str << "«too deep»";
return;
}
switch (v.type()) {
case nInt:
str << v.integer;
break;
case nBool:
printLiteralBool(str, v.boolean);
break;
case nString:
printLiteralString(str, v.string_view());
break;
case nPath:
str << v.path().to_string(); // !!! escaping?
break;
case nNull:
str << "null";
break;
case nAttrs: {
if (seen && !v.attrs->empty() && !seen->insert(v.attrs).second)
str << "«repeated»";
else {
str << "{ ";
for (auto & i : v.attrs->lexicographicOrder(symbols)) {
str << symbols[i->name] << " = ";
printAmbiguous(*i->value, symbols, str, seen, depth - 1);
str << "; ";
}
str << "}";
}
break;
}
case nList:
if (seen && v.listSize() && !seen->insert(v.listElems()).second)
str << "«repeated»";
else {
str << "[ ";
for (auto v2 : v.listItems()) {
if (v2)
printAmbiguous(*v2, symbols, str, seen, depth - 1);
else
str << "(nullptr)";
str << " ";
}
str << "]";
}
break;
case nThunk:
if (!v.isBlackhole()) {
str << "<CODE>";
} else {
// Although we know for sure that it's going to be an infinite recursion
// when this value is accessed _in the current context_, it's likely
// that the user will misinterpret a simpler «infinite recursion» output
// as a definitive statement about the value, while in fact it may be
// a valid value after `builtins.trace` and perhaps some other steps
// have completed.
str << "«potential infinite recursion»";
}
break;
case nFunction:
if (v.isLambda()) {
str << "<LAMBDA>";
} else if (v.isPrimOp()) {
str << "<PRIMOP>";
} else if (v.isPrimOpApp()) {
str << "<PRIMOP-APP>";
}
break;
case nExternal:
str << *v.external;
break;
case nFloat:
str << v.fpoint;
break;
default:
printError("Nix evaluator internal error: printAmbiguous: invalid value type");
abort();
}
}
}

View file

@ -0,0 +1,24 @@
#pragma once
#include "value.hh"
namespace nix {
/**
* Print a value in the deprecated format used by `nix-instantiate --eval` and
* `nix-env` (for manifests).
*
* This output can't be changed because it's part of the `nix-instantiate` API,
* but it produces ambiguous output; unevaluated thunks and lambdas (and a few
* other types) are printed as Nix path syntax like `<CODE>`.
*
* See: https://github.com/NixOS/nix/issues/9730
*/
void printAmbiguous(
Value &v,
const SymbolTable &symbols,
std::ostream &str,
std::set<const void *> *seen,
int depth);
}

View file

@ -108,8 +108,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
environment. */ environment. */
auto manifestFile = ({ auto manifestFile = ({
std::ostringstream str; std::ostringstream str;
std::set<const void *> seen; printAmbiguous(manifest, state.symbols, str, nullptr, std::numeric_limits<int>::max());
printAmbiguous(manifest, state.symbols, str, &seen, std::numeric_limits<int>::max());
// TODO with C++20 we can use str.view() instead and avoid copy. // TODO with C++20 we can use str.view() instead and avoid copy.
std::string str2 = str.str(); std::string str2 = str.str();
StringSource source { str2 }; StringSource source { str2 };

View file

@ -1,9 +1,11 @@
#include "globals.hh" #include "globals.hh"
#include "print-ambiguous.hh"
#include "shared.hh" #include "shared.hh"
#include "eval.hh" #include "eval.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
#include "get-drvs.hh" #include "get-drvs.hh"
#include "attr-path.hh" #include "attr-path.hh"
#include "signals.hh"
#include "value-to-xml.hh" #include "value-to-xml.hh"
#include "value-to-json.hh" #include "value-to-json.hh"
#include "store-api.hh" #include "store-api.hh"
@ -24,7 +26,6 @@ static int rootNr = 0;
enum OutputKind { okPlain, okXML, okJSON }; enum OutputKind { okPlain, okXML, okJSON };
void processExpr(EvalState & state, const Strings & attrPaths, void processExpr(EvalState & state, const Strings & attrPaths,
bool parseOnly, bool strict, Bindings & autoArgs, bool parseOnly, bool strict, Bindings & autoArgs,
bool evalOnly, OutputKind output, bool location, Expr * e) bool evalOnly, OutputKind output, bool location, Expr * e)
@ -56,7 +57,8 @@ void processExpr(EvalState & state, const Strings & attrPaths,
std::cout << std::endl; std::cout << std::endl;
} else { } else {
if (strict) state.forceValueDeep(vRes); if (strict) state.forceValueDeep(vRes);
vRes.print(state, std::cout); std::set<const void *> seen;
printAmbiguous(vRes, state.symbols, std::cout, &seen, std::numeric_limits<int>::max());
std::cout << std::endl; std::cout << std::endl;
} }
} else { } else {

View file

@ -1 +1 @@
[ null «primop toString» «partially applied primop deepSeq» «lambda @ /pwd/lang/eval-okay-print.nix:1:61» [ [ «repeated» ] ] ] [ null <PRIMOP> <PRIMOP-APP> <LAMBDA> [ [ «repeated» ] ] ]