Compare commits

...
Author SHA1 Message Date
Raito Bezarius a6f0e87fa8 libexpr/static-analyzer: init
Change-Id: I606718fbe33db1f6c00ca34f45c6553ce468398c
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-11-21 17:36:35 +01:00
eldritch horrors 17879c9a83 libexpr: de-ptr-ize Value references
thunk values are shareable, and we can represent invalid/uninitialized
values with a special bit pattern that makes no sense otherwise. there
is no need to keep allocating values on the heap, instead we can treat
values like reference-counted smart pointers to heap objects, which in
turn lets us save a lot of allocations and, ultimately, gc heap space.

compared to our baseline (main of 2025-09-27) we save 15%+ memory on a
system rebuild and 17% on nix search. eval time regresses by ~3% for a
system rebuild, while nix search is 7% faster. further optimization is
probably possible (but for now this will just have to be good enough).

Change-Id: Ib6c47acdbe2fac4f76a83c2269f16f30ef66b2e1
2025-09-28 00:02:21 +02:00
eldritch horrors 9ba7a7eee7 libexpr: reformat stuff we'll change soon
mainly to keep the next diff smaller. it'll be large enough as is.

Change-Id: Ib8a34520f03539cbf6aa2f0e66cbed05fe1225eb
2025-09-28 00:02:21 +02:00
eldritch horrors 5f070b2297 libexpr: de-ptr-ize many Value uses
with thunk state being shareable we no longer need to worry about value
uniqueness, only about value lifetime. this means we can liberally drop
indirections and allocations, passing references instead of pointers or
using stack memory instead of gc-managed memory for some intermediates.

Change-Id: I2d48a6fd57a376d544bd9bd2d05e5420611986d1
2025-09-28 00:02:21 +02:00
eldritch horrors 3b093988fb libexpr: remove ValueVector{,Map}
these typedefs were convenient in the past, but now they're not really.

Change-Id: I7522f7582ead545148af8e6444295d620ee0872a
2025-09-28 00:02:21 +02:00
eldritch horrors fe6dfa5ace libexpr: remove unused return types
Change-Id: Ief898b70f9781bd3dbe66734710f388daa2f2fed
2025-09-28 00:02:21 +02:00
eldritch horrors acd85805ab libexpr: remove type punning in primt_attrValues
there's no measurable performance gain in real-world testing to abusing
our list value storage like this. we haven't tested how much storage we
actually need on the stack to offset most of the temporary storage cost
and used 128 as a "good enough" value instead, reserving 1 kiB of stack
space on 64 bit platforms in a leaf function. this should do for a bit.

Change-Id: Ie98519b1da3e6fe685da88d1c44ffb4580fb592a
2025-09-28 00:02:21 +02:00
eldritch horrors af86b74467 libexpr: tag Value::Acb
Value is already tagged. Value::Acb blocks are allocated for lambdas (so
we can fit the value tag into the three bits we have available), but the
current layout is rather wasteful for this purpose. the type bits can be
stored together with parts of pointers, which in the lambda case will be
the scope the lambda captures. the expr could also be used, but Env is a
gc-allocated item and thus guaranteed to be aligned properly for tagging

Change-Id: Ia685875387c7795bc4a00d73d1ce3cfea84e7297
2025-09-28 00:02:21 +02:00
eldritch horrors 85ed12485e libexpr: make thunk state shareable
this is a strong prerequisite for making values themselves copyable
without duplicating evaluation side effects. with this we can treat
`Value` the way we treated `Value *` to date and drop indirections.

Change-Id: I08f30d12697614a3ae7149615f6f1da83b13f52b
2025-09-28 00:02:21 +02:00
eldritch horrors 30b971f6e0 libexpr: reduce Value size to one pointer
Change-Id: Id6e4a2f68eaa4afdd006379ebcf839c4a126b819
2025-09-28 00:02:21 +02:00
eldritch horrors 32440b5fae libexpr: heap-allocate "large" integers
Change-Id: Ic391f2f1bf87f044d7a688196ba9e0ad766d65aa
2025-09-28 00:02:21 +02:00
eldritch horrors aa39e14fcf libexpr: heap-allocate app nodes
Change-Id: I9a39dcf0be7589cedf494757e21665c5d50e446b
2025-09-28 00:02:21 +02:00
eldritch horrors b0d11f9da1 libexpr: heap-allocate thunk control state
Change-Id: I20d8ab1d6f683c0a2f3b77edf9bdad147d62c8fa
2025-09-28 00:02:20 +02:00
eldritch horrors 002dfbb2e3 libexpr: move lambdas to auxiliary storage
Change-Id: Ibe4885f17c0ba1634ed6dbca0a45f8bd4619d69b
2025-09-28 00:02:20 +02:00
eldritch horrors 6e242e8b9b libexpr: move primops to auxiliary storage
same as for null: we have few of them, they're statically allocated,
and they're not the largest contributor to the value population. not
storing them in Value itself frees up resources we *will* use later.

Change-Id: I521f9f243f48f56a78f7bffdf1dc1f0bc40a5c2d
2025-09-28 00:02:20 +02:00
eldritch horrors b1ffae3ccd libexpr: move null to auxiliary storage
we only need the one object for it. there's no need to waste precious
bits of the value internal type enum for this simple singleton datum.

Change-Id: Ie314b5bf429015e518798d9d65ad8ab2bb84a38e
2025-09-28 00:02:20 +02:00
eldritch horrors 5def7559a6 libexpr: move floats to auxiliary storage
floats are used very rarely, and our float support is bad enough to
strongly discourage using them on reproducibility grounds alone. we
can thus move them to more expensive storage without hurting folks.

Change-Id: I1086f612f85e294dd3fae4a2d334e09f52bbe4a8
2025-09-28 00:02:20 +02:00
eldritch horrors ce70234904 libexpr: move external value refs to aux storage
external values very rarely appear during eval "normal" eval, and
creating them is pretty expensive. does *anything* even use them?

Change-Id: Id50fa3f76b7e1f551d550d99996a1ed5880b2531
2025-09-28 00:02:20 +02:00
eldritch horrors cbc378b277 libexpr: heap-alloc string control blocks
despite not using allocation caches this does not have a statistically
significant performance impact, with less than 1% extra memory needed.

Change-Id: Ibe51a55ba986e471f217f3724977af17880fafff
2025-09-28 00:02:20 +02:00
eldritch horrors 5d6bb8c350 libexpr: remove tPrimOpApp
using the same nodes as tApp is possible, and thanks to multi-arg app
nodes it can even be a bit faster than the linked lists used to date.

Change-Id: Idccb7c0b54c808e62da85d1c42ee09e6e92c4f7b
2025-09-28 00:02:20 +02:00
eldritch horrors 93bda92508 libexpr: add multi-arg app nodes
these behave like the old chains of app nodes, but they can store more
than one argument per node. for tApp values themselves this is not all
that useful, but if we could share tApp and tPrimOpApp backing storage
we could avoid creating and traversing the linked lists of values that
are currently needed to represent partially applied builtin functions.

Change-Id: I5a2a02d9733e1e0be5443459e2998d62fd3b9a5b
2025-09-28 00:02:20 +02:00
eldritch horrors dbee9d15c5 libexpr: use std::span for callFunction
Change-Id: I8c94bafabdb2416c85d9721d3d6424f52fbd45e0
2025-09-28 00:02:20 +02:00
eldritch horrors 7642b7227a libexpr: unify strings and paths
paths already are just strings with slightly magic semantics. the type
can mirror that at no perf cost, letting us drop one internalType tag.

Change-Id: I98acaa4fe3bedd28fc0841e1b81184d8dcddacc5
2025-09-28 00:02:20 +02:00
eldritch horrors aa96b00182 libexpr: alloc list storage as a (length, vla) type
memory overhead is minimal and performance impact not measurable. once
we've done something like this for all value types that don't fit in a
single machine word we can cut a word from Value, offsetting the cost.

Change-Id: I9813bacd7e851957ad3426aed8f74033179a4212
2025-09-28 00:02:20 +02:00
eldritch horrors d37a5d4000 libexpr: don't inline small lists into values
this has no measurable performance impact thanks to the new caches.

Change-Id: Ib403a9a567161675f78e8c5d314d6340183d181d
2025-09-28 00:02:20 +02:00
eldritch horrors b36a19e50c libexpr: cache more allocation sizes
we now use a single cache set for a number of sizes from one to eight
words. this also matches small attrsets, but perf impact seems small.

Change-Id: Icf16b329b98a20fcc9fe75e6395e148f0852c798
2025-09-28 00:02:20 +02:00
eldritch horrors f08b8532be libexpr: "hide" Value union members
on its own this is not very useful, but having accessors for every value
kind is a prerequisite for doing smart things with Value than the union.
the net effect for now is only to add a few parentheses across the tree.

Change-Id: I88688ac09eb08495dad1eb221034ca540f094950
2025-09-28 00:02:20 +02:00
eldritch horrors f3fcd7c02f bench: add memory benchmark
Change-Id: I4b2aa305b452a3a0c73e37953a5c332219d43d2c
2025-09-28 00:02:20 +02:00
eldritch horrors 1f0b25a92c bench: allow benchmarking a single build
Change-Id: Ie77247f9348eaeea740b0f894819ee2383d92312
2025-09-28 00:02:20 +02:00
eldritch horrors 82b771951c testing: disable substitution in f2
mirrors f1. much faster.

Change-Id: I38bbbd5b26220f480afe76ef9302e5c90f21507a
2025-09-28 00:02:20 +02:00
68 changed files with 2880 additions and 2063 deletions
+61 -8
View File
@@ -9,6 +9,7 @@ import tempfile
import platform
import shlex
import textwrap
import dataclasses
flake_args = ["--extra-experimental-features", "nix-command flakes"]
cases = {
@@ -64,8 +65,9 @@ arg_parser.add_argument(
)
arg_parser.add_argument(
'--mode',
choices=[ "walltime" ] + [ "icount" ] if platform.system() == 'Linux' else [], # perf doesn't run on Darwin
default="walltime",
nargs='+',
choices=[ "walltime", "memory" ] + [ "icount" ] if platform.system() == 'Linux' else [], # perf doesn't run on Darwin
default=[ "walltime" ],
)
arg_parser.add_argument(
'--daemon',
@@ -73,8 +75,8 @@ arg_parser.add_argument(
help='Run a temporary daemon for the benchmark instead of using a local store directly',
)
args = arg_parser.parse_args()
if len(args.builds) < 2:
raise ValueError("need at least two build directories to compare")
if len(args.builds) < 1:
raise ValueError("need at least one build directory to benchmark")
benchmarks: list[str] = []
if args.cases is None:
@@ -162,6 +164,54 @@ def bench_icount(env):
print(" relative instructions:", int(instr)/perf_results_for[case][0][1])
print("\n")
@dataclasses.dataclass
class MemoryStatistics:
envBytes: int
listBytes: int
setBytes: int
valueBytes: int
heapBytes: int
heapSize: int
def bench_memory(env):
path = "bench/bench-memory.json"
env = env | {
'NIX_SHOW_STATS': '1',
'NIX_SHOW_STATS_PATH': path,
}
results: dict[str, list[tuple[str, MemoryStatistics]]] = {}
for case in benchmarks:
for build in args.builds:
case_command = make_full_command(build, case)
commandline = [ "sh", "-c", case_command ]
print("running", case_command)
subprocess.run(commandline, env=env, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
with open(path) as fd:
stats = json.load(fd)
results.setdefault(case, []).append((case_command, MemoryStatistics(
envBytes=stats['envs']['bytes'],
listBytes=stats['list']['bytes'],
setBytes=stats['sets']['bytes'],
valueBytes=stats['values']['bytes'],
heapSize=stats['gc']['heapSize'],
heapBytes=stats['gc']['totalBytes'],
)))
print("Benchmarks summary\n---\n")
for (case, entries) in results.items():
for cmd, stats in entries:
print(cmd)
print("-" * min(80, len(cmd)))
print(f" env bytes: {stats.envBytes :15d} | {(stats.envBytes / entries[0][1].envBytes) :.3f}x")
print(f" list bytes: {stats.listBytes :15d} | {(stats.listBytes / entries[0][1].listBytes) :.3f}x")
print(f" set bytes: {stats.setBytes :15d} | {(stats.setBytes / entries[0][1].setBytes) :.3f}x")
if not entries[0][1].valueBytes:
print(f" value bytes: {0:15d}")
else:
print(f" value bytes: {stats.valueBytes:15d} | {(stats.valueBytes / entries[0][1].valueBytes):.3f}x")
print(f" heap alloc'd: {stats.heapBytes :15d} | {(stats.heapBytes / entries[0][1].heapBytes) :.3f}x")
print(f" heap size: {stats.heapSize :15d} | {(stats.heapSize / entries[0][1].heapSize) :.3f}x")
print("\n")
with tempfile.TemporaryDirectory() as tmp_dir:
subprocess.run([
@@ -178,7 +228,10 @@ with tempfile.TemporaryDirectory() as tmp_dir:
])
subenv["NIX_DAEMON_SOCKET_PATH"] = f"{tmp_dir}/daemon"
if args.mode == "walltime":
bench_walltime(subenv)
else:
bench_icount(subenv)
for mode in args.mode:
if mode == "walltime":
bench_walltime(subenv)
elif mode == "memory":
bench_memory(subenv)
else:
bench_icount(subenv)
+7 -7
View File
@@ -272,7 +272,7 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
}
bool add = false;
if (v.type() == nFunction) {
if (auto pattern = dynamic_cast<AttrsPattern *>(v.lambda.fun->pattern.get())) {
if (auto pattern = dynamic_cast<AttrsPattern *>(v.lambda().fun->pattern.get())) {
for (auto & i : pattern->formals) {
if (evaluator->symbols[i.name] == "inNixShell") {
add = true;
@@ -285,12 +285,12 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
};
for (auto & i : attrPaths) {
Value & v(*findAlongAttrPath(
*state,
i,
takesNixShellAttr(vRoot) ? *autoArgsWithInNixShell : *autoArgs,
vRoot
).first);
Value v(
findAlongAttrPath(
*state, i, takesNixShellAttr(vRoot) ? *autoArgsWithInNixShell : *autoArgs, vRoot
)
.first
);
state->forceValue(v, noPos);
getDerivations(
*state,
+24 -18
View File
@@ -1,6 +1,7 @@
#include "lix/libcmd/cmd-profiles.hh"
#include "lix/libexpr/attr-path.hh"
#include "lix/libcmd/common-eval-args.hh"
#include "lix/libexpr/value.hh"
#include "lix/libstore/derivations.hh"
#include "lix/libutil/terminal.hh"
#include "lix/libexpr/eval.hh"
@@ -150,11 +151,12 @@ static void getAllExprs(Evaluator & state,
continue;
}
/* Load the expression on demand. */
auto vArg = state.mem.allocValue();
vArg->mkString(path2.canonical().abs());
Value vArg;
vArg.mkString(path2.canonical().abs());
if (seen.size() == maxAttrs)
throw Error("too many Nix expressions in directory '%1%'", path);
attrs.alloc(attrName).mkApp(&state.builtins.get("import"), vArg);
attrs.alloc(attrName
) = {NewValueAs::app, state.mem, state.builtins.get("import"), vArg};
}
else if (st.type == InputAccessor::tDirectory)
/* `path2' is a directory (with no default.nix in it);
@@ -181,7 +183,7 @@ static void loadSourceExpr(EvalState & state, const SourcePath & path_, Value &
directory). */
else if (st.type == InputAccessor::tDirectory) {
auto attrs = state.ctx.buildBindings(maxAttrs);
attrs.alloc("_combineChannels").mkList(0);
attrs.alloc("_combineChannels") = Value::EMPTY_LIST;
StringSet seen;
getAllExprs(state.ctx, path, seen, attrs);
v.mkAttrs(attrs);
@@ -198,7 +200,7 @@ static void loadDerivations(EvalState & state, const SourcePath & nixExprPath,
Value vRoot;
loadSourceExpr(state, nixExprPath, vRoot);
Value & v(*findAlongAttrPath(state, pathPrefix, autoArgs, vRoot).first);
Value v(findAlongAttrPath(state, pathPrefix, autoArgs, vRoot).first);
getDerivations(state, v, pathPrefix, autoArgs, elems, true);
@@ -425,7 +427,7 @@ static void queryInstSources(EvalState & state,
Expr & eFun = state.ctx.parseExprFromString(i, CanonPath::fromCwd());
Value vFun, vTmp;
state.eval(eFun, vFun);
vTmp.mkApp(&vFun, &vArg);
vTmp = {NewValueAs::app, state.ctx.mem, vFun, vArg};
getDerivations(state, vTmp, "", *instSource.autoArgs, elems, true);
}
@@ -480,7 +482,7 @@ static void queryInstSources(EvalState & state,
Value vRoot;
loadSourceExpr(state, *instSource.nixExprPath, vRoot);
for (auto & i : args) {
Value & v(*findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first);
Value v(findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first);
getDerivations(state, v, "", *instSource.autoArgs, elems, true);
}
break;
@@ -515,8 +517,8 @@ static bool keep(EvalState & state, DrvInfo & drv)
static void setMetaFlag(EvalState & state, DrvInfo & drv,
const std::string & name, const std::string & value)
{
auto v = state.ctx.mem.allocValue();
v->mkString(value);
Value v;
v.mkString(value);
drv.setMeta(state, name, v);
}
@@ -1275,35 +1277,39 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nInt) {
attrs2["type"] = "int";
attrs2["value"] = fmt("%1%", v->integer);
attrs2["value"] = fmt("%1%", v->integer());
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nFloat) {
attrs2["type"] = "float";
attrs2["value"] = fmt("%1%", v->fpoint);
attrs2["value"] = fmt("%1%", v->fpoint());
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nBool) {
attrs2["type"] = "bool";
attrs2["value"] = v->boolean ? "true" : "false";
attrs2["value"] = v->boolean() ? "true" : "false";
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nList) {
attrs2["type"] = "strings";
XMLOpenElement m(xml, "meta", attrs2);
for (auto elem : v->listItems()) {
if (elem->type() != nString) continue;
for (auto & elem : v->listItems()) {
if (elem.type() != nString) {
continue;
}
XMLAttrs attrs3;
attrs3["value"] = elem->str();
attrs3["value"] = elem.str();
xml.writeEmptyElement("string", attrs3);
}
} else if (v->type() == nAttrs) {
attrs2["type"] = "strings";
XMLOpenElement m(xml, "meta", attrs2);
Bindings & attrs = *v->attrs;
Bindings & attrs = *v->attrs();
for (auto &i : attrs) {
const Attr & a(*attrs.get(i.name));
if(a.value->type() != nString) continue;
if (a.value.type() != nString) {
continue;
}
XMLAttrs attrs3;
attrs3["type"] = globals.state->symbols[i.name];
attrs3["value"] = a.value->str();
attrs3["value"] = a.value.str();
xml.writeEmptyElement("string", attrs3);
}
}
+1 -1
View File
@@ -38,7 +38,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
state.eval(e, vRoot);
for (auto & i : attrPaths) {
Value & v(*findAlongAttrPath(state, i, autoArgs, vRoot).first);
Value v(findAlongAttrPath(state, i, autoArgs, vRoot).first);
state.forceValue(v, noPos);
NixStringContext context;
+15 -13
View File
@@ -1,4 +1,5 @@
#include "user-env.hh"
#include "lix/libexpr/value.hh"
#include "lix/libstore/derivations.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libstore/path-with-outputs.hh"
@@ -32,7 +33,8 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
/* Construct the whole top level derivation. */
StorePathSet references;
Value manifest = state.ctx.mem.newList(elems.size());
auto manifest = state.ctx.mem.newList(elems.size());
Value vManifest{NewValueAs::list, manifest};
size_t n = 0;
for (auto & i : elems) {
/* Create a pseudo-derivation containing the name, system,
@@ -55,9 +57,10 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
// Copy each output meant for installation.
auto & vOutputs = attrs.alloc(state.ctx.s.outputs);
vOutputs = state.ctx.mem.newList(outputs.size());
auto outputsList = state.ctx.mem.newList(outputs.size());
vOutputs = {NewValueAs::list, outputsList};
for (const auto & [m, j] : enumerate(outputs)) {
(vOutputs.listElems()[m] = state.ctx.mem.allocValue())->mkString(j.first);
outputsList->elems[m].mkString(j.first);
auto outputAttrs = state.ctx.buildBindings(2);
outputAttrs.alloc(state.ctx.s.outPath).mkString(state.ctx.store->printStorePath(*j.second));
attrs.alloc(j.first).mkAttrs(outputAttrs);
@@ -75,12 +78,12 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
for (auto & j : metaNames) {
Value * v = i.queryMeta(state, j);
if (!v) continue;
meta.insert(state.ctx.symbols.create(j), v);
meta.insert(state.ctx.symbols.create(j), *v);
}
attrs.alloc(state.ctx.s.meta).mkAttrs(meta);
(manifest.listElems()[n++] = state.ctx.mem.allocValue())->mkAttrs(attrs);
manifest->elems[n++].mkAttrs(attrs);
if (drvPath) references.insert(*drvPath);
}
@@ -89,7 +92,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
the store; we need it for future modifications of the
environment. */
std::ostringstream str;
printAmbiguous(manifest, state.ctx.symbols, str, nullptr, std::numeric_limits<int>::max());
printAmbiguous(vManifest, state.ctx.symbols, str, nullptr, std::numeric_limits<int>::max());
auto manifestFile = state.aio.blockOn(state.ctx.store->addTextToStore("env-manifest.nix",
str.str(), references));
@@ -103,21 +106,20 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
builder with the manifest as argument. */
auto attrs = state.ctx.buildBindings(3);
state.ctx.paths.mkStorePathString(manifestFile, attrs.alloc("manifest"));
attrs.insert(state.ctx.symbols.create("derivations"), &manifest);
attrs.insert(state.ctx.symbols.create("derivations"), vManifest);
Value args;
args.mkAttrs(attrs);
Value topLevel;
topLevel.mkApp(&envBuilder, &args);
Value topLevel{NewValueAs::app, state.ctx.mem, envBuilder, args};
/* Evaluate it. */
debug("evaluating user environment builder");
state.forceValue(topLevel, noPos);
NixStringContext context;
const Attr & aDrvPath(*topLevel.attrs->get(state.ctx.s.drvPath));
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context, "");
const Attr & aOutPath(*topLevel.attrs->get(state.ctx.s.outPath));
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context, "");
const Attr & aDrvPath(*topLevel.attrs()->get(state.ctx.s.drvPath));
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, aDrvPath.value, context, "");
const Attr & aOutPath(*topLevel.attrs()->get(state.ctx.s.outPath));
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, aOutPath.value, context, "");
/* Realise the resulting store expression. */
debug("building user environment");
+3 -3
View File
@@ -183,13 +183,13 @@ Bindings * MixEvalArgs::getAutoArgs(Evaluator & state)
{
auto res = state.buildBindings(autoArgs.size());
for (auto & i : autoArgs) {
auto v = state.mem.allocValue();
Value v;
if (i.second[0] == 'E')
state.evalLazily(
state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()), *v
state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()), v
);
else
v->mkString(((std::string_view) i.second).substr(1));
v.mkString(((std::string_view) i.second).substr(1));
res.insert(state.symbols.create(i.first), v);
}
return res.finish();
+11 -9
View File
@@ -12,9 +12,10 @@ namespace nix {
InstallableAttrPath::InstallableAttrPath(
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value * v,
Value & v,
const std::string & attrPath,
ExtendedOutputsSpec extendedOutputsSpec)
ExtendedOutputsSpec extendedOutputsSpec
)
: InstallableValue(state)
, cmd(cmd)
, v(allocRootValue(v))
@@ -22,10 +23,10 @@ InstallableAttrPath::InstallableAttrPath(
, extendedOutputsSpec(std::move(extendedOutputsSpec))
{ }
std::pair<Value *, PosIdx> InstallableAttrPath::toValue(EvalState & state)
std::pair<Value, PosIdx> InstallableAttrPath::toValue(EvalState & state)
{
auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(*evaluator), **v);
state.forceValue(*vRes, pos);
auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(*evaluator), *v);
state.forceValue(vRes, pos);
return {vRes, pos};
}
@@ -34,7 +35,7 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths(EvalState & state)
auto [v, pos] = toValue(state);
if (std::optional derivedPathWithInfo = trySinglePathToDerivedPaths(
state, *v, pos, fmt("while evaluating the attribute '%s'", attrPath)
state, v, pos, fmt("while evaluating the attribute '%s'", attrPath)
))
{
return { *derivedPathWithInfo };
@@ -43,7 +44,7 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths(EvalState & state)
Bindings & autoArgs = *cmd.getAutoArgs(*evaluator);
DrvInfos drvInfos;
getDerivations(state, *v, "", autoArgs, drvInfos, false);
getDerivations(state, v, "", autoArgs, drvInfos, false);
// Backward compatibility hack: group results by drvPath. This
// helps keep .all output together.
@@ -92,9 +93,10 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths(EvalState & state)
InstallableAttrPath InstallableAttrPath::parse(
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value * v,
Value & v,
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec)
ExtendedOutputsSpec extendedOutputsSpec
)
{
return {
state, cmd, v,
+7 -5
View File
@@ -20,13 +20,14 @@ class InstallableAttrPath : public InstallableValue
InstallableAttrPath(
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value * v,
Value & v,
const std::string & attrPath,
ExtendedOutputsSpec extendedOutputsSpec);
ExtendedOutputsSpec extendedOutputsSpec
);
std::string what() const override { return attrPath; };
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
std::pair<Value, PosIdx> toValue(EvalState & state) override;
DerivedPathsWithInfo toDerivedPaths(EvalState & state) override;
@@ -35,9 +36,10 @@ public:
static InstallableAttrPath parse(
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value * v,
Value & v,
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec);
ExtendedOutputsSpec extendedOutputsSpec
);
};
}
+2 -2
View File
@@ -136,9 +136,9 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths(EvalState & state)
}};
}
std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state)
std::pair<Value, PosIdx> InstallableFlake::toValue(EvalState & state)
{
return {&getCursor(state)->forceValue(state), noPos};
return {getCursor(state)->forceValue(state), noPos};
}
std::vector<ref<eval_cache::AttrCursor>>
+1 -1
View File
@@ -55,7 +55,7 @@ struct InstallableFlake : InstallableValue
DerivedPathsWithInfo toDerivedPaths(EvalState & state) override;
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
std::pair<Value, PosIdx> toValue(EvalState & state) override;
/**
* Get a cursor to every attrpath in getActualAttrPaths() that
+3 -2
View File
@@ -9,8 +9,9 @@ std::vector<ref<eval_cache::AttrCursor>>
InstallableValue::getCursors(EvalState & state)
{
auto evalCache =
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt,
[&](EvalState & state) { return toValue(state).first; });
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, [&](EvalState & state) {
return toValue(state).first;
});
return {evalCache->getRoot()};
}
+1 -1
View File
@@ -77,7 +77,7 @@ struct InstallableValue : Installable
virtual ~InstallableValue() { }
virtual std::pair<Value *, PosIdx> toValue(EvalState & state) = 0;
virtual std::pair<Value, PosIdx> toValue(EvalState & state) = 0;
/**
* Get a cursor to each value this Installable could refer to.
+13 -15
View File
@@ -235,14 +235,13 @@ void SourceExprCommand::completeInstallable(EvalState & state, AddCompletions &
prefix_ = "";
}
auto [v, pos] = findAlongAttrPath(state, prefix_, *autoArgs, root);
Value &v1(*v);
auto [v1, pos] = findAlongAttrPath(state, prefix_, *autoArgs, root);
state.forceValue(v1, pos);
Value v2;
state.autoCallFunction(*autoArgs, v1, v2, pos);
if (v2.type() == nAttrs) {
for (auto & i : *v2.attrs) {
for (auto & i : *v2.attrs()) {
std::string name{evaluator->symbols[i.name]};
if (name.find(searchWord) == 0) {
if (prefix_ == "")
@@ -412,12 +411,12 @@ ref<eval_cache::EvalCache> openEvalCache(
if (getEnv("NIX_ALLOW_EVAL").value_or("1") == "0")
throw Error("not everything is cached, but evaluation is not allowed");
auto vFlake = state.ctx.mem.allocValue();
flake::callFlake(state, *lockedFlake, *vFlake);
Value vFlake;
flake::callFlake(state, *lockedFlake, vFlake);
state.forceAttrs(*vFlake, noPos, "while parsing cached flake data");
state.forceAttrs(vFlake, noPos, "while parsing cached flake data");
auto aOutputs = vFlake->attrs->get(state.ctx.symbols.create("outputs"));
auto aOutputs = vFlake.attrs()->get(state.ctx.symbols.create("outputs"));
assert(aOutputs);
return aOutputs->value;
@@ -450,25 +449,24 @@ Installables SourceExprCommand::parseInstallables(
throw UsageError("'--file' and '--expr' are exclusive");
auto evaluator = getEvaluator();
auto vFile = evaluator->mem.allocValue();
Value vFile;
if (file == "-") {
auto & e = evaluator->parseStdin();
state.eval(e, *vFile);
state.eval(e, vFile);
}
else if (file)
state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap(), *vFile);
state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap(), vFile);
else {
auto & e = evaluator->parseExprFromString(*expr, CanonPath::fromCwd());
state.eval(e, *vFile);
state.eval(e, vFile);
}
for (auto & s : ss) {
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s);
result.push_back(
make_ref<InstallableAttrPath>(
InstallableAttrPath::parse(
evaluator, *this, vFile, std::move(prefix), std::move(extendedOutputsSpec))));
result.push_back(make_ref<InstallableAttrPath>(InstallableAttrPath::parse(
evaluator, *this, vFile, std::move(prefix), std::move(extendedOutputsSpec)
)));
}
} else {
+65 -57
View File
@@ -5,6 +5,7 @@
#include <cstring>
#include <string_view>
#include "lix/libexpr/value.hh"
#include "lix/libutil/box_ptr.hh"
#include "lix/libcmd/repl-interacter.hh"
#include "lix/libcmd/repl.hh"
@@ -173,35 +174,37 @@ struct NixRepl
/**
* Get a list of each of the `repl-overlays` (parsed and evaluated).
*/
Value * replOverlays();
Value replOverlays();
/**
* Get the Nix function that composes the `repl-overlays` together.
*/
Value * getReplOverlaysEvalFunction();
Value getReplOverlaysEvalFunction();
/**
* Cached return value of `getReplOverlaysEvalFunction`.
*
* Note: This is `shared_ptr` to avoid garbage collection.
*/
std::shared_ptr<Value *> replOverlaysEvalFunction =
std::allocate_shared<Value *>(TraceableAllocator<Value *>(), nullptr);
std::shared_ptr<std::optional<Value>> replOverlaysEvalFunction =
std::allocate_shared<std::optional<Value>>(
TraceableAllocator<std::optional<Value>>(), std::nullopt
);
/**
* Get the `info` AttrSet that's passed as the first argument to each
* of the `repl-overlays`.
*/
Value * replInitInfo();
Value replInitInfo();
/**
* Get the current top-level bindings as an AttrSet.
*/
Value * bindingsToAttrs();
Value bindingsToAttrs();
/**
* Parse a file, evaluate its result, and force the resulting value.
*/
Value * evalFile(SourcePath & path);
Value evalFile(SourcePath & path);
void printValue(std::ostream & str,
Value & v,
@@ -450,7 +453,7 @@ StringSet NixRepl::completePrefix(const std::string &prefix)
e.eval(state, *env, v);
state.forceAttrs(v, noPos, "while evaluating an attrset for the purpose of completion (this error should not be displayed; file an issue?)");
for (auto & i : *v.attrs) {
for (auto & i : *v.attrs()) {
std::ostringstream output;
printAttributeName(output, evaluator.symbols[i.name]);
std::string name = output.str();
@@ -653,7 +656,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
auto path = state.coerceToPath(noPos, v, context, "while evaluating the filename to edit");
return {path, 0};
} else if (v.isLambda()) {
auto pos = evaluator.positions[v.lambda.fun->pos];
auto pos = evaluator.positions[v.lambda().fun->pos];
if (auto path = std::get_if<CheckedSourcePath>(&pos.origin))
return {*path, pos.line};
else
@@ -822,7 +825,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
logger->cout(trim(renderMarkdownToTerminal(markdown)));
} else if (v.isLambda()) {
auto pos = evaluator.positions[v.lambda.fun->pos];
auto pos = evaluator.positions[v.lambda().fun->pos];
if (auto path = std::get_if<CheckedSourcePath>(&pos.origin)) {
// Path and position have now been obtained, feed to nix-doc library to get data.
auto docComment = lambdaDocsForPos(*path, pos);
@@ -862,10 +865,10 @@ ProcessLineResult NixRepl::processLine(std::string line)
std::visit(overloaded {
[&](ExprReplBindings & b) {
for (auto & [name, e] : b.symbols) {
Value * v = state.ctx.mem.allocValue();
e->eval(state, *env, *v);
Value v;
e->eval(state, *env, v);
(void) e.release(); // NOLINT(bugprone-unused-return-value): leak because of thunk references
addVarToScope(name, *v);
addVarToScope(name, v);
}
},
[&](std::unique_ptr<Expr> & e) {
@@ -948,7 +951,7 @@ void NixRepl::loadFiles()
for (auto & [i, what] : getValues()) {
notice("Loading installable '%1%'...", Magenta(what));
addAttrsToScope(*i);
addAttrsToScope(i);
}
loadReplOverlays();
@@ -963,9 +966,9 @@ void NixRepl::loadReplOverlays()
notice("Loading '%1%'...", "repl-overlays");
auto replInitFilesFunction = getReplOverlaysEvalFunction();
Value &newAttrs(*evaluator.mem.allocValue());
SmallValueVector<3> args = {replInitInfo(), bindingsToAttrs(), replOverlays()};
state.callFunction(*replInitFilesFunction, args.size(), args.data(), newAttrs, noPos);
Value newAttrs;
Value args[] = {replInitInfo(), bindingsToAttrs(), replOverlays()};
state.callFunction(replInitFilesFunction, args, newAttrs, noPos);
// n.b. this does in fact load the stuff into the environment twice (once
// from the superset of the environment returned by repl-overlays and once
@@ -975,14 +978,14 @@ void NixRepl::loadReplOverlays()
addAttrsToScope(newAttrs);
}
Value * NixRepl::getReplOverlaysEvalFunction()
Value NixRepl::getReplOverlaysEvalFunction()
{
if (replOverlaysEvalFunction && *replOverlaysEvalFunction) {
return *replOverlaysEvalFunction;
return **replOverlaysEvalFunction;
}
auto evalReplInitFilesPath = CanonPath::root + "repl-overlays.nix";
*replOverlaysEvalFunction = evaluator.mem.allocValue();
*replOverlaysEvalFunction = Value{};
auto code =
#include "repl-overlays.nix.gen.hh"
;
@@ -994,14 +997,14 @@ Value * NixRepl::getReplOverlaysEvalFunction()
state.eval(expr, **replOverlaysEvalFunction);
return *replOverlaysEvalFunction;
return **replOverlaysEvalFunction;
}
Value * NixRepl::replOverlays()
Value NixRepl::replOverlays()
{
Value * replInits(evaluator.mem.allocValue());
*replInits = evaluator.mem.newList(evalSettings.replOverlays.get().size());
Value ** replInitElems = replInits->listElems();
Value replInits;
auto replInitElems = evaluator.mem.newList(evalSettings.replOverlays.get().size());
replInits = {NewValueAs::list, replInitElems};
size_t i = 0;
for (auto path : evalSettings.replOverlays.get()) {
@@ -1017,27 +1020,32 @@ Value * NixRepl::replOverlays()
auto replInit = evalFile(sourcePath);
evalSettings.pureEval.setDefault(prevPureEval);
if (!replInit->isLambda()) {
evaluator.errors.make<TypeError>(
"Expected `repl-overlays` entry %s to be a lambda but found %s: %s",
path,
showType(*replInit),
ValuePrinter(state, *replInit, errorPrintOptions)
)
.debugThrow();
}
if (auto attrs = dynamic_cast<AttrsPattern *>(replInit->lambda.fun->pattern.get()); attrs && !attrs->ellipsis) {
evaluator.errors.make<TypeError>(
"Expected first argument of %1% to have %2% to allow future versions of Lix to add additional attributes to the argument",
"repl-overlays",
"..."
)
.atPos(replInit->lambda.fun->pos)
if (!replInit.isLambda()) {
evaluator.errors
.make<TypeError>(
"Expected `repl-overlays` entry %s to be a lambda but found %s: %s",
path,
showType(replInit),
ValuePrinter(state, replInit, errorPrintOptions)
)
.debugThrow();
}
replInitElems[i] = replInit;
if (auto attrs = dynamic_cast<AttrsPattern *>(replInit.lambda().fun->pattern.get());
attrs && !attrs->ellipsis)
{
evaluator.errors
.make<TypeError>(
"Expected first argument of %1% to have %2% to allow future versions of Lix to "
"add additional attributes to the argument",
"repl-overlays",
"..."
)
.atPos(replInit.lambda().fun->pos)
.debugThrow();
}
replInitElems->elems[i] = replInit;
i++;
}
@@ -1045,16 +1053,16 @@ Value * NixRepl::replOverlays()
return replInits;
}
Value * NixRepl::replInitInfo()
Value NixRepl::replInitInfo()
{
auto builder = evaluator.buildBindings(2);
Value * currentSystem(evaluator.mem.allocValue());
currentSystem->mkString(evalSettings.getCurrentSystem());
Value currentSystem;
currentSystem.mkString(evalSettings.getCurrentSystem());
builder.insert(evaluator.symbols.create("currentSystem"), currentSystem);
Value * info(evaluator.mem.allocValue());
info->mkAttrs(builder.finish());
Value info;
info.mkAttrs(builder.finish());
return info;
}
@@ -1090,7 +1098,7 @@ void NixRepl::addAttrsToScope(Value & attrs)
{
state.forceAttrs(attrs, noPos, "while evaluating an attribute set to be merged in the global scope");
addToScope(
*attrs.attrs, [](const Attr & a) { return a.name; }, [](const Attr & a) { return a.value; }
*attrs.attrs(), [](const Attr & a) { return a.name; }, [](const Attr & a) { return a.value; }
);
}
@@ -1112,19 +1120,19 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
} else {
notice("Added %s.", evaluator.symbols[name]);
}
env->values[displ++] = &v;
env->values[displ++] = v;
varNames.emplace(evaluator.symbols[name]);
}
Value * NixRepl::bindingsToAttrs()
Value NixRepl::bindingsToAttrs()
{
auto builder = evaluator.buildBindings(staticEnv->vars.size());
for (auto & [symbol, displacement] : staticEnv->vars) {
builder.insert(symbol, env->values[displacement]);
}
Value * attrs(evaluator.mem.allocValue());
attrs->mkAttrs(builder.finish());
Value attrs;
attrs.mkAttrs(builder.finish());
return attrs;
}
@@ -1147,12 +1155,12 @@ void NixRepl::evalString(std::string s, Value & v)
state.forceValue(v, noPos);
}
Value * NixRepl::evalFile(SourcePath & path)
Value NixRepl::evalFile(SourcePath & path)
{
auto & expr = evaluator.parseExprFromFile(evaluator.paths.checkSourcePath(path), staticEnv);
Value * result(evaluator.mem.allocValue());
expr.eval(state, *env, *result);
state.forceValue(*result, noPos);
Value result;
expr.eval(state, *env, result);
state.forceValue(result, noPos);
return result;
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace nix {
struct AbstractNixRepl : NeverAsync
{
typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
typedef std::vector<std::pair<Value, std::string>> AnnotatedValues;
static ReplExitStatus
run(const SearchPath & searchPath,
+24 -22
View File
@@ -69,13 +69,12 @@ std::string unparseAttrPath(std::vector<std::string> const & attrPath)
return ret.str();
}
std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::string & attrPath,
Bindings & autoArgs, Value & vIn)
std::pair<Value, PosIdx>
findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & autoArgs, Value & vIn)
{
auto tokens = parseAttrPath(attrPath);
Value * v = &vIn;
Value v = vIn;
PosIdx pos = noPos;
for (auto [attrPathIdx, attr] : enumerate(tokens)) {
@@ -84,10 +83,10 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
auto attrIndex = string2Int<unsigned int>(attr);
/* Evaluate the expression. */
Value * vNew = state.ctx.mem.allocValue();
state.autoCallFunction(autoArgs, *v, *vNew, pos);
Value vNew;
state.autoCallFunction(autoArgs, v, vNew, pos);
v = vNew;
state.forceValue(*v, noPos);
state.forceValue(v, noPos);
/* It should evaluate to either a set or an expression,
according to what is specified in the attrPath. */
@@ -96,7 +95,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
if (attr.empty())
throw Error("empty attribute name in selection path '%1%'", attrPath);
if (v->type() != nAttrs) {
if (v.type() != nAttrs) {
auto pathPart =
std::vector<std::string>(tokens.begin(), tokens.begin() + attrPathIdx);
state.ctx.errors
@@ -105,17 +104,18 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
"set but is %3%: %4%",
attrPath,
unparseAttrPath(pathPart),
showType(*v),
ValuePrinter(state, *v, errorPrintOptions)
showType(v),
ValuePrinter(state, v, errorPrintOptions)
)
.debugThrow();
}
auto a = v->attrs->get(state.ctx.symbols.create(attr));
auto a = v.attrs()->get(state.ctx.symbols.create(attr));
if (!a) {
std::set<std::string> attrNames;
for (auto & attr : *v->attrs)
for (auto & attr : *v.attrs()) {
attrNames.emplace(state.ctx.symbols[attr.name]);
}
auto suggestions = Suggestions::bestMatches(attrNames, attr);
auto pathPart =
@@ -127,33 +127,33 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
attr,
attrPath,
unparseAttrPath(pathPart),
ValuePrinter(state, *v, errorPrintOptions)
ValuePrinter(state, v, errorPrintOptions)
);
}
v = &*a->value;
v = a->value;
pos = a->pos;
} else {
if (!v->isList()) {
if (!v.isList()) {
state.ctx.errors
.make<TypeError>(
"the expression selected by the selection path '%1%' should be a list but "
"is %2%: %3%",
attrPath,
showType(*v),
ValuePrinter(state, *v, errorPrintOptions)
showType(v),
ValuePrinter(state, v, errorPrintOptions)
)
.debugThrow();
}
if (*attrIndex >= v->listSize()) {
if (*attrIndex >= v.listSize()) {
throw AttrPathNotFound(
"list index %1% in selection path '%2%' is out of range for list %3%",
*attrIndex,
attrPath,
ValuePrinter(state, *v, errorPrintOptions)
ValuePrinter(state, v, errorPrintOptions)
);
}
v = v->listElems()[*attrIndex];
v = v.listElems()[*attrIndex];
pos = noPos;
}
@@ -165,7 +165,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
{
Value * v2;
Value v2;
try {
auto dummyArgs = state.ctx.mem.allocBindings(0);
v2 = findAlongAttrPath(state, "meta.position", *dummyArgs, v).first;
@@ -176,7 +176,9 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
// FIXME: is it possible to extract the Pos object instead of doing this
// toString + parsing?
NixStringContext context;
auto path = state.coerceToPath(noPos, *v2, context, "while evaluating the 'meta.position' attribute of a derivation");
auto path = state.coerceToPath(
noPos, v2, context, "while evaluating the 'meta.position' attribute of a derivation"
);
auto fn = path.canonical().abs();
+3 -5
View File
@@ -10,11 +10,9 @@ namespace nix {
MakeError(AttrPathNotFound, Error);
MakeError(NoPositionInfo, Error);
std::pair<Value *, PosIdx> findAlongAttrPath(
EvalState & state,
const std::string & attrPath,
Bindings & autoArgs,
Value & vIn);
std::pair<Value, PosIdx> findAlongAttrPath(
EvalState & state, const std::string & attrPath, Bindings & autoArgs, Value & vIn
);
/**
* Heuristic to find the filename and lineno or a nix value.
+3 -4
View File
@@ -20,15 +20,14 @@ Bindings * EvalMemory::allocBindings(size_t capacity)
throw Error("attribute set of size %d is too big", capacity);
stats.nrAttrsets++;
stats.nrAttrsInAttrsets += capacity;
return new (gcAllocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings();
return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings();
}
Value & BindingsBuilder::alloc(Symbol name, PosIdx pos)
{
auto value = mem.allocValue();
bindings->push_back(Attr(name, value, pos));
return *value;
bindings->push_back(Attr(name, {}, pos));
return (bindings->end() - 1)->value;
}
+4 -5
View File
@@ -23,9 +23,8 @@ struct Attr
way we keep Attr size at two words with no wasted space. */
Symbol name;
PosIdx pos;
Value * value;
Attr(Symbol name, Value * value, PosIdx pos = noPos)
: name(name), pos(pos), value(value) { };
mutable Value value;
Attr(Symbol name, Value value, PosIdx pos = noPos) : name(name), pos(pos), value(value) {}
Attr() { };
bool operator < (const Attr & a) const
{
@@ -73,7 +72,7 @@ public:
const Attr * get(Symbol name)
{
Attr key(name, 0);
Attr key(name, {});
iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return &*i;
return nullptr;
@@ -136,7 +135,7 @@ public:
{
}
void insert(Symbol name, Value * value, PosIdx pos = noPos)
void insert(Symbol name, Value value, PosIdx pos = noPos)
{
insert(Attr(name, value, pos));
}
+22 -16
View File
@@ -341,7 +341,7 @@ EvalCache::EvalCache(
{
}
Value * EvalCache::getRootValue(EvalState & state)
Value & EvalCache::getRootValue(EvalState & state)
{
if (!value) {
debug("getting root value");
@@ -362,8 +362,9 @@ AttrCursor::AttrCursor(
std::optional<std::pair<AttrId, AttrValue>> && cachedValue)
: root(root), parent(parent), cachedValue(std::move(cachedValue))
{
if (value)
_value = allocRootValue(value);
if (value) {
_value = allocRootValue(*value);
}
}
AttrKey AttrCursor::getKey()
@@ -383,14 +384,14 @@ Value & AttrCursor::getValue(EvalState & state)
if (parent) {
auto & vParent = parent->first->getValue(state);
state.forceAttrs(vParent, noPos, "while searching for an attribute");
auto attr = vParent.attrs->get(state.ctx.symbols.create(parent->second));
auto attr = vParent.attrs()->get(state.ctx.symbols.create(parent->second));
if (!attr)
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr(state));
_value = allocRootValue(attr->value);
} else
_value = allocRootValue(root->getRootValue(state));
}
return **_value;
return *_value;
}
std::vector<std::string> AttrCursor::getAttrPath(EvalState & state) const
@@ -438,16 +439,16 @@ Value & AttrCursor::forceValue(EvalState & state)
if (root->db && (!cachedValue || std::get_if<placeholder_t>(&cachedValue->second))) {
if (v.type() == nString)
cachedValue = {
root->db->setString(getKey(), v.str(), v.string.context), string_t{v.str(), {}}
root->db->setString(getKey(), v.str(), v.string().context), string_t{v.str(), {}}
};
else if (v.type() == nPath) {
auto path = v.path().canonical().abs();
cachedValue = {root->db->setString(getKey(), path), string_t{path, {}}};
}
else if (v.type() == nBool)
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
cachedValue = {root->db->setBool(getKey(), v.boolean()), v.boolean()};
else if (v.type() == nInt)
cachedValue = {root->db->setInt(getKey(), v.integer.value), int_t{v.integer}};
cachedValue = {root->db->setInt(getKey(), v.integer().value), int_t{v.integer()}};
else if (v.type() == nAttrs)
; // FIXME: do something?
else
@@ -500,7 +501,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(EvalState & state, const st
return nullptr;
//errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
auto attr = v.attrs->get(state.ctx.symbols.create(name));
auto attr = v.attrs()->get(state.ctx.symbols.create(name));
if (!attr) {
if (root->db) {
@@ -519,7 +520,8 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(EvalState & state, const st
}
return make_ref<AttrCursor>(
root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2));
root, std::make_pair(shared_from_this(), name), &attr->value, std::move(cachedValue2)
);
}
ref<AttrCursor> AttrCursor::getAttr(EvalState & state, const std::string & name)
@@ -633,7 +635,7 @@ bool AttrCursor::getBool(EvalState & state)
if (v.type() != nBool)
state.ctx.errors.make<TypeError>("'%s' is not a Boolean", getAttrPathStr(state)).debugThrow();
return v.boolean;
return v.boolean();
}
NixInt AttrCursor::getInt(EvalState & state)
@@ -655,7 +657,7 @@ NixInt AttrCursor::getInt(EvalState & state)
if (v.type() != nInt)
state.ctx.errors.make<TypeError>("'%s' is not an integer", getAttrPathStr(state)).debugThrow();
return v.integer;
return v.integer();
}
std::vector<std::string> AttrCursor::getListOfStrings(EvalState & state)
@@ -682,11 +684,15 @@ std::vector<std::string> AttrCursor::getListOfStrings(EvalState & state)
std::vector<std::string> res;
for (auto & elem : v.listItems())
res.push_back(std::string(state.forceStringNoCtx(*elem, noPos, "while evaluating an attribute for caching")));
for (auto & elem : v.listItems()) {
res.push_back(std::string(
state.forceStringNoCtx(elem, noPos, "while evaluating an attribute for caching")
));
}
if (root->db)
if (root->db) {
cachedValue = {root->db->setListOfStrings(getKey(), res), res};
}
return res;
}
@@ -711,7 +717,7 @@ std::vector<std::string> AttrCursor::getAttrs(EvalState & state)
state.ctx.errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr(state)).debugThrow();
fullattr_t attrs;
for (auto & attr : *getValue(state).attrs)
for (auto & attr : *getValue(state).attrs())
attrs.p.emplace_back(state.ctx.symbols[attr.name]);
std::sort(attrs.p.begin(), attrs.p.end());
+2 -2
View File
@@ -12,7 +12,7 @@ namespace nix::eval_cache {
struct AttrDb;
class AttrCursor;
typedef std::function<Value *(EvalState &)> RootLoader;
typedef std::function<Value(EvalState &)> RootLoader;
/**
* EvalState with caching support. Historically this was part of EvalState,
@@ -42,7 +42,7 @@ class EvalCache : public std::enable_shared_from_this<EvalCache>
RootLoader rootLoader;
RootValue value;
Value * getRootValue(EvalState & state);
Value & getRootValue(EvalState & state);
public:
+94 -43
View File
@@ -5,59 +5,94 @@
#include "lix/libexpr/eval.hh"
#include "lix/libexpr/eval-error.hh"
#include "lix/libexpr/gc-alloc.hh"
#include "value.hh"
#include <cstdint>
namespace nix {
inline Value::Value(app_t, EvalMemory & mem, Value & lhs, Value & rhs)
{
auto app = static_cast<Value::App *>(mem.allocBytes(sizeof(Value::App) + sizeof(Value *)));
app->_left = lhs;
app->_n = 1;
app->_args[0] = rhs;
raw = tag(tApp, app);
}
inline Value::Value(app_t, EvalMemory & mem, Value & lhs, std::span<Value> args)
{
auto app = static_cast<Value::App *>(mem.allocBytes(sizeof(Value::App) + args.size_bytes()));
app->_left = lhs;
app->_n = args.size();
std::copy(args.begin(), args.end(), app->_args);
raw = tag(tApp, app);
}
inline Value::Value(thunk_t, EvalMemory & mem, Env & env, Expr & expr)
{
auto thunk = mem.allocType<Thunk>();
*thunk = {._env = reinterpret_cast<uintptr_t>(&env), .expr = &expr};
raw = tag(tThunk, thunk);
}
inline Value::Value(lambda_t, EvalMemory & mem, Env & env, ExprLambda & lambda)
{
auto lp = mem.allocType<Lambda>();
new (lp) Lambda{env, lambda};
raw = tag(tAuxiliary, lp);
}
[[gnu::always_inline]]
Value * EvalMemory::allocValue()
void * EvalMemory::allocBytes(size_t size)
{
#if HAVE_BOEHMGC
/* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
GC_malloc_many returns a linked list of objects of the given size, where the first word
of each object is also the pointer to the next object in the list. This also means that we
have to explicitly clear the first word of every object we take. */
if (!*valueAllocCache) {
*valueAllocCache = GC_malloc_many(sizeof(Value));
if (!*valueAllocCache) throw std::bad_alloc();
}
// NOTE: we purposely do not allocate 0 byte blocks on caches; we never allocate
// zero bytes anyway, and it makes cache index calculation a little bit simpler.
const auto cacheIdx = (size - 1) / CACHE_INCREMENT;
if (cacheIdx < CACHES) {
const auto roundedSize = (cacheIdx + 1) * CACHE_INCREMENT;
auto & cache = gcCache[cacheIdx];
if (!cache) {
cache = GC_malloc_many(roundedSize);
if (!cache) {
throw std::bad_alloc();
}
}
/* GC_NEXT is a convenience macro for accessing the first word of an object.
Take the first list item, advance the list to the next item, and clear the next pointer. */
void * p = *valueAllocCache;
*valueAllocCache = GC_NEXT(p);
GC_NEXT(p) = nullptr;
#else
void * p = gcAllocBytes(sizeof(Value));
/* GC_NEXT is a convenience macro for accessing the first word of an object.
Take the first list item, advance the list to the next item, and clear the next pointer.
*/
void * p = cache;
cache = GC_NEXT(p);
GC_NEXT(p) = nullptr;
return p;
}
#endif
stats.nrValues++;
return static_cast<Value *>(p);
return gcAllocBytes(size);
}
/// `gcAllocType`, but using allocation caches to amortize allocation overhead.
template<typename T>
[[gnu::always_inline]]
T * EvalMemory::allocType(size_t n)
{
return static_cast<T *>(allocBytes(checkedArrayAllocSize(sizeof(T), n)));
}
[[gnu::always_inline]]
Env & EvalMemory::allocEnv(size_t size)
{
static_assert(CACHES * CACHE_INCREMENT >= sizeof(Env) + sizeof(Value *));
stats.nrEnvs++;
stats.nrValuesInEnvs += size;
Env * env;
#if HAVE_BOEHMGC
if (size == 1) {
/* see allocValue for explanations. */
if (!*env1AllocCache) {
*env1AllocCache = GC_malloc_many(sizeof(Env) + sizeof(Value *));
if (!*env1AllocCache) throw std::bad_alloc();
}
void * p = *env1AllocCache;
*env1AllocCache = GC_NEXT(p);
GC_NEXT(p) = nullptr;
env = static_cast<Env *>(p);
} else
#endif
env = static_cast<Env *>(gcAllocBytes(sizeof(Env) + size * sizeof(Value *)));
Env * env = static_cast<Env *>(allocBytes(sizeof(Env) + size * sizeof(Value *)));
/* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */
@@ -69,22 +104,38 @@ Env & EvalMemory::allocEnv(size_t size)
void EvalState::forceValue(Value & v, const PosIdx pos)
{
if (v.isThunk()) {
Env * env = v.thunk.env;
Expr & expr = *v.thunk.expr;
try {
v.mkBlackhole();
expr.eval(*this, *env, v);
} catch (...) {
v.mkThunk(env, expr);
tryFixupBlackHolePos(v, pos);
throw;
auto & thunk = v.thunk();
if (thunk.resolved()) {
v = thunk.result();
} else {
const auto backup = thunk;
Env * env = thunk.env();
Expr & expr = *thunk.expr;
thunk = Value::blackHole;
try {
expr.eval(*this, *env, v);
thunk.resolve(v);
} catch (...) {
thunk = backup;
tryFixupBlackHolePos(v, pos);
throw;
}
}
} else if (v.isApp()) {
auto & app = v.app();
if (app.resolved()) {
v = app.result();
} else {
auto target = app.target();
if (!target.isPrimOp() || target.primOp()->arity <= app.totalArgs()) {
auto tmp = v.app().left();
callFunction(tmp, v.app().args(), v, pos);
app.resolve(v);
}
}
}
else if (v.isApp())
callFunction(*v.app.left, *v.app.right, v, pos);
}
[[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx)
{
+343 -283
View File
File diff suppressed because it is too large Load Diff
+20 -67
View File
@@ -36,52 +36,7 @@ namespace eval_cache {
class EvalCache;
}
/**
* Function that implements a primop.
*/
using PrimOpImpl = void(EvalState & state, Value ** args, Value & v);
/**
* Info about a primitive operation, and its implementation
*/
struct PrimOp
{
/**
* Name of the primop. `__` prefix is treated specially.
*/
std::string name;
/**
* Names of the parameters of a primop, for primops that take a
* fixed number of arguments to be substituted for these parameters.
*/
std::vector<std::string> args;
/**
* Aritiy of the primop.
*
* If `args` is not empty, this field will be computed from that
* field instead, so it doesn't need to be manually set.
*/
size_t arity = 0;
/**
* Optional free-form documentation about the primop.
*/
const char * doc = nullptr;
/**
* Implementation of the primop.
*/
std::function<PrimOpImpl> fun;
/**
* Optional experimental for this to be gated on.
*/
std::optional<ExperimentalFeature> experimentalFeature;
};
std::ostream & operator<<(std::ostream & output, PrimOp & primOp);
std::ostream & operator<<(std::ostream & output, const PrimOp & primOp);
/**
* Info about a constant
@@ -106,12 +61,12 @@ struct Constant
bool impureOnly = false;
};
using ValMap = GcMap<std::string, Value *>;
using ValMap = GcMap<std::string, Value>;
struct Env
struct alignas(Value::Acb::TAG_ALIGN) Env
{
Env * up;
Value * values[0];
Value values[0];
};
void printEnvBindings(const EvalState &es, const Expr & expr, const Env & env);
@@ -224,39 +179,40 @@ struct StaticSymbols
class EvalMemory
{
/**
* Allocation cache for GC'd Value objects.
*/
std::shared_ptr<void *> valueAllocCache;
static constexpr size_t CACHES = 8;
static constexpr size_t CACHE_INCREMENT = sizeof(void *);
/**
* Allocation cache for size-1 Env objects.
* Allocation caches for small values.
*/
std::shared_ptr<void *> env1AllocCache;
void * gcCache[CACHES] = {};
public:
struct Statistics
{
unsigned long nrEnvs = 0;
unsigned long nrValuesInEnvs = 0;
unsigned long nrValues = 0;
unsigned long nrAttrsets = 0;
unsigned long nrAttrsInAttrsets = 0;
unsigned long nrListElems = 0;
};
EvalMemory();
~EvalMemory();
EvalMemory(const EvalMemory &) = delete;
EvalMemory(EvalMemory &&) = delete;
EvalMemory & operator=(const EvalMemory &) = delete;
EvalMemory & operator=(EvalMemory &&) = delete;
inline Value * allocValue();
inline void * allocBytes(size_t size);
template<typename T>
inline T * allocType(size_t n = 1);
inline Env & allocEnv(size_t size);
Bindings * allocBindings(size_t capacity);
Value newList(size_t length);
Value::List * newList(size_t length);
BindingsBuilder buildBindings(SymbolTable & symbols, size_t capacity)
{
@@ -307,11 +263,9 @@ private:
void createBaseEnv(const SearchPath & searchPath, const Path & storeDir);
Value * addConstant(const std::string & name, const Value & v, Constant info);
void addConstant(const std::string & name, const Value & v, Constant info);
void addConstant(const std::string & name, Value * v, Constant info);
Value * addPrimOp(PrimOp && primOp);
void addPrimOp(PrimOpDetails && primOp);
Value prepareNixPath(const SearchPath & searchPath);
@@ -839,13 +793,11 @@ public:
bool isFunctor(Value & fun);
// FIXME: use std::span
void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos);
void callFunction(Value & fun, std::span<Value> args, Value & vRes, const PosIdx pos);
void callFunction(Value & fun, Value & arg, Value & vRes, const PosIdx pos)
{
Value * args[] = {&arg};
callFunction(fun, 1, args, vRes, pos);
callFunction(fun, {&arg, 1}, vRes, pos);
}
/**
@@ -885,7 +837,8 @@ public:
const SingleDerivedPath & p,
Value & v);
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);
void
concatLists(Value & v, std::span<Value> lists, const PosIdx pos, std::string_view errorCtx);
private:
+121 -81
View File
@@ -96,7 +96,7 @@ static void expectType(EvalState & state, ValueType type,
static std::pair<std::map<FlakeId, FlakeInput>, std::optional<fetchers::Attrs>> parseFlakeInputs(
EvalState & state,
Value * value,
Value & value,
const PosIdx pos,
const std::optional<Path> & baseDir,
InputPath lockRootPath,
@@ -109,15 +109,15 @@ static void parseFlakeInputAttr(EvalState & state, const Attr & attr, fetchers::
// Allow selecting a subset of enum values
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (attr.value->type()) {
switch (attr.value.type()) {
case nString:
attrs.emplace(state.ctx.symbols[attr.name], std::string(attr.value->str()));
attrs.emplace(state.ctx.symbols[attr.name], std::string(attr.value.str()));
break;
case nBool:
attrs.emplace(state.ctx.symbols[attr.name], Explicit<bool>{attr.value->boolean});
attrs.emplace(state.ctx.symbols[attr.name], Explicit<bool>{attr.value.boolean()});
break;
case nInt: {
auto intValue = attr.value->integer.value;
auto intValue = attr.value.integer().value;
if (intValue < 0) {
state.ctx.errors
@@ -137,18 +137,24 @@ static void parseFlakeInputAttr(EvalState & state, const Attr & attr, fetchers::
.make<TypeError>(
"flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
state.ctx.symbols[attr.name],
showType(*attr.value)
showType(attr.value)
)
.debugThrow();
}
#pragma GCC diagnostic pop
}
static FlakeInput parseFlakeInput(EvalState & state,
const std::string & inputName, Value * value, const PosIdx pos,
const std::optional<Path> & baseDir, InputPath lockRootPath, unsigned depth)
static FlakeInput parseFlakeInput(
EvalState & state,
const std::string & inputName,
Value & value,
const PosIdx pos,
const std::optional<Path> & baseDir,
InputPath lockRootPath,
unsigned depth
)
{
expectType(state, nAttrs, *value, pos);
expectType(state, nAttrs, value, pos);
FlakeInput input;
@@ -160,15 +166,15 @@ static FlakeInput parseFlakeInput(EvalState & state,
fetchers::Attrs attrs;
std::optional<std::string> url;
for (nix::Attr attr : *(value->attrs)) {
for (nix::Attr attr : *(value.attrs())) {
try {
if (attr.name == sUrl) {
expectType(state, nString, *attr.value, attr.pos);
url = attr.value->str();
expectType(state, nString, attr.value, attr.pos);
url = attr.value.str();
attrs.emplace("url", *url);
} else if (attr.name == sFlake) {
expectType(state, nBool, *attr.value, attr.pos);
input.isFlake = attr.value->boolean;
expectType(state, nBool, attr.value, attr.pos);
input.isFlake = attr.value.boolean();
} else if (attr.name == sInputs) {
input.overrides =
parseFlakeInputs(
@@ -176,8 +182,8 @@ static FlakeInput parseFlakeInput(EvalState & state,
)
.first;
} else if (attr.name == sFollows) {
expectType(state, nString, *attr.value, attr.pos);
auto follows(parseInputPath(attr.value->str()));
expectType(state, nString, attr.value, attr.pos);
auto follows(parseInputPath(attr.value.str()));
follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end());
input.follows = follows;
} else {
@@ -218,7 +224,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
static std::pair<std::map<FlakeId, FlakeInput>, std::optional<fetchers::Attrs>> parseFlakeInputs(
EvalState & state,
Value * value,
Value & value,
const PosIdx pos,
const std::optional<Path> & baseDir,
InputPath lockRootPath,
@@ -228,10 +234,10 @@ static std::pair<std::map<FlakeId, FlakeInput>, std::optional<fetchers::Attrs>>
{
std::map<FlakeId, FlakeInput> inputs;
expectType(state, nAttrs, *value, pos);
expectType(state, nAttrs, value, pos);
std::optional<fetchers::Attrs> selfAttrs = std::nullopt;
for (const nix::Attr & inputAttr : *(*value).attrs) {
for (const nix::Attr & inputAttr : *value.attrs()) {
std::string inputName{state.ctx.symbols[inputAttr.name]};
if (inputName == "self") {
experimentalFeatureSettings.require(Xp::FlakeSelfAttrs);
@@ -241,10 +247,10 @@ static std::pair<std::map<FlakeId, FlakeInput>, std::optional<fetchers::Attrs>>
"'self' input attributes not allowed at %s", state.ctx.positions[inputAttr.pos]
);
}
expectType(state, nAttrs, *inputAttr.value, inputAttr.pos);
expectType(state, nAttrs, inputAttr.value, inputAttr.pos);
selfAttrs = selfAttrs.value_or(fetchers::Attrs{});
for (auto & attr : *inputAttr.value->attrs) {
for (auto & attr : *inputAttr.value.attrs()) {
parseFlakeInputAttr(state, attr, *selfAttrs);
}
} else {
@@ -328,14 +334,14 @@ static Flake getFlake(
Value vInfo;
state.eval(flakeExpr, vInfo);
if (auto description = vInfo.attrs->get(state.ctx.s.description)) {
expectType(state, nString, *description->value, description->pos);
flake.description = description->value->str();
if (auto description = vInfo.attrs()->get(state.ctx.s.description)) {
expectType(state, nString, description->value, description->pos);
flake.description = description->value.str();
}
auto sInputs = state.ctx.symbols.create("inputs");
if (auto inputs = vInfo.attrs->get(sInputs)) {
if (auto inputs = vInfo.attrs()->get(sInputs)) {
auto [flakeInputs, selfAttrs] =
parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir, lockRootPath, 0, true);
flake.inputs = std::move(flakeInputs);
@@ -361,11 +367,14 @@ static Flake getFlake(
flake.resolvedRef = resolvedRef;
}
if (auto outputs = vInfo.attrs->get(state.ctx.s.outputs)) {
expectType(state, nFunction, *outputs->value, outputs->pos);
if (auto outputs = vInfo.attrs()->get(state.ctx.s.outputs)) {
expectType(state, nFunction, outputs->value, outputs->pos);
if (outputs->value->isLambda()) {
if (auto pattern = dynamic_cast<AttrsPattern *>(outputs->value->lambda.fun->pattern.get()); pattern) {
if (outputs->value.isLambda()) {
if (auto pattern =
dynamic_cast<AttrsPattern *>(outputs->value.lambda().fun->pattern.get());
pattern)
{
for (auto & formal : pattern->formals) {
if (formal.name != state.ctx.s.self)
flake.inputs.emplace(
@@ -383,46 +392,71 @@ static Flake getFlake(
auto sNixConfig = state.ctx.symbols.create("nixConfig");
if (auto nixConfig = vInfo.attrs->get(sNixConfig)) {
expectType(state, nAttrs, *nixConfig->value, nixConfig->pos);
if (auto nixConfig = vInfo.attrs()->get(sNixConfig)) {
expectType(state, nAttrs, nixConfig->value, nixConfig->pos);
for (auto & setting : *nixConfig->value->attrs) {
forceTrivialValue(state, *setting.value, setting.pos);
if (setting.value->type() == nString)
for (auto & setting : *nixConfig->value.attrs()) {
forceTrivialValue(state, setting.value, setting.pos);
if (setting.value.type() == nString) {
flake.config.settings.emplace(
state.ctx.symbols[setting.name],
std::string(state.forceStringNoCtx(*setting.value, setting.pos, "")));
else if (setting.value->type() == nPath) {
std::string(state.forceStringNoCtx(setting.value, setting.pos, ""))
);
} else if (setting.value.type() == nPath) {
NixStringContext emptyContext = {};
flake.config.settings.emplace(
state.ctx.symbols[setting.name],
state.coerceToString(setting.pos, *setting.value, emptyContext, "", StringCoercionMode::Strict, true, true) .toOwned());
}
else if (setting.value->type() == nInt)
state
.coerceToString(
setting.pos,
setting.value,
emptyContext,
"",
StringCoercionMode::Strict,
true,
true
)
.toOwned()
);
} else if (setting.value.type() == nInt) {
flake.config.settings.emplace(
state.ctx.symbols[setting.name],
state.forceInt(*setting.value, setting.pos, "").value);
else if (setting.value->type() == nBool)
state.forceInt(setting.value, setting.pos, "").value
);
} else if (setting.value.type() == nBool) {
flake.config.settings.emplace(
state.ctx.symbols[setting.name],
Explicit<bool> { state.forceBool(*setting.value, setting.pos, "") });
else if (setting.value->type() == nList) {
Explicit<bool>{state.forceBool(setting.value, setting.pos, "")}
);
} else if (setting.value.type() == nList) {
std::vector<std::string> ss;
for (auto elem : setting.value->listItems()) {
if (elem->type() != nString)
state.ctx.errors.make<TypeError>("list element in flake configuration setting '%s' is %s while a string is expected",
state.ctx.symbols[setting.name], showType(*setting.value)).debugThrow();
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, ""));
for (auto & elem : setting.value.listItems()) {
if (elem.type() != nString) {
state.ctx.errors
.make<TypeError>(
"list element in flake configuration setting '%s' is %s while a "
"string is expected",
state.ctx.symbols[setting.name],
showType(setting.value)
)
.debugThrow();
}
ss.emplace_back(state.forceStringNoCtx(elem, setting.pos, ""));
}
flake.config.settings.emplace(state.ctx.symbols[setting.name], ss);
} else {
state.ctx.errors
.make<TypeError>(
"flake configuration setting '%s' is %s",
state.ctx.symbols[setting.name],
showType(setting.value)
)
.debugThrow();
}
else
state.ctx.errors.make<TypeError>("flake configuration setting '%s' is %s",
state.ctx.symbols[setting.name], showType(*setting.value)).debugThrow();
}
}
for (auto & attr : *vInfo.attrs) {
for (auto & attr : *vInfo.attrs()) {
if (attr.name != state.ctx.s.description &&
attr.name != sInputs &&
attr.name != state.ctx.s.outputs &&
@@ -907,34 +941,39 @@ void callFlake(EvalState & state,
const LockedFlake & lockedFlake,
Value & vRes)
{
auto vLocks = state.ctx.mem.allocValue();
auto vRootSrc = state.ctx.mem.allocValue();
auto vRootSubdir = state.ctx.mem.allocValue();
auto vTmp1 = state.ctx.mem.allocValue();
auto vTmp2 = state.ctx.mem.allocValue();
Value vLocks;
Value vRootSrc;
Value vRootSubdir;
Value vTmp1;
Value vTmp2;
vLocks->mkString(lockedFlake.lockFile.to_string());
vLocks.mkString(lockedFlake.lockFile.to_string());
emitTreeAttrs(
state.ctx,
*lockedFlake.flake.sourceInfo,
lockedFlake.flake.lockedRef.input,
*vRootSrc,
vRootSrc,
false,
lockedFlake.flake.forceDirty);
lockedFlake.flake.forceDirty
);
vRootSubdir->mkString(lockedFlake.flake.lockedRef.subdir);
vRootSubdir.mkString(lockedFlake.flake.lockedRef.subdir);
if (!state.ctx.caches.vCallFlake) {
state.ctx.caches.vCallFlake = allocRootValue(state.ctx.mem.allocValue());
state.eval(state.ctx.parseExprFromString(
#include "call-flake.nix.gen.hh"
, CanonPath::root), **state.ctx.caches.vCallFlake);
state.ctx.caches.vCallFlake = allocRootValue({});
state.eval(
state.ctx.parseExprFromString(
#include "call-flake.nix.gen.hh"
, CanonPath::root
),
*state.ctx.caches.vCallFlake
);
}
state.callFunction(**state.ctx.caches.vCallFlake, *vLocks, *vTmp1, noPos);
state.callFunction(*vTmp1, *vRootSrc, *vTmp2, noPos);
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
state.callFunction(*state.ctx.caches.vCallFlake, vLocks, vTmp1, noPos);
state.callFunction(vTmp1, vRootSrc, vTmp2, noPos);
state.callFunction(vTmp2, vRootSubdir, vRes, noPos);
}
void prim_getFlake(EvalState & state, Value * * args, Value & v)
@@ -984,10 +1023,10 @@ void prim_flakeRefToString(
state.forceAttrs(*args[0], noPos,
"while evaluating the argument passed to builtins.flakeRefToString");
fetchers::Attrs attrs;
for (const auto & attr : *args[0]->attrs) {
auto t = attr.value->type();
for (const auto & attr : *args[0]->attrs()) {
auto t = attr.value.type();
if (t == nInt) {
auto intValue = attr.value->integer.value;
auto intValue = attr.value.integer().value;
if (intValue < 0) {
state.ctx.errors.make<EvalError>("negative value given for flake ref attr %1%: %2%", state.ctx.symbols[attr.name], intValue).debugThrow();
@@ -996,17 +1035,18 @@ void prim_flakeRefToString(
attrs.emplace(state.ctx.symbols[attr.name], asUnsigned);
} else if (t == nBool) {
attrs.emplace(state.ctx.symbols[attr.name],
Explicit<bool> { attr.value->boolean });
attrs.emplace(state.ctx.symbols[attr.name], Explicit<bool>{attr.value.boolean()});
} else if (t == nString) {
attrs.emplace(state.ctx.symbols[attr.name],
std::string(attr.value->str()));
attrs.emplace(state.ctx.symbols[attr.name], std::string(attr.value.str()));
} else {
state.ctx.errors.make<EvalError>(
"flake reference attribute sets may only contain integers, Booleans, "
"and strings, but attribute '%s' is %s",
state.ctx.symbols[attr.name],
showType(*attr.value)).debugThrow();
state.ctx.errors
.make<EvalError>(
"flake reference attribute sets may only contain integers, Booleans, "
"and strings, but attribute '%s' is %s",
state.ctx.symbols[attr.name],
showType(attr.value)
)
.debugThrow();
}
}
auto flakeRef = FlakeRef::fromAttrs(attrs);
+19 -15
View File
@@ -95,6 +95,24 @@ inline void * gcAllocBytes(size_t n)
return ptr;
}
[[gnu::always_inline]]
inline size_t checkedArrayAllocSize(size_t size, size_t howMany)
{
// NOTE: size_t * size_t, which can definitely overflow.
// Unsigned integer overflow is definitely a bug, but isn't undefined
// behavior, so we can just check if we overflowed after the fact.
// However, people can and do request zero sized allocations, so we need
// to check that neither of our multiplicands were zero before complaining
// about it.
auto checkedSz = checked::Checked<size_t>(howMany) * size;
if (checkedSz.overflowed()) {
// Congrats, you done did an overflow.
throw std::bad_alloc();
}
return checkedSz.valueWrapping();
}
/// Typed, safe wrapper around calloc() (transparently GC-enabled). Allocates
/// enough for the requested count of the specified type. Also checks for
/// nullptr (and throws @ref std::bad_alloc), and casts the void pointer to
@@ -103,21 +121,7 @@ template<typename T>
[[gnu::always_inline]]
inline T * gcAllocType(size_t howMany = 1)
{
// NOTE: size_t * size_t, which can definitely overflow.
// Unsigned integer overflow is definitely a bug, but isn't undefined
// behavior, so we can just check if we overflowed after the fact.
// However, people can and do request zero sized allocations, so we need
// to check that neither of our multiplicands were zero before complaining
// about it.
// NOLINTNEXTLINE(bugprone-sizeof-expression): yeah we only seem to alloc pointers with this. the calculation *is* correct though!
auto checkedSz = checked::Checked<size_t>(howMany) * sizeof(T);
size_t sz = checkedSz.valueWrapping();
if (checkedSz.overflowed()) {
// Congrats, you done did an overflow.
throw std::bad_alloc();
}
return static_cast<T *>(gcAllocBytes(sz));
return static_cast<T *>(gcAllocBytes(checkedArrayAllocSize(sizeof(T), howMany)));
}
/// GC-transparently allocates a buffer for a C-string of @ref size *bytes*,
+2 -2
View File
@@ -17,8 +17,8 @@ using SmallVector = boost::container::small_vector<T, nItems, TraceableAllocator
/**
* A vector of value pointers. See `SmallVector`.
*/
template <size_t nItems>
using SmallValueVector = SmallVector<Value *, nItems>;
template<size_t nItems>
using SmallValueVector = SmallVector<Value, nItems>;
/**
* A vector of values that must not be referenced after the vector is destroyed.
+58 -62
View File
@@ -68,7 +68,9 @@ std::string DrvInfo::queryName(EvalState & state)
if (!i) {
state.ctx.errors.make<TypeError>("derivation name missing").debugThrow();
}
name = state.forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
name = state.forceStringNoCtx(
i->value, noPos, "while evaluating the 'name' attribute of a derivation"
);
}
return name;
}
@@ -81,7 +83,7 @@ std::string DrvInfo::querySystem(EvalState & state)
system = !i
? "unknown"
: state.forceStringNoCtx(
*i->value, i->pos, "while evaluating the 'system' attribute of a derivation"
i->value, i->pos, "while evaluating the 'system' attribute of a derivation"
);
}
return system;
@@ -98,7 +100,7 @@ std::optional<StorePath> DrvInfo::queryDrvPath(EvalState & state)
} else {
drvPath = {state.coerceToStorePath(
i->pos,
*i->value,
i->value,
context,
"while evaluating the 'drvPath' attribute of a derivation"
)};
@@ -123,7 +125,7 @@ StorePath DrvInfo::queryOutPath(EvalState & state)
NixStringContext context;
if (i) {
outPath = state.coerceToStorePath(
i->pos, *i->value, context, "while evaluating the output path of a derivation"
i->pos, i->value, context, "while evaluating the output path of a derivation"
);
}
}
@@ -156,23 +158,17 @@ void DrvInfo::fillOutputs(EvalState & state, bool withPaths)
// NOTE(Qyriad): I don't think there is any codepath that can cause this to error.
state.forceList(
*outputs->value,
outputs->pos,
"while evaluating the 'outputs' attribute of a derivation"
outputs->value, outputs->pos, "while evaluating the 'outputs' attribute of a derivation"
);
for (auto [idx, elem] : enumerate(outputs->value->listItems())) {
for (auto && [idx, elem] : enumerate(outputs->value.listItems())) {
// NOTE(Qyriad): This error should be *extremely* rare in practice.
// It is impossible to construct with `stdenv.mkDerivation`,
// `builtins.derivation`, or even `derivationStrict`. As far as we can tell,
// it is only possible by overriding a derivation attrset already created by
// one of those with `//` to introduce the failing `outputs` entry.
auto errMsg = fmt("while evaluating output %d of a derivation", idx);
std::string_view outputName = state.forceStringNoCtx(
*elem,
outputs->pos,
errMsg
);
std::string_view outputName = state.forceStringNoCtx(elem, outputs->pos, errMsg);
if (withPaths) {
// Find the attr with this output's name...
@@ -184,10 +180,10 @@ void DrvInfo::fillOutputs(EvalState & state, bool withPaths)
// Meanwhile we couldn't figure out any circumstances
// that cause this to error.
state.forceAttrs(*out->value, outputs->pos, errMsg);
state.forceAttrs(out->value, outputs->pos, errMsg);
// ...and evaluate its `outPath` attribute.
const Attr * outPath = out->value->attrs->get(state.ctx.s.outPath);
const Attr * outPath = out->value.attrs()->get(state.ctx.s.outPath);
if (outPath == nullptr) {
continue;
// FIXME: throw error?
@@ -196,12 +192,7 @@ void DrvInfo::fillOutputs(EvalState & state, bool withPaths)
NixStringContext context;
// And idk what could possibly cause this one to error
// that wouldn't error before here.
auto storePath = state.coerceToStorePath(
outPath->pos,
*outPath->value,
context,
errMsg
);
auto storePath = state.coerceToStorePath(outPath->pos, outPath->value, context, errMsg);
this->outputs.emplace(outputName, storePath);
} else {
this->outputs.emplace(outputName, std::nullopt);
@@ -233,7 +224,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(EvalState & state, bool withPaths, bool o
// explicitly selected-into output.
if (const Attr * outSpecAttr = attrs->get(state.ctx.s.outputSpecified)) {
bool outputSpecified = state.forceBool(
*outSpecAttr->value,
outSpecAttr->value,
outSpecAttr->pos,
"while evaluating the 'outputSpecified' attribute of a derivation"
);
@@ -253,15 +244,16 @@ DrvInfo::Outputs DrvInfo::queryOutputs(EvalState & state, bool withPaths, bool o
/* ^ this shows during `nix-env -i` right under the bad derivation */
if (!outTI->isList()) throw Error(errMsg + "expected a list but got %s", Uncolored(showType(outTI->type())));
Outputs result;
for (auto elem : outTI->listItems()) {
if (elem->type() != nString)
for (auto & elem : outTI->listItems()) {
if (elem.type() != nString) {
throw Error(
errMsg + "element is %s where a string was expected",
Uncolored(showType(elem->type()))
Uncolored(showType(elem.type()))
);
auto out = outputs.find(std::string(elem->str()));
}
auto out = outputs.find(std::string(elem.str()));
if (out == outputs.end()) {
throw Error(errMsg + "output '%s' does not exist", elem->str());
throw Error(errMsg + "output '%s' does not exist", elem.str());
}
result.insert(*out);
}
@@ -274,7 +266,7 @@ std::string DrvInfo::queryOutputName(EvalState & state)
if (outputName == "" && attrs) {
auto i = attrs->get(state.ctx.s.outputName);
outputName = i ? state.forceStringNoCtx(
*i->value, noPos, "while evaluating the output name of a derivation"
i->value, noPos, "while evaluating the output name of a derivation"
)
: "";
}
@@ -290,8 +282,8 @@ Bindings * DrvInfo::getMeta(EvalState & state)
if (!a) {
return 0;
}
state.forceAttrs(*a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
meta = a->value->attrs;
state.forceAttrs(a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
meta = a->value.attrs();
return meta;
}
@@ -310,17 +302,23 @@ bool DrvInfo::checkMeta(EvalState & state, Value & v)
{
state.forceValue(v, noPos);
if (v.type() == nList) {
for (auto elem : v.listItems())
if (!checkMeta(state, *elem)) return false;
for (auto & elem : v.listItems()) {
if (!checkMeta(state, elem)) {
return false;
}
}
return true;
}
else if (v.type() == nAttrs) {
auto i = v.attrs->get(state.ctx.s.outPath);
auto i = v.attrs()->get(state.ctx.s.outPath);
if (i) {
return false;
}
for (auto & i : *v.attrs)
if (!checkMeta(state, *i.value)) return false;
for (auto & i : *v.attrs()) {
if (!checkMeta(state, i.value)) {
return false;
}
}
return true;
}
else return v.type() == nInt || v.type() == nBool || v.type() == nString ||
@@ -332,10 +330,10 @@ Value * DrvInfo::queryMeta(EvalState & state, const std::string & name)
{
if (!getMeta(state)) return 0;
auto a = meta->get(state.ctx.symbols.create(name));
if (!a || !checkMeta(state, *a->value)) {
if (!a || !checkMeta(state, a->value)) {
return 0;
}
return a->value;
return &a->value;
}
@@ -351,7 +349,9 @@ NixInt DrvInfo::queryMetaInt(EvalState & state, const std::string & name, NixInt
{
Value * v = queryMeta(state, name);
if (!v) return def;
if (v->type() == nInt) return v->integer;
if (v->type() == nInt) {
return v->integer();
}
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
integer meta fields. */
@@ -366,7 +366,9 @@ bool DrvInfo::queryMetaBool(EvalState & state, const std::string & name, bool de
{
Value * v = queryMeta(state, name);
if (!v) return def;
if (v->type() == nBool) return v->boolean;
if (v->type() == nBool) {
return v->boolean();
}
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
Boolean meta fields. */
@@ -380,8 +382,7 @@ bool DrvInfo::queryMetaBool(EvalState & state, const std::string & name, bool de
return def;
}
void DrvInfo::setMeta(EvalState & state, const std::string & name, Value * v)
void DrvInfo::setMeta(EvalState & state, const std::string & name, Value & v)
{
getMeta(state);
auto attrs = state.ctx.buildBindings(1 + (meta ? meta->size() : 0));
@@ -390,7 +391,7 @@ void DrvInfo::setMeta(EvalState & state, const std::string & name, Value * v)
for (auto i : *meta)
if (i.name != sym)
attrs.insert(i);
if (v) attrs.insert(sym, v);
attrs.insert(sym, v);
meta = attrs.finish();
}
@@ -409,7 +410,7 @@ static bool getDerivation(EvalState & state, Value & v,
state.forceValue(v, noPos);
if (!state.isDerivation(v)) return true;
DrvInfo drv(attrPath, v.attrs);
DrvInfo drv(attrPath, v.attrs());
drv.queryName(state);
@@ -462,19 +463,13 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
if (v.type() == nList) {
// NOTE we can't really deduplicate here because small lists don't have stable addresses
// and can cause spurious duplicate detections due to v being on the stack.
for (auto [n, elem] : enumerate(v.listItems())) {
for (auto && [n, elem] : enumerate(v.listItems())) {
std::string joinedAttrPath = addToPath(pathPrefix, fmt("%d", n));
bool shouldRecurse = getDerivation(state, *elem, joinedAttrPath, drvs, ignoreAssertionFailures);
bool shouldRecurse =
getDerivation(state, elem, joinedAttrPath, drvs, ignoreAssertionFailures);
if (shouldRecurse) {
getDerivations(
state,
*elem,
pos,
joinedAttrPath,
autoArgs,
drvs,
done,
ignoreAssertionFailures
state, elem, pos, joinedAttrPath, autoArgs, drvs, done, ignoreAssertionFailures
);
}
}
@@ -489,7 +484,7 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
/* Dont consider sets we've already seen, e.g. y in
`rec { x.d = derivation {...}; y = x; }`. */
auto const &[_, didInsert] = done.insert(v.attrs);
auto const &[_, didInsert] = done.insert(v.attrs());
if (!didInsert) {
return;
}
@@ -497,14 +492,14 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
// FIXME: what the fuck???
/* !!! undocumented hackery to support combining channels in
nix-env.cc. */
bool combineChannels = v.attrs->get(state.ctx.symbols.create("_combineChannels"));
bool combineChannels = v.attrs()->get(state.ctx.symbols.create("_combineChannels"));
/* Consider the attributes in sorted order to get more
deterministic behaviour in nix-env operations (e.g. when
there are names clashes between derivations, the derivation
bound to the attribute with the "lower" name should take
precedence). */
for (auto & attr : v.attrs->lexicographicOrder(state.ctx.symbols)) {
for (auto & attr : v.attrs()->lexicographicOrder(state.ctx.symbols)) {
debug("evaluating attribute '%1%'", state.ctx.symbols[attr->name]);
// FIXME: only consider attrs with identifier-like names?? Why???
if (!std::regex_match(std::string(state.ctx.symbols[attr->name]), attrRegex)) {
@@ -514,7 +509,7 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
if (combineChannels) {
getDerivations(
state,
*attr->value,
attr->value,
attr->pos,
joinedAttrPath,
autoArgs,
@@ -522,18 +517,19 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
done,
ignoreAssertionFailures
);
} else if (getDerivation(state, *attr->value, joinedAttrPath, drvs, ignoreAssertionFailures)) {
} else if (getDerivation(state, attr->value, joinedAttrPath, drvs, ignoreAssertionFailures))
{
/* If the value of this attribute is itself a set,
should we recurse into it? => Only if it has a
`recurseForDerivations = true' attribute. */
if (attr->value->type() == nAttrs) {
if (attr->value.type() == nAttrs) {
const Attr * recurseForDrvs =
attr->value->attrs->get(state.ctx.s.recurseForDerivations);
attr->value.attrs()->get(state.ctx.s.recurseForDerivations);
if (recurseForDrvs == nullptr) {
continue;
}
bool shouldRecurse = state.forceBool(
*recurseForDrvs->value,
recurseForDrvs->value,
attr->pos,
fmt("while evaluating the '%s' attribute", Magenta("recurseForDerivations"))
);
@@ -543,7 +539,7 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
getDerivations(
state,
*attr->value,
attr->value,
attr->pos,
joinedAttrPath,
autoArgs,
+1 -1
View File
@@ -73,7 +73,7 @@ public:
std::string queryMetaString(EvalState & state, const std::string & name);
NixInt queryMetaInt(EvalState & state, const std::string & name, NixInt def);
bool queryMetaBool(EvalState & state, const std::string & name, bool def);
void setMeta(EvalState & state, const std::string & name, Value * v);
void setMeta(EvalState & state, const std::string & name, Value & v);
/*
MetaInfo queryMetaInfo(EvalState & state) const;
+36 -27
View File
@@ -1,4 +1,5 @@
#include "lix/libexpr/json-to-value.hh"
#include "gc-alloc.hh"
#include "lix/libexpr/value.hh"
#include "lix/libexpr/eval.hh"
#include "lix/libutil/json.hh"
@@ -7,11 +8,6 @@
namespace nix {
/*
* Used for `JSONObjectState`
*/
using ValueMap = GcMap<Symbol, Value *>;
// for more information, refer to
// https://github.com/nlohmann/json/blob/master/include/nlohmann/detail/input/json_sax.hpp
class JSONSax : nlohmann::json_sax<JSON> {
@@ -25,13 +21,14 @@ class JSONSax : nlohmann::json_sax<JSON> {
assert(false && "tried to close toplevel json parser state");
}
explicit JSONState(std::unique_ptr<JSONState> && p) : parent(std::move(p)) {}
explicit JSONState(Value * v) : v(allocRootValue(v)) {}
JSONState() = default;
JSONState(JSONState & p) = delete;
Value & value(EvalState & state)
Value & value()
{
if (!v)
v = allocRootValue(state.ctx.mem.allocValue());
return **v;
if (!v) {
v = allocRootValue({});
}
return *v;
}
virtual ~JSONState() {}
virtual void add() {}
@@ -39,35 +36,41 @@ class JSONSax : nlohmann::json_sax<JSON> {
class JSONObjectState : public JSONState {
using JSONState::JSONState;
ValueMap attrs;
GcMap<Symbol, Value> attrs;
Symbol _key;
std::unique_ptr<JSONState> resolve(EvalState & state) override
{
auto attrs2 = state.ctx.buildBindings(attrs.size());
for (auto & i : attrs)
attrs2.insert(i.first, i.second);
parent->value(state).mkAttrs(attrs2.alreadySorted());
parent->value().mkAttrs(attrs2.alreadySorted());
return std::move(parent);
}
void add() override { v = nullptr; }
void add() override
{
attrs.insert_or_assign(_key, value());
v = nullptr;
}
public:
void key(string_t & name, EvalState & state)
{
attrs.insert_or_assign(state.ctx.symbols.create(name), &value(state));
_key = state.ctx.symbols.create(name);
}
};
class JSONListState : public JSONState {
ValueVector values;
GcVector<Value> values;
std::unique_ptr<JSONState> resolve(EvalState & state) override
{
Value & v = parent->value(state);
v = state.ctx.mem.newList(values.size());
auto list = state.ctx.mem.newList(values.size());
parent->value() = {NewValueAs::list, list};
for (size_t n = 0; n < values.size(); ++n) {
v.listElems()[n] = values[n];
list->elems[n] = values[n];
}
return std::move(parent);
}
void add() override {
void add() override
{
values.push_back(*v);
v = nullptr;
}
@@ -82,25 +85,30 @@ class JSONSax : nlohmann::json_sax<JSON> {
std::unique_ptr<JSONState> rs;
public:
JSONSax(EvalState & state, Value & v) : state(state), rs(new JSONState(&v)) {};
JSONSax(EvalState & state) : state(state), rs(new JSONState()) {};
Value result()
{
return rs->value();
}
bool null() override
{
rs->value(state).mkNull();
rs->value().mkNull();
rs->add();
return true;
}
bool boolean(bool val) override
{
rs->value(state).mkBool(val);
rs->value().mkBool(val);
rs->add();
return true;
}
bool number_integer(number_integer_t val) override
{
rs->value(state).mkInt(val);
rs->value().mkInt(val);
rs->add();
return true;
}
@@ -113,21 +121,21 @@ public:
return number_float(static_cast<number_float_t>(val_), "");
}
NixInt::Inner val = val_;
rs->value(state).mkInt(val);
rs->value().mkInt(val);
rs->add();
return true;
}
bool number_float(number_float_t val, const string_t & s) override
{
rs->value(state).mkFloat(val);
rs->value().mkFloat(val);
rs->add();
return true;
}
bool string(string_t & val) override
{
rs->value(state).mkString(val);
rs->value().mkString(val);
rs->add();
return true;
}
@@ -178,10 +186,11 @@ public:
void parseJSON(EvalState & state, const std::string_view & s_, Value & v)
{
JSONSax parser(state, v);
JSONSax parser(state);
bool res = JSON::sax_parse(s_, &parser);
if (!res)
throw JSONParseError("Invalid JSON Value");
v = parser.result();
}
}
+79 -3
View File
@@ -9,7 +9,8 @@
namespace nix {
ExprBlackHole eBlackHole;
Expr *eBlackHoleAddr = &eBlackHole;
Value::Thunk Value::blackHole{{0}, &eBlackHole};
// FIXME: remove, because *symbols* are abstract and do not have a single
// textual representation; see printIdentifier()
@@ -47,11 +48,11 @@ JSON ExprLiteral::toJSON(const SymbolTable & symbols) const
switch (v.type()) {
case nInt:
valueType = "Int";
value = v.integer.value;
value = v.integer().value;
break;
case nFloat:
valueType = "Float";
value = v.fpoint;
value = v.fpoint();
break;
case nString:
valueType = "String";
@@ -320,6 +321,74 @@ JSON printAttrPathToJson(const SymbolTable & symbols, const AttrPath & attrPath)
/* Computing levels/displacements for variables. */
namespace {
// This is a one-pass static analyzer for
// various topics.
struct StaticAnalyzer : ExprVisitor
{
std::set<Symbol> staticallyUsedVariables;
bool usedDynamicVariables = false;
StaticAnalyzer() {}
using ExprVisitor::visit;
void visit(ExprDebugFrame & e, std::unique_ptr<Expr> & ptr) override
{
visit(e.inner);
}
void visit(ExprLiteral & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprVar & e, std::unique_ptr<Expr> & ptr) override
{
staticallyUsedVariables.insert(e.name);
}
void visit(ExprInheritFrom & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprSelect & e, std::unique_ptr<Expr> & ptr) override
{
if (e.isDynamic()) {
usedDynamicVariables = true;
}
visit(e.def);
visit(e.e);
}
void visit(ExprOpHasAttr & e, std::unique_ptr<Expr> & ptr) override
{
if (e.isDynamic()) {
usedDynamicVariables = true;
}
visit(e.e);
}
void visit(ExprSet & e, std::unique_ptr<Expr> & ptr) override
{
// TODO: oh bro, we need to analyze dynamic attributes for their value expressions.
}
void visit(ExprList & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprLambda & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprCall & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprLet & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprWith & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprIf & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprAssert & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprOpNot & e, std::unique_ptr<Expr> & ptr) override {}
#define BINOP(type) \
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
void visit(type & e, std::unique_ptr<Expr> & ptr) override \
{ \
visit(e.e1); \
visit(e.e2); \
}
BINOP(ExprOpEq)
BINOP(ExprOpNEq)
BINOP(ExprOpAnd)
BINOP(ExprOpOr)
BINOP(ExprOpImpl)
BINOP(ExprOpUpdate)
BINOP(ExprOpConcatLists)
#undef BINOP
void visit(ExprConcatStrings & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprPos & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprBlackHole & e, std::unique_ptr<Expr> & ptr) override {}
};
struct VarBinder : ExprVisitor
{
Evaluator & es;
@@ -589,6 +658,13 @@ void VarBinder::visit(ExprLambda & e, std::unique_ptr<Expr> & ptr)
{
withEnv(e.pattern->buildEnv(env.get()), [&] {
e.pattern->accept(*this);
/* TODO: If statically, e.body makes only use of some parameters and not the whole scope.
* We shouldn't have to keep around all the environment data which might contain trapped
* pointers. Analyze `e.body` and return its statically known set of used variables.
* */
DirectCallAnalyzer analyzer{es, env};
analyzer.visit(e.body);
e.shortcut = analyzer.shortcut;
visit(e.body);
});
}
+69 -13
View File
@@ -127,7 +127,7 @@ public:
virtual JSON toJSON(const SymbolTable & symbols) const;
virtual void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) = 0;
virtual void eval(EvalState & state, Env & env, Value & v);
virtual Value * maybeThunk(EvalState & state, Env & env);
virtual Value maybeThunk(EvalState & state, Env & env);
virtual void setName(Symbol name);
PosIdx getPos() const { return pos; }
@@ -175,26 +175,51 @@ protected:
Value v;
ExprLiteral(const PosIdx pos) : Expr(pos) {};
public:
ExprLiteral(const PosIdx pos, NewValueAs::integer_t, NixInt n) : Expr(pos) { v.mkInt(n); };
ExprLiteral(const PosIdx pos, NewValueAs::integer_t, NixInt::Inner n) : Expr(pos) { v.mkInt(n); };
ExprLiteral(const PosIdx pos, NewValueAs::floating_t, NixFloat nf) : Expr(pos) { v.mkFloat(nf); };
Value * maybeThunk(EvalState & state, Env & env) override;
Value maybeThunk(EvalState & state, Env & env) override;
JSON toJSON(const SymbolTable & symbols) const override;
void eval(EvalState & state, Env & env, Value & v) override;
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
};
struct ExprInt : ExprLiteral
{
Value::Int i;
ExprInt(const PosIdx pos, NixInt n) : ExprLiteral(pos), i{{Value::Acb::tInt}, n}
{
v = Value::isTaggableInteger(n) ? Value{NewValueAs::integer, n} : Value(i);
}
ExprInt(const PosIdx pos, NixInt::Inner n) : ExprInt(pos, NixInt(n)) {}
};
struct ExprFloat : ExprLiteral
{
Value::Float f;
ExprFloat(const PosIdx pos, NewValueAs::floating_t, double f)
: ExprLiteral(pos)
, f{{Value::Acb::tFloat}, f}
{
v = Value(this->f);
}
};
struct ExprString : ExprLiteral
{
std::string s;
ExprString(const PosIdx pos, std::string &&s) : ExprLiteral(pos), s(std::move(s)) { v.mkString(this->s.data()); };
Value::String strcb{.content = s.c_str(), .context = nullptr};
ExprString(const PosIdx pos, std::string && s) : ExprLiteral(pos), s(std::move(s))
{
v = {NewValueAs::string, &strcb};
}
};
struct ExprPath : ExprLiteral
{
std::string s;
ExprPath(const PosIdx pos, std::string s) : ExprLiteral(pos), s(std::move(s)) { v.mkPath(this->s.c_str()); };
Value::String strcb{.content = s.c_str(), .context = Value::String::path};
ExprPath(const PosIdx pos, std::string s) : ExprLiteral(pos), s(std::move(s))
{
v = {NewValueAs::path, &strcb};
}
};
typedef uint32_t Level;
@@ -228,7 +253,7 @@ struct ExprVar : Expr
ExprVar(Symbol name) : name(name), needsRoot(false) { };
ExprVar(const PosIdx & pos, Symbol name, bool needsRoot = false) : Expr(pos), name(name), needsRoot(needsRoot) { };
Value * maybeThunk(EvalState & state, Env & env) override;
Value maybeThunk(EvalState & state, Env & env) override;
JSON toJSON(const SymbolTable & symbols) const override;
void eval(EvalState & state, Env & env, Value & v) override;
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
@@ -267,6 +292,17 @@ struct ExprSelect : Expr
/** The path of attributes being selected. e.g. `bar.baz` in `foo.bar.baz.` */
AttrPath attrPath;
bool isDynamic() const
{
for (auto & name : attrPath) {
if (name.expr) {
return true;
}
}
return false;
}
ExprSelect(const PosIdx & pos, std::unique_ptr<Expr> e, AttrPath attrPath, std::unique_ptr<Expr> def) : Expr(pos), e(std::move(e)), def(std::move(def)), attrPath(std::move(attrPath)) { };
ExprSelect(const PosIdx & pos, std::unique_ptr<Expr> e, const PosIdx namePos, Symbol name) : Expr(pos), e(std::move(e)) { attrPath.push_back(AttrName(namePos, name)); };
JSON toJSON(const SymbolTable & symbols) const override;
@@ -278,6 +314,16 @@ struct ExprOpHasAttr : Expr
{
std::unique_ptr<Expr> e;
AttrPath attrPath;
bool isDynamic() const
{
for (auto & name : attrPath) {
if (name.expr) {
return true;
}
}
return false;
}
ExprOpHasAttr(const PosIdx & pos, std::unique_ptr<Expr> e, AttrPath attrPath) : Expr(pos), e(std::move(e)), attrPath(std::move(attrPath)) { };
JSON toJSON(const SymbolTable & symbols) const override;
void eval(EvalState & state, Env & env, Value & v) override;
@@ -370,7 +416,7 @@ struct ExprList : Expr
JSON toJSON(const SymbolTable & symbols) const override;
void eval(EvalState & state, Env & env, Value & v) override;
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
Value * maybeThunk(EvalState & state, Env & env) override;
Value maybeThunk(EvalState & state, Env & env) override;
};
struct Pattern {
@@ -384,7 +430,8 @@ struct Pattern {
virtual std::shared_ptr<const StaticEnv> buildEnv(const StaticEnv * up) = 0;
virtual void accept(ExprVisitor & ev) = 0;
virtual Env & match(ExprLambda & lambda, EvalState & state, Env & up, Value * arg, const PosIdx pos) = 0;
virtual Env &
match(ExprLambda & lambda, EvalState & state, Env & up, Value & arg, const PosIdx pos) = 0;
virtual void addBindingsToJSON(JSON & out, const SymbolTable & symbols) const = 0;
};
@@ -399,7 +446,8 @@ struct SimplePattern : Pattern
virtual std::shared_ptr<const StaticEnv> buildEnv(const StaticEnv * up) override;
virtual void accept(ExprVisitor & ev) override;
virtual Env & match(ExprLambda & lambda, EvalState & state, Env & up, Value * arg, const PosIdx pos) override;
virtual Env &
match(ExprLambda & lambda, EvalState & state, Env & up, Value & arg, const PosIdx pos) override;
virtual void addBindingsToJSON(JSON & out, const SymbolTable & symbols) const override;
};
@@ -420,7 +468,8 @@ struct AttrsPattern : Pattern
virtual std::shared_ptr<const StaticEnv> buildEnv(const StaticEnv * up) override;
virtual void accept(ExprVisitor & ev) override;
virtual Env & match(ExprLambda & lambda, EvalState & state, Env & up, Value * arg, const PosIdx pos) override;
virtual Env &
match(ExprLambda & lambda, EvalState & state, Env & up, Value & arg, const PosIdx pos) override;
virtual void addBindingsToJSON(JSON & out, const SymbolTable & symbols) const override;
@@ -451,6 +500,13 @@ struct ExprLambda : Expr
Symbol name;
std::unique_ptr<Pattern> pattern;
std::unique_ptr<Expr> body;
// This is a shortcut variant which
// exhausts the body further lambda constructions
// to transform x1: x2: …: xn: b
// into { x1, …, xn }: b
// This can be used when you know that you are
// passing all the arguments at once.
std::unique_ptr<ExprLambda> shortcut;
ExprLambda(PosIdx pos, std::unique_ptr<Pattern> pattern, std::unique_ptr<Expr> body)
: Expr(pos), pattern(std::move(pattern)), body(std::move(body))
{
+3 -3
View File
@@ -148,7 +148,7 @@ struct ExprState
std::unique_ptr<Expr> negate(PosIdx pos, State & state)
{
std::vector<std::unique_ptr<Expr>> args(2);
args[0] = std::make_unique<ExprLiteral>(pos, NewValueAs::integer, 0);
args[0] = std::make_unique<ExprInt>(pos, 0);
args[1] = popExprOnly();
return std::make_unique<ExprCall>(pos, state.mkInternalVar(pos, state.s.sub), std::move(args));
}
@@ -507,7 +507,7 @@ template<> struct BuildAST<grammar::v1::expr::int_> {
.pos = ps.positions[ps.at(in)],
});
}
s.emplaceExpr<ExprLiteral>(ps.at(in), NewValueAs::integer, v);
s.emplaceExpr<ExprInt>(ps.at(in), v);
}
};
@@ -542,7 +542,7 @@ template<> struct BuildAST<grammar::v1::expr::float_> {
});
}
}();
s.emplaceExpr<ExprLiteral>(ps.at(in), NewValueAs::floating, v);
s.emplaceExpr<ExprFloat>(ps.at(in), NewValueAs::floating, v);
}
};
+500 -315
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -33,7 +33,7 @@ struct RegisterPrimOp
* will get called during EvalState initialization, so there
* may be primops not yet added and builtins is not yet sorted.
*/
RegisterPrimOp(PrimOp && primOp);
RegisterPrimOp(PrimOpDetails && primOp);
};
/* These primops are disabled without enableNativeCode, but plugins
+15 -13
View File
@@ -3,6 +3,7 @@
#include "lix/libstore/derivations.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libutil/types.hh"
#include "value.hh"
namespace nix {
@@ -147,9 +148,10 @@ void prim_getContext(EvalState & state, Value * * args, Value & v)
infoAttrs.alloc(sAllOutputs).mkBool(true);
if (!info.second.outputs.empty()) {
auto & outputsVal = infoAttrs.alloc(state.ctx.s.outputs);
outputsVal = state.ctx.mem.newList(info.second.outputs.size());
auto content = state.ctx.mem.newList(info.second.outputs.size());
outputsVal = {NewValueAs::list, content};
for (const auto & [i, output] : enumerate(info.second.outputs))
(outputsVal.listElems()[i] = state.ctx.mem.allocValue())->mkString(output);
content->elems[i].mkString(output);
}
attrs.alloc(state.ctx.store->printStorePath(info.first)).mkAttrs(infoAttrs);
}
@@ -171,7 +173,7 @@ static void prim_appendContext(EvalState & state, Value * * args, Value & v)
state.forceAttrs(*args[1], noPos, "while evaluating the second argument passed to builtins.appendContext");
auto sAllOutputs = state.ctx.symbols.create("allOutputs");
for (auto & i : *args[1]->attrs) {
for (auto & i : *args[1]->attrs()) {
const auto & name = state.ctx.symbols[i.name];
if (!state.ctx.store->isStorePath(name))
state.ctx.errors.make<EvalError>(
@@ -181,11 +183,11 @@ static void prim_appendContext(EvalState & state, Value * * args, Value & v)
auto namePath = state.ctx.store->parseStorePath(name);
if (!settings.readOnlyMode)
state.aio.blockOn(state.ctx.store->ensurePath(namePath));
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
auto a = i.value->attrs->get(state.ctx.s.path);
state.forceAttrs(i.value, i.pos, "while evaluating the value of a string context");
auto a = i.value.attrs()->get(state.ctx.s.path);
if (a) {
if (state.forceBool(
*a->value, a->pos, "while evaluating the `path` attribute of a string context"
a->value, a->pos, "while evaluating the `path` attribute of a string context"
))
{
context.emplace(NixStringContextElem::Opaque{
@@ -194,10 +196,10 @@ static void prim_appendContext(EvalState & state, Value * * args, Value & v)
}
}
a = i.value->attrs->get(sAllOutputs);
a = i.value.attrs()->get(sAllOutputs);
if (a) {
if (state.forceBool(
*a->value,
a->value,
a->pos,
"while evaluating the `allOutputs` attribute of a string context"
))
@@ -214,20 +216,20 @@ static void prim_appendContext(EvalState & state, Value * * args, Value & v)
}
}
a = i.value->attrs->get(state.ctx.s.outputs);
a = i.value.attrs()->get(state.ctx.s.outputs);
if (a) {
state.forceList(
*a->value, a->pos, "while evaluating the `outputs` attribute of a string context"
a->value, a->pos, "while evaluating the `outputs` attribute of a string context"
);
if (a->value->listSize() && !isDerivation(name)) {
if (a->value.listSize() && !isDerivation(name)) {
state.ctx.errors.make<EvalError>(
"tried to add derivation output context of %s, which is not a derivation, to a string",
name
).atPos(i.pos).debugThrow();
}
for (auto elem : a->value->listItems()) {
for (auto & elem : a->value.listItems()) {
auto outputName = state.forceStringNoCtx(
*elem, a->pos, "while evaluating an output name within a string context"
elem, a->pos, "while evaluating an output name within a string context"
);
context.emplace(NixStringContextElem::Built {
.drvPath = makeConstantStorePath(namePath),
+7 -8
View File
@@ -118,7 +118,7 @@ void prim_fetchClosure(EvalState & state, Value * * args, Value & v)
std::optional<StorePathOrGap> toPath;
std::optional<bool> inputAddressedMaybe;
for (auto & attr : *args[0]->attrs) {
for (auto & attr : *args[0]->attrs()) {
const auto & attrName = state.ctx.symbols[attr.name];
auto attrHint = [&]() -> std::string {
return "while evaluating the '" + attrName + "' attribute passed to builtins.fetchClosure";
@@ -126,27 +126,26 @@ void prim_fetchClosure(EvalState & state, Value * * args, Value & v)
if (attrName == "fromPath") {
NixStringContext context;
fromPath = state.coerceToStorePath(attr.pos, *attr.value, context, attrHint());
fromPath = state.coerceToStorePath(attr.pos, attr.value, context, attrHint());
}
else if (attrName == "toPath") {
state.forceValue(*attr.value, attr.pos);
bool isEmptyString = attr.value->type() == nString && attr.value->str().empty();
state.forceValue(attr.value, attr.pos);
bool isEmptyString = attr.value.type() == nString && attr.value.str().empty();
if (isEmptyString) {
toPath = StorePathOrGap {};
}
else {
NixStringContext context;
toPath = state.coerceToStorePath(attr.pos, *attr.value, context, attrHint());
toPath = state.coerceToStorePath(attr.pos, attr.value, context, attrHint());
}
}
else if (attrName == "fromStore")
fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos,
attrHint());
fromStoreUrl = state.forceStringNoCtx(attr.value, attr.pos, attrHint());
else if (attrName == "inputAddressed")
inputAddressedMaybe = state.forceBool(*attr.value, attr.pos, attrHint());
inputAddressedMaybe = state.forceBool(attr.value, attr.pos, attrHint());
else
throw Error({
+33 -10
View File
@@ -17,25 +17,48 @@ static void prim_fetchMercurial(EvalState & state, Value * * args, Value & v)
if (args[0]->type() == nAttrs) {
for (auto & attr : *args[0]->attrs) {
for (auto & attr : *args[0]->attrs()) {
std::string_view n(state.ctx.symbols[attr.name]);
if (n == "url")
url = state.coerceToString(attr.pos, *attr.value, context,
"while evaluating the `url` attribute passed to builtins.fetchMercurial",
StringCoercionMode::Strict, false).toOwned();
url = state
.coerceToString(
attr.pos,
attr.value,
context,
"while evaluating the `url` attribute passed to "
"builtins.fetchMercurial",
StringCoercionMode::Strict,
false
)
.toOwned();
else if (n == "rev") {
// Ugly: unlike fetchGit, here the "rev" attribute can
// be both a revision or a branch/tag name.
auto value = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `rev` attribute passed to builtins.fetchMercurial");
if (std::regex_match(value.begin(), value.end(), revRegex))
auto value = state.forceStringNoCtx(
attr.value,
attr.pos,
"while evaluating the `rev` attribute passed to builtins.fetchMercurial"
);
if (std::regex_match(value.begin(), value.end(), revRegex)) {
rev = Hash::parseAny(value, HashType::SHA1);
else
} else
ref = value;
}
else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
else
state.ctx.errors.make<EvalError>("unsupported argument '%s' to 'fetchMercurial'", state.ctx.symbols[attr.name]).atPos(attr.pos).debugThrow();
name = state.forceStringNoCtx(
attr.value,
attr.pos,
"while evaluating the `name` attribute passed to builtins.fetchMercurial"
);
else {
state.ctx.errors
.make<EvalError>(
"unsupported argument '%s' to 'fetchMercurial'",
state.ctx.symbols[attr.name]
)
.atPos(attr.pos)
.debugThrow();
}
}
if (url.empty())
+57 -26
View File
@@ -122,35 +122,44 @@ static void fetchTree(
fetchers::Attrs attrs;
if (auto aType = args[0]->attrs->get(state.ctx.s.type)) {
if (auto aType = args[0]->attrs()->get(state.ctx.s.type)) {
if (type)
state.ctx.errors.make<EvalError>(
"unexpected attribute 'type'"
).atPos(pos).debugThrow();
type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
} else if (!type)
state.ctx.errors.make<EvalError>(
"attribute 'type' is missing in call to 'fetchTree'"
).atPos(pos).debugThrow();
type = state.forceStringNoCtx(
aType->value,
aType->pos,
"while evaluating the `type` attribute passed to builtins.fetchTree"
);
} else if (!type) {
state.ctx.errors.make<EvalError>("attribute 'type' is missing in call to 'fetchTree'")
.atPos(pos)
.debugThrow();
}
attrs.emplace("type", type.value());
for (auto & attr : *args[0]->attrs) {
for (auto & attr : *args[0]->attrs()) {
if (attr.name == state.ctx.s.type) continue;
state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) {
auto s = state.coerceToString(attr.pos, *attr.value, context, "", StringCoercionMode::Strict, false).toOwned();
state.forceValue(attr.value, attr.pos);
if (attr.value.type() == nPath || attr.value.type() == nString) {
auto s =
state
.coerceToString(
attr.pos, attr.value, context, "", StringCoercionMode::Strict, false
)
.toOwned();
attrs.emplace(state.ctx.symbols[attr.name],
state.ctx.symbols[attr.name] == "url"
? type == "git"
? fixURIForGit(s, state)
: fixURI(s, state)
: s);
}
else if (attr.value->type() == nBool)
attrs.emplace(state.ctx.symbols[attr.name], Explicit<bool>{attr.value->boolean});
else if (attr.value->type() == nInt) {
auto intValue = attr.value->integer.value;
} else if (attr.value.type() == nBool) {
attrs.emplace(state.ctx.symbols[attr.name], Explicit<bool>{attr.value.boolean()});
} else if (attr.value.type() == nInt) {
auto intValue = attr.value.integer().value;
if (intValue < 0) {
state.ctx.errors.make<EvalError>("negative value given for fetchTree attr %1%: %2%", state.ctx.symbols[attr.name], intValue).atPos(pos).debugThrow();
@@ -158,9 +167,16 @@ static void fetchTree(
unsigned long asUnsigned = intValue;
attrs.emplace(state.ctx.symbols[attr.name], asUnsigned);
} else
state.ctx.errors.make<TypeError>("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
state.ctx.symbols[attr.name], showType(*attr.value)).debugThrow();
} else {
state.ctx.errors
.make<TypeError>(
"fetchTree argument '%s' is %s while a string, Boolean or integer is "
"expected",
state.ctx.symbols[attr.name],
showType(attr.value)
)
.debugThrow();
}
}
if (!params.allowNameArgument)
@@ -221,17 +237,32 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
if (args[0]->type() == nAttrs) {
for (auto & attr : *args[0]->attrs) {
for (auto & attr : *args[0]->attrs()) {
std::string_view n(state.ctx.symbols[attr.name]);
if (n == "url")
url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch");
else if (n == "sha256")
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the sha256 of the content we should fetch"), HashType::SHA256);
else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
else
url = state.forceStringNoCtx(
attr.value, attr.pos, "while evaluating the url we should fetch"
);
else if (n == "sha256") {
expectedHash = newHashAllowEmpty(
state.forceStringNoCtx(
attr.value,
attr.pos,
"while evaluating the sha256 of the content we should fetch"
),
HashType::SHA256
);
} else if (n == "name")
name = state.forceStringNoCtx(
attr.value,
attr.pos,
"while evaluating the name of the content we should fetch"
);
else {
state.ctx.errors.make<EvalError>("unsupported argument '%s' to '%s'", n, who)
.atPos(pos).debugThrow();
.atPos(pos)
.debugThrow();
}
}
if (!url)
+4 -2
View File
@@ -1,5 +1,6 @@
#include "lix/libexpr/eval.hh"
#include "lix/libexpr/extra-primops.hh"
#include "value.hh"
#include <sstream>
#include <toml.hpp>
@@ -30,9 +31,10 @@ void prim_fromTOML(EvalState & state, Value ** args, Value & val)
auto array = toml::get<std::vector<toml::value>>(t);
size_t size = array.size();
v = state.ctx.mem.newList(size);
auto list = state.ctx.mem.newList(size);
v = {NewValueAs::list, list};
for (size_t i = 0; i < size; ++i) {
self(*(v.listElems()[i] = state.ctx.mem.allocValue()), array[i]);
self(list->elems[i], array[i]);
}
} break;
case toml::value_t::boolean:
+19 -17
View File
@@ -9,11 +9,12 @@ 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)
const Value & v,
const SymbolTable & symbols,
std::ostream & str,
std::set<const void *> * seen,
int depth
)
{
checkInterrupt();
@@ -21,12 +22,16 @@ void printAmbiguous(
str << "«too deep»";
return;
}
if (v.isInvalid()) {
str << "<INVALID>";
return;
}
switch (v.type()) {
case nInt:
str << v.integer;
str << v.integer();
break;
case nBool:
printLiteralBool(str, v.boolean);
printLiteralBool(str, v.boolean());
break;
case nString:
escapeString(str, v.str());
@@ -38,13 +43,13 @@ void printAmbiguous(
str << "null";
break;
case nAttrs: {
if (seen && !v.attrs->empty() && !seen->insert(v.attrs).second)
if (seen && !v.attrs()->empty() && !seen->insert(v.attrs()).second)
str << "«repeated»";
else {
str << "{ ";
for (auto & i : v.attrs->lexicographicOrder(symbols)) {
for (auto & i : v.attrs()->lexicographicOrder(symbols)) {
str << symbols[i->name] << " = ";
printAmbiguous(*i->value, symbols, str, seen, depth - 1);
printAmbiguous(i->value, symbols, str, seen, depth - 1);
str << "; ";
}
str << "}";
@@ -56,11 +61,8 @@ void printAmbiguous(
str << "«repeated»";
else {
str << "[ ";
for (auto v2 : v.listItems()) {
if (v2)
printAmbiguous(*v2, symbols, str, seen, depth - 1);
else
str << "(nullptr)";
for (auto & v2 : v.listItems()) {
printAmbiguous(v2, symbols, str, seen, depth - 1);
str << " ";
}
str << "]";
@@ -89,10 +91,10 @@ void printAmbiguous(
}
break;
case nExternal:
str << *v.external;
str << *v.external();
break;
case nFloat:
str << v.fpoint;
str << v.fpoint();
break;
default:
printError("Lix evaluator internal error: printAmbiguous: invalid value type");
+6 -6
View File
@@ -17,10 +17,10 @@ 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);
const Value & v,
const SymbolTable & symbols,
std::ostream & str,
std::set<const void *> * seen,
int depth
);
}
+39 -32
View File
@@ -177,7 +177,7 @@ private:
{
if (options.ansiColors)
output << ANSI_CYAN;
output << v.integer;
output << v.integer();
if (options.ansiColors)
output << ANSI_NORMAL;
}
@@ -186,7 +186,7 @@ private:
{
if (options.ansiColors)
output << ANSI_CYAN;
output << v.fpoint;
output << v.fpoint();
if (options.ansiColors)
output << ANSI_NORMAL;
}
@@ -195,7 +195,7 @@ private:
{
if (options.ansiColors)
output << ANSI_CYAN;
printLiteralBool(output, v.boolean);
printLiteralBool(output, v.boolean());
if (options.ansiColors)
output << ANSI_NORMAL;
}
@@ -232,12 +232,12 @@ private:
void printDerivation(Value & v)
{
auto i = v.attrs->get(state.ctx.s.drvPath);
auto i = v.attrs()->get(state.ctx.s.drvPath);
NixStringContext context;
std::string storePath;
if (i) {
storePath = state.ctx.store->printStorePath(state.coerceToStorePath(
i->pos, *i->value, context, "while evaluating the drvPath of a derivation"
i->pos, i->value, context, "while evaluating the drvPath of a derivation"
));
}
@@ -264,18 +264,18 @@ private:
}
auto item = v[0].second;
if (!item->value) {
if (item->value.isInvalid()) {
return true;
}
if (options.force) {
// The item is going to be forced during printing anyway, but we need its type now.
state.forceValue(*item->value, noPos);
state.forceValue(item->value, noPos);
}
// Pretty-print single-item attrsets only if they contain nested
// structures.
auto itemType = item->value->type();
auto itemType = item->value.type();
return itemType == nList || itemType == nAttrs;
}
@@ -283,14 +283,14 @@ private:
{
if (options.force && options.derivationPaths && state.isDerivation(v)) {
printDerivation(v);
} else if (seen && !v.attrs->empty() && !seen->insert(v.attrs).second) {
} else if (seen && !v.attrs()->empty() && !seen->insert(v.attrs()).second) {
printRepeated();
} else if (depth < options.maxDepth || v.attrs->empty()) {
} else if (depth < options.maxDepth || v.attrs()->empty()) {
increaseIndent();
output << "{";
AttrVec sorted;
for (auto & i : *v.attrs)
for (auto & i : *v.attrs())
sorted.emplace_back(state.ctx.symbols[i.name], &i);
if (options.maxAttrs == std::numeric_limits<size_t>::max())
@@ -324,7 +324,7 @@ private:
}
output << " = ";
print(*i.second->value, depth + 1);
print(i.second->value, depth + 1);
output << ";";
attrsPrinted++;
printedHere++;
@@ -338,7 +338,7 @@ private:
}
}
bool shouldPrettyPrintList(std::span<Value * const> list)
bool shouldPrettyPrintList(std::span<Value> list)
{
if (!options.shouldPrettyPrint() || list.empty()) {
return false;
@@ -349,19 +349,19 @@ private:
return true;
}
auto item = list[0];
if (!item) {
auto & item = list[0];
if (item.isInvalid()) {
return true;
}
if (options.force) {
// The item is going to be forced during printing anyway, but we need its type now.
state.forceValue(*item, noPos);
state.forceValue(item, noPos);
}
// Pretty-print single-item lists only if they contain nested
// structures.
auto itemType = item->type();
auto itemType = item.type();
return itemType == nList || itemType == nAttrs;
}
@@ -378,7 +378,7 @@ private:
auto listItems = v.listItems();
auto prettyPrint = shouldPrettyPrintList(listItems);
size_t printedHere = 0;
for (auto elem : listItems) {
for (auto & elem : listItems) {
printSpace(prettyPrint);
if (listItemsPrinted >= options.maxListItems) {
@@ -386,11 +386,7 @@ private:
break;
}
if (elem) {
print(*elem, depth + 1);
} else {
printNullptr();
}
print(elem, depth + 1);
listItemsPrinted++;
printedHere++;
}
@@ -411,23 +407,23 @@ private:
if (v.isLambda()) {
output << "lambda";
if (v.lambda.fun) {
if (v.lambda.fun->name) {
output << " " << state.ctx.symbols[v.lambda.fun->name];
if (v.lambda().fun) {
if (v.lambda().fun->name) {
output << " " << state.ctx.symbols[v.lambda().fun->name];
}
std::ostringstream s;
s << state.ctx.positions[v.lambda.fun->pos];
s << state.ctx.positions[v.lambda().fun->pos];
output << " @ " << filterANSIEscapes(s.str());
}
} else if (v.isPrimOp()) {
if (v.primOp)
output << *v.primOp;
if (v.primOp())
output << *v.primOp();
else
output << "primop";
} else if (v.isPrimOpApp()) {
output << "partially applied ";
auto primOp = v.primOpAppPrimOp();
auto primOp = v.app().target().primOp();
if (primOp)
output << *primOp;
else
@@ -468,7 +464,7 @@ private:
void printExternal(Value & v)
{
v.external->print(output);
v.external()->print(output);
}
void printUnknown()
@@ -495,11 +491,22 @@ private:
checkInterrupt();
try {
if (v.isInvalid()) {
if (options.ansiColors) {
output << ANSI_MAGENTA;
}
output << "«invalid»";
if (options.ansiColors) {
output << ANSI_NORMAL;
}
return;
}
if (options.force) {
state.forceValue(v, noPos);
}
switch (v.type()) {
switch (v.type(true)) {
case nInt:
printInt(v);
+6 -3
View File
@@ -47,6 +47,8 @@ private:
*/
std::string contents;
Value::String strcb;
/*
* A value containing a string that can be immediately passed to the evaluator.
*/
@@ -55,7 +57,8 @@ private:
public:
explicit InternedSymbol(std::string_view s)
: contents(s)
, underlyingValue(NewValueAs::string, contents.c_str(), nullptr)
, strcb{.content = contents.c_str(), .context = nullptr}
, underlyingValue(NewValueAs::string, &strcb)
{
}
@@ -79,9 +82,9 @@ public:
return contents;
}
const Value * toValuePtr() const
Value toValue() const
{
return &underlyingValue;
return underlyingValue;
}
friend std::ostream & operator<<(std::ostream & os, const InternedSymbol & symbol);
+15 -12
View File
@@ -19,11 +19,11 @@ JSON printValueAsJSON(EvalState & state, bool strict,
switch (v.type()) {
case nInt:
out = v.integer.value;
out = v.integer().value;
break;
case nBool:
out = v.boolean;
out = v.boolean();
break;
case nString:
@@ -51,24 +51,27 @@ JSON printValueAsJSON(EvalState & state, bool strict,
out = *maybeString;
break;
}
auto i = v.attrs->get(state.ctx.s.outPath);
auto i = v.attrs()->get(state.ctx.s.outPath);
if (!i) {
out = JSON::object();
StringSet names;
for (auto & j : *v.attrs)
for (auto & j : *v.attrs())
names.emplace(state.ctx.symbols[j.name]);
for (auto & j : names) {
const Attr & a(*v.attrs->get(state.ctx.symbols.create(j)));
const Attr & a(*v.attrs()->get(state.ctx.symbols.create(j)));
try {
out[j] = printValueAsJSON(state, strict, *a.value, a.pos, context, copyToStore);
out[j] =
printValueAsJSON(state, strict, a.value, a.pos, context, copyToStore);
} catch (Error & e) {
e.addTrace(state.ctx.positions[a.pos],
HintFmt("while evaluating attribute '%1%'", j));
e.addTrace(
state.ctx.positions[a.pos],
HintFmt("while evaluating attribute '%1%'", j)
);
throw;
}
}
} else {
return printValueAsJSON(state, strict, *i->value, i->pos, context, copyToStore);
return printValueAsJSON(state, strict, i->value, i->pos, context, copyToStore);
}
break;
}
@@ -78,7 +81,7 @@ JSON printValueAsJSON(EvalState & state, bool strict,
int i = 0;
for (auto elem : v.listItems()) {
try {
out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore));
out.push_back(printValueAsJSON(state, strict, elem, pos, context, copyToStore));
} catch (Error & e) {
e.addTrace(state.ctx.positions[pos],
HintFmt("while evaluating list element at index %1%", i));
@@ -90,11 +93,11 @@ JSON printValueAsJSON(EvalState & state, bool strict,
}
case nExternal:
return v.external->printValueAsJSON(state, strict, context, copyToStore);
return v.external()->printValueAsJSON(state, strict, context, copyToStore);
break;
case nFloat:
out = v.fpoint;
out = v.fpoint();
break;
case nThunk:
+27 -22
View File
@@ -43,8 +43,7 @@ static void showAttrs(EvalState & state, bool strict, bool location,
if (location && a.pos) posToXML(state, xmlAttrs, state.ctx.positions[a.pos]);
XMLOpenElement _(doc, "attr", xmlAttrs);
printValueAsXML(state, strict, location,
*a.value, doc, context, drvsSeen, a.pos);
printValueAsXML(state, strict, location, a.value, doc, context, drvsSeen, a.pos);
}
}
@@ -60,11 +59,11 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
switch (v.type()) {
case nInt:
doc.writeEmptyElement("int", singletonAttrs("value", fmt("%1%", v.integer)));
doc.writeEmptyElement("int", singletonAttrs("value", fmt("%1%", v.integer())));
break;
case nBool:
doc.writeEmptyElement("bool", singletonAttrs("value", v.boolean ? "true" : "false"));
doc.writeEmptyElement("bool", singletonAttrs("value", v.boolean() ? "true" : "false"));
break;
case nString:
@@ -85,43 +84,49 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
if (state.isDerivation(v)) {
XMLAttrs xmlAttrs;
auto a = v.attrs->get(state.ctx.symbols.create("derivation"));
auto a = v.attrs()->get(state.ctx.symbols.create("derivation"));
Path drvPath;
a = v.attrs->get(state.ctx.s.drvPath);
a = v.attrs()->get(state.ctx.s.drvPath);
if (a) {
if (strict) state.forceValue(*a->value, a->pos);
if (a->value->type() == nString)
xmlAttrs["drvPath"] = drvPath = a->value->str();
if (strict) {
state.forceValue(a->value, a->pos);
}
if (a->value.type() == nString) {
xmlAttrs["drvPath"] = drvPath = a->value.str();
}
}
a = v.attrs->get(state.ctx.s.outPath);
a = v.attrs()->get(state.ctx.s.outPath);
if (a) {
if (strict) state.forceValue(*a->value, a->pos);
if (a->value->type() == nString) {
xmlAttrs["outPath"] = a->value->str();
if (strict) {
state.forceValue(a->value, a->pos);
}
if (a->value.type() == nString) {
xmlAttrs["outPath"] = a->value.str();
}
}
XMLOpenElement _(doc, "derivation", xmlAttrs);
if (drvPath != "" && drvsSeen.insert(drvPath).second)
showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen);
showAttrs(state, strict, location, *v.attrs(), doc, context, drvsSeen);
else
doc.writeEmptyElement("repeated");
}
else {
XMLOpenElement _(doc, "attrs");
showAttrs(state, strict, location, *v.attrs, doc, context, drvsSeen);
showAttrs(state, strict, location, *v.attrs(), doc, context, drvsSeen);
}
break;
case nList: {
XMLOpenElement _(doc, "list");
for (auto v2 : v.listItems())
printValueAsXML(state, strict, location, *v2, doc, context, drvsSeen, pos);
for (auto & v2 : v.listItems()) {
printValueAsXML(state, strict, location, v2, doc, context, drvsSeen, pos);
}
break;
}
@@ -132,10 +137,10 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break;
}
XMLAttrs xmlAttrs;
if (location) posToXML(state, xmlAttrs, state.ctx.positions[v.lambda.fun->pos]);
if (location) posToXML(state, xmlAttrs, state.ctx.positions[v.lambda().fun->pos]);
XMLOpenElement _(doc, "function", xmlAttrs);
if (auto formals = dynamic_cast<AttrsPattern *>(v.lambda.fun->pattern.get()); formals) {
if (auto formals = dynamic_cast<AttrsPattern *>(v.lambda().fun->pattern.get()); formals) {
XMLAttrs attrs;
if (formals->name) attrs["name"] = state.ctx.symbols[formals->name];
if (formals->ellipsis) attrs["ellipsis"] = "1";
@@ -143,17 +148,17 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
for (const AttrsPattern::Formal & i : formals->lexicographicOrder(state.ctx.symbols))
doc.writeEmptyElement("attr", singletonAttrs("name", state.ctx.symbols[i.name]));
} else
doc.writeEmptyElement("varpat", singletonAttrs("name", state.ctx.symbols[v.lambda.fun->pattern->name]));
doc.writeEmptyElement("varpat", singletonAttrs("name", state.ctx.symbols[v.lambda().fun->pattern->name]));
break;
}
case nExternal:
v.external->printValueAsXML(state, strict, location, doc, context, drvsSeen, pos);
v.external()->printValueAsXML(state, strict, location, doc, context, drvsSeen, pos);
break;
case nFloat:
doc.writeEmptyElement("float", singletonAttrs("value", fmt("%1%", v.fpoint)));
doc.writeEmptyElement("float", singletonAttrs("value", fmt("%1%", v.fpoint())));
break;
case nThunk:
+17 -38
View File
@@ -9,26 +9,23 @@
namespace nix
{
Value Value::EMPTY_LIST{Value::list_t{}, {}};
static const Value::List emptyListData{.size = 0};
Value Value::EMPTY_LIST{Value::list_t{}, &emptyListData};
static void copyContextToValue(Value & v, const NixStringContext & context)
const Value::Null Value::NULL_ACB = {{Value::Acb::tNull}};
static void copyContextToValue(Value::String & s, const NixStringContext & context)
{
if (!context.empty()) {
size_t n = 0;
v.string.context = gcAllocType<char const *>(context.size() + 1);
s.context = gcAllocType<char const *>(context.size() + 1);
for (auto & i : context)
v.string.context[n++] = gcCopyStringIfNeeded(i.to_string());
v.string.context[n] = 0;
s.context[n++] = gcCopyStringIfNeeded(i.to_string());
s.context[n] = 0;
}
}
Value::Value(primop_t, PrimOp & primop)
: internalType(tPrimOp)
, primOp(&primop)
, _primop_pad(0)
{
}
Value::Value(primop_t, PrimOp & primop) : raw(tag(tAuxiliary, &primop)) {}
void Value::print(EvalState & state, std::ostream & str, PrintOptions options)
{
@@ -37,33 +34,16 @@ void Value::print(EvalState & state, std::ostream & str, PrintOptions options)
bool Value::isTrivial() const
{
return
internalType != tApp
&& internalType != tPrimOpApp
&& (internalType != tThunk
|| (thunk.expr->try_cast<ExprSet>()
&& static_cast<ExprSet *>(thunk.expr)->dynamicAttrs.empty())
|| thunk.expr->try_cast<ExprLambda>()
|| thunk.expr->try_cast<ExprList>());
}
PrimOp * Value::primOpAppPrimOp() const
{
Value * left = primOpApp.left;
while (left && !left->isPrimOp()) {
left = left->primOpApp.left;
}
if (!left)
return nullptr;
return left->primOp;
return internalType() != tApp
&& (internalType() != tThunk
|| (thunk().expr->try_cast<ExprSet>()
&& static_cast<ExprSet *>(thunk().expr)->dynamicAttrs.empty())
|| thunk().expr->try_cast<ExprLambda>() || thunk().expr->try_cast<ExprList>());
}
void Value::mkPrimOp(PrimOp * p)
{
clearValue();
internalType = tPrimOp;
primOp = p;
*this = {NewValueAs::primop, *p};
}
void Value::mkString(std::string_view s)
@@ -74,16 +54,15 @@ void Value::mkString(std::string_view s)
void Value::mkString(std::string_view s, const NixStringContext & context)
{
mkString(s);
copyContextToValue(*this, context);
copyContextToValue(*untag<String *>(), context);
}
void Value::mkStringMove(const char * s, const NixStringContext & context)
{
mkString(s);
copyContextToValue(*this, context);
copyContextToValue(*untag<String *>(), context);
}
void Value::mkPath(const SourcePath & path)
{
*this = Value(NewValueAs::path, path);
+546 -373
View File
File diff suppressed because it is too large Load Diff
+11 -9
View File
@@ -96,24 +96,26 @@ struct CmdBundle : InstallableCommand
lockFlags
};
auto vRes = evaluator->mem.allocValue();
evalState->callFunction(*bundler.toValue(*evalState).first, *val, *vRes, noPos);
Value vRes;
auto fn = bundler.toValue(*evalState).first;
evalState->callFunction(fn, val, vRes, noPos);
if (!evalState->isDerivation(*vRes))
if (!evalState->isDerivation(vRes)) {
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
}
auto attr1 = vRes->attrs->get(evaluator->s.drvPath);
auto attr1 = vRes.attrs()->get(evaluator->s.drvPath);
if (!attr1)
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
NixStringContext context2;
auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2, "");
auto drvPath = evalState->coerceToStorePath(attr1->pos, attr1->value, context2, "");
auto attr2 = vRes->attrs->get(evaluator->s.outPath);
auto attr2 = vRes.attrs()->get(evaluator->s.outPath);
if (!attr2)
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2, "");
auto outPath = evalState->coerceToStorePath(attr2->pos, attr2->value, context2, "");
aio().blockOn(store->buildPaths({
DerivedPath::Built {
@@ -123,10 +125,10 @@ struct CmdBundle : InstallableCommand
}));
if (!outLink) {
auto * attr = vRes->attrs->get(evaluator->s.name);
auto * attr = vRes.attrs()->get(evaluator->s.name);
if (!attr)
throw Error("attribute 'name' missing");
outLink = evalState->forceStringNoCtx(*attr->value, attr->pos, "");
outLink = evalState->forceStringNoCtx(attr->value, attr->pos, "");
}
// TODO: will crash if not a localFSStore?
+1 -1
View File
@@ -37,7 +37,7 @@ struct CmdEdit : InstallableCommand
auto [v, pos] = installableValue->toValue(*state);
try {
return findPackageFilename(*state, *v, installable->what());
return findPackageFilename(*state, v, installable->what());
} catch (NoPositionInfo &) {
throw Error("cannot find position information for '%s", installableValue->what());
}
+13 -8
View File
@@ -79,21 +79,26 @@ struct CmdEval : MixJSON, InstallableCommand, MixReadOnlyOption
NixStringContext context;
if (apply) {
auto vApply = evaluator->mem.allocValue();
state->eval(evaluator->parseExprFromString(*apply, CanonPath::fromCwd()), *vApply);
auto vRes = evaluator->mem.allocValue();
state->callFunction(*vApply, *v, *vRes, noPos);
Value vApply;
state->eval(evaluator->parseExprFromString(*apply, CanonPath::fromCwd()), vApply);
Value vRes;
state->callFunction(vApply, v, vRes, noPos);
v = vRes;
}
if (raw) {
logger->pause();
writeFull(STDOUT_FILENO, *state->coerceToString(noPos, *v, context, "while generating the eval command output"));
writeFull(
STDOUT_FILENO,
*state->coerceToString(
noPos, v, context, "while generating the eval command output"
)
);
}
else if (json)
{
logger->cout("%s", printValueAsJSON(*state, true, *v, pos, context, false));
logger->cout("%s", printValueAsJSON(*state, true, v, pos, context, false));
}
else
@@ -102,8 +107,8 @@ struct CmdEval : MixJSON, InstallableCommand, MixReadOnlyOption
"%s",
ValuePrinter(
*state,
*v,
PrintOptions {
v,
PrintOptions{
.force = true,
.derivationPaths = true,
.errors = ErrorPrintBehavior::ThrowTopLevel,
+105 -71
View File
@@ -182,22 +182,24 @@ static void enumerateOutputs(
{
state.forceAttrs(vFlake, noPos, "while evaluating a flake to get its outputs");
auto aOutputs = vFlake.attrs->get(state.ctx.symbols.create("outputs"));
auto aOutputs = vFlake.attrs()->get(state.ctx.symbols.create("outputs"));
assert(aOutputs);
state.forceAttrs(*aOutputs->value, noPos, "while evaluating the outputs of a flake");
state.forceAttrs(aOutputs->value, noPos, "while evaluating the outputs of a flake");
auto sHydraJobs = state.ctx.symbols.create("hydraJobs");
/* Hack: ensure that hydraJobs is evaluated before anything
else. This way we can disable IFD for hydraJobs and then enable
it for other outputs. */
if (auto attr = aOutputs->value->attrs->get(sHydraJobs))
callback(state.ctx.symbols[attr->name], *attr->value, attr->pos);
if (auto attr = aOutputs->value.attrs()->get(sHydraJobs)) {
callback(state.ctx.symbols[attr->name], attr->value, attr->pos);
}
for (auto & attr : *aOutputs->value->attrs) {
if (attr.name != sHydraJobs)
callback(state.ctx.symbols[attr.name], *attr.value, attr.pos);
for (auto & attr : *aOutputs->value.attrs()) {
if (attr.name != sHydraJobs) {
callback(state.ctx.symbols[attr.name], attr.value, attr.pos);
}
}
}
@@ -463,7 +465,7 @@ struct CmdFlakeCheck : FlakeCommand
if (!v.isLambda()) {
throw Error("overlay is not a function, but %s instead", showType(v));
}
auto body = v.lambda.fun->body->try_cast<ExprLambda>();
auto body = v.lambda().fun->body->try_cast<ExprLambda>();
if (!body)
throw Error("overlay is not a function with two arguments, but only takes one");
if (body->body->try_cast<ExprLambda>())
@@ -499,15 +501,16 @@ struct CmdFlakeCheck : FlakeCommand
if (state->isDerivation(v))
throw Error("jobset should not be a derivation at top-level");
for (auto & attr : *v.attrs) {
state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr : *v.attrs()) {
state->forceAttrs(attr.value, attr.pos, "");
auto attrPath2 = concatStrings(attrPath, ".", evaluator->symbols[attr.name]);
if (state->isDerivation(*attr.value)) {
if (state->isDerivation(attr.value)) {
Activity act(*logger, lvlInfo, actUnknown,
fmt("checking Hydra job '%s'", attrPath2));
checkDerivation(attrPath2, *attr.value, attr.pos);
} else
checkHydraJobs(attrPath2, *attr.value, attr.pos);
checkDerivation(attrPath2, attr.value, attr.pos);
} else {
checkHydraJobs(attrPath2, attr.value, attr.pos);
}
}
} catch (Error & e) {
@@ -522,9 +525,10 @@ struct CmdFlakeCheck : FlakeCommand
fmt("checking NixOS configuration '%s'", attrPath));
Bindings & bindings(*evaluator->mem.allocBindings(0));
auto vToplevel = findAlongAttrPath(*state, "config.system.build.toplevel", bindings, v).first;
state->forceValue(*vToplevel, pos);
if (!state->isDerivation(*vToplevel))
state->forceValue(vToplevel, pos);
if (!state->isDerivation(vToplevel)) {
throw Error("attribute 'config.system.build.toplevel' is not a derivation");
}
} catch (Error & e) {
e.addTrace(resolve(pos), HintFmt("while checking the NixOS configuration '%s'", attrPath));
reportError(e);
@@ -538,11 +542,11 @@ struct CmdFlakeCheck : FlakeCommand
state->forceAttrs(v, pos, "");
if (auto attr = v.attrs->get(evaluator->symbols.create("path"))) {
if (auto attr = v.attrs()->get(evaluator->symbols.create("path"))) {
if (attr->name == evaluator->symbols.create("path")) {
NixStringContext context;
auto path = state->ctx.paths.checkSourcePath(
state->coerceToPath(attr->pos, *attr->value, context, "")
state->coerceToPath(attr->pos, attr->value, context, "")
);
if (!path.pathExists())
throw Error("template '%s' refers to a non-existent path '%s'", attrPath, path);
@@ -551,12 +555,12 @@ struct CmdFlakeCheck : FlakeCommand
} else
throw Error("template '%s' lacks attribute 'path'", attrPath);
if (auto attr = v.attrs->get(evaluator->symbols.create("description")))
state->forceStringNoCtx(*attr->value, attr->pos, "");
if (auto attr = v.attrs()->get(evaluator->symbols.create("description")))
state->forceStringNoCtx(attr->value, attr->pos, "");
else
throw Error("template '%s' lacks attribute 'description'", attrPath);
for (auto & attr : *v.attrs) {
for (auto & attr : *v.attrs()) {
std::string_view name(evaluator->symbols[attr.name]);
if (name != "path" && name != "description" && name != "welcomeText")
throw Error("template '%s' has unsupported attribute '%s'", attrPath, name);
@@ -584,12 +588,12 @@ struct CmdFlakeCheck : FlakeCommand
{
Activity act(*logger, lvlInfo, actUnknown, "evaluating flake");
auto vFlake = evaluator->mem.allocValue();
flake::callFlake(*state, flake, *vFlake);
Value vFlake;
flake::callFlake(*state, flake, vFlake);
enumerateOutputs(
*state,
*vFlake,
vFlake,
[&](const std::string_view name, Value & vOutput, const PosIdx pos) {
Activity act(*logger, lvlInfo, actUnknown,
fmt("checking flake output '%s'", name));
@@ -617,15 +621,20 @@ struct CmdFlakeCheck : FlakeCommand
if (name == "checks") {
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
for (auto & attr : *vOutput.attrs()) {
const auto & attr_name = evaluator->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
if (checkSystemType(attr_name, attr.pos)) {
state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs) {
state->forceAttrs(attr.value, attr.pos, "");
for (auto & attr2 : *attr.value.attrs()) {
auto drvPath = checkDerivation(
fmt("%s.%s.%s", name, attr_name, evaluator->symbols[attr2.name]),
*attr2.value, attr2.pos);
fmt("%s.%s.%s",
name,
attr_name,
evaluator->symbols[attr2.name]),
attr2.value,
attr2.pos
);
if (drvPath && attr_name == evalSettings.getCurrentSystem()) {
drvPaths.push_back(DerivedPath::Built {
.drvPath = makeConstantStorePath(*drvPath),
@@ -640,13 +649,11 @@ struct CmdFlakeCheck : FlakeCommand
else if (name == "formatter")
{
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
for (auto & attr : *vOutput.attrs()) {
const auto & attr_name = evaluator->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
if (checkSystemType(attr_name, attr.pos)) {
checkApp(
fmt("%s.%s", name, attr_name),
*attr.value, attr.pos);
checkApp(fmt("%s.%s", name, attr_name), attr.value, attr.pos);
};
}
}
@@ -654,15 +661,21 @@ struct CmdFlakeCheck : FlakeCommand
else if (name == "packages" || name == "devShells")
{
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
for (auto & attr : *vOutput.attrs()) {
const auto & attr_name = evaluator->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
if (checkSystemType(attr_name, attr.pos)) {
state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs)
state->forceAttrs(attr.value, attr.pos, "");
for (auto & attr2 : *attr.value.attrs()) {
checkDerivation(
fmt("%s.%s.%s", name, attr_name, evaluator->symbols[attr2.name]),
*attr2.value, attr2.pos);
fmt("%s.%s.%s",
name,
attr_name,
evaluator->symbols[attr2.name]),
attr2.value,
attr2.pos
);
}
};
}
}
@@ -670,15 +683,21 @@ struct CmdFlakeCheck : FlakeCommand
else if (name == "apps")
{
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
for (auto & attr : *vOutput.attrs()) {
const auto & attr_name = evaluator->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
if (checkSystemType(attr_name, attr.pos)) {
state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs)
state->forceAttrs(attr.value, attr.pos, "");
for (auto & attr2 : *attr.value.attrs()) {
checkApp(
fmt("%s.%s.%s", name, attr_name, evaluator->symbols[attr2.name]),
*attr2.value, attr2.pos);
fmt("%s.%s.%s",
name,
attr_name,
evaluator->symbols[attr2.name]),
attr2.value,
attr2.pos
);
}
};
}
}
@@ -686,13 +705,13 @@ struct CmdFlakeCheck : FlakeCommand
else if (name == "defaultPackage" || name == "devShell")
{
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
for (auto & attr : *vOutput.attrs()) {
const auto & attr_name = evaluator->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
if (checkSystemType(attr_name, attr.pos)) {
checkDerivation(
fmt("%s.%s", name, attr_name),
*attr.value, attr.pos);
fmt("%s.%s", name, attr_name), attr.value, attr.pos
);
};
}
}
@@ -700,13 +719,11 @@ struct CmdFlakeCheck : FlakeCommand
else if (name == "defaultApp")
{
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
for (auto & attr : *vOutput.attrs()) {
const auto & attr_name = evaluator->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
if (checkSystemType(attr_name, attr.pos) ) {
checkApp(
fmt("%s.%s", name, attr_name),
*attr.value, attr.pos);
checkApp(fmt("%s.%s", name, attr_name), attr.value, attr.pos);
};
}
}
@@ -714,7 +731,7 @@ struct CmdFlakeCheck : FlakeCommand
else if (name == "legacyPackages")
{
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
for (auto & attr : *vOutput.attrs()) {
checkSystemName(evaluator->symbols[attr.name], attr.pos);
checkSystemType(evaluator->symbols[attr.name], attr.pos);
// FIXME: do getDerivations?
@@ -729,9 +746,12 @@ struct CmdFlakeCheck : FlakeCommand
else if (name == "overlays")
{
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs)
checkOverlay(fmt("%s.%s", name, evaluator->symbols[attr.name]),
*attr.value, attr.pos);
for (auto & attr : *vOutput.attrs())
checkOverlay(
fmt("%s.%s", name, evaluator->symbols[attr.name]),
attr.value,
attr.pos
);
}
else if (name == "nixosModule")
@@ -742,17 +762,23 @@ struct CmdFlakeCheck : FlakeCommand
else if (name == "nixosModules")
{
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs)
checkModule(fmt("%s.%s", name, evaluator->symbols[attr.name]),
*attr.value, attr.pos);
for (auto & attr : *vOutput.attrs())
checkModule(
fmt("%s.%s", name, evaluator->symbols[attr.name]),
attr.value,
attr.pos
);
}
else if (name == "nixosConfigurations")
{
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs)
checkNixOSConfiguration(fmt("%s.%s", name, evaluator->symbols[attr.name]),
*attr.value, attr.pos);
for (auto & attr : *vOutput.attrs())
checkNixOSConfiguration(
fmt("%s.%s", name, evaluator->symbols[attr.name]),
attr.value,
attr.pos
);
}
else if (name == "hydraJobs")
@@ -768,21 +794,24 @@ struct CmdFlakeCheck : FlakeCommand
else if (name == "templates")
{
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs)
checkTemplate(fmt("%s.%s", name, evaluator->symbols[attr.name]),
*attr.value, attr.pos);
for (auto & attr : *vOutput.attrs())
checkTemplate(
fmt("%s.%s", name, evaluator->symbols[attr.name]),
attr.value,
attr.pos
);
}
else if (name == "defaultBundler")
{
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
for (auto & attr : *vOutput.attrs()) {
const auto & attr_name = evaluator->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
if (checkSystemType(attr_name, attr.pos)) {
checkBundler(
fmt("%s.%s", name, attr_name),
*attr.value, attr.pos);
fmt("%s.%s", name, attr_name), attr.value, attr.pos
);
};
}
}
@@ -790,15 +819,20 @@ struct CmdFlakeCheck : FlakeCommand
else if (name == "bundlers")
{
state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) {
for (auto & attr : *vOutput.attrs()) {
const auto & attr_name = evaluator->symbols[attr.name];
checkSystemName(attr_name, attr.pos);
if (checkSystemType(attr_name, attr.pos)) {
state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs) {
state->forceAttrs(attr.value, attr.pos, "");
for (auto & attr2 : *attr.value.attrs()) {
checkBundler(
fmt("%s.%s.%s", name, attr_name, evaluator->symbols[attr2.name]),
*attr2.value, attr2.pos);
fmt("%s.%s.%s",
name,
attr_name,
evaluator->symbols[attr2.name]),
attr2.value,
attr2.pos
);
}
};
}
+24 -15
View File
@@ -357,23 +357,28 @@ static void showHelp(AsyncIoRoot & aio, std::vector<std::string> subcommand, Nix
Evaluator evaluator(aio, {}, aio.blockOn(openStore("dummy://")));
auto state = evaluator.begin(aio);
auto vGenerateManpage = evaluator.mem.allocValue();
state->eval(evaluator.parseExprFromString(
#include "generate-manpage.nix.gen.hh"
, CanonPath::root), *vGenerateManpage);
Value vGenerateManpage;
state->eval(
evaluator.parseExprFromString(
#include "generate-manpage.nix.gen.hh"
, CanonPath::root
),
vGenerateManpage
);
auto vDump = evaluator.mem.allocValue();
vDump->mkString(toplevel.dumpCli());
Value vDump;
vDump.mkString(toplevel.dumpCli());
auto vRes = evaluator.mem.allocValue();
state->callFunction(*vGenerateManpage, evaluator.builtins.get("false"), *vRes, noPos);
state->callFunction(*vRes, *vDump, *vRes, noPos);
Value vRes;
state->callFunction(vGenerateManpage, evaluator.builtins.get("false"), vRes, noPos);
state->callFunction(vRes, vDump, vRes, noPos);
auto attr = vRes->attrs->get(evaluator.symbols.create(mdName + ".md"));
auto attr = vRes.attrs()->get(evaluator.symbols.create(mdName + ".md"));
if (!attr)
throw UsageError("`nix` has no subcommand '%s'", concatStringsSep("", subcommand));
auto markdown = state->forceString(*attr->value, noPos, "while evaluating the lowdown help text");
auto markdown =
state->forceString(attr->value, noPos, "while evaluating the lowdown help text");
RunPager pager;
std::cout << renderMarkdownToTerminal(markdown) << "\n";
@@ -523,12 +528,16 @@ void mainWrapped(AsyncIoRoot & aio, int argc, char * * argv)
auto res = JSON::object();
res["builtins"] = ({
auto builtinsJson = JSON::object();
auto builtins = state.builtins.env.values[0]->attrs;
auto builtins = state.builtins.env.values[0].attrs();
for (auto & builtin : *builtins) {
auto b = JSON::object();
if (!builtin.value->isPrimOp()) continue;
auto primOp = builtin.value->primOp;
if (!primOp->doc) continue;
if (!builtin.value.isPrimOp()) {
continue;
}
auto primOp = builtin.value.primOp();
if (!primOp->doc) {
continue;
}
b["arity"] = primOp->arity;
b["args"] = primOp->args;
b["doc"] = trim(stripIndentation(primOp->doc));
+28 -13
View File
@@ -35,16 +35,19 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url)
vMirrors);
state.forceAttrs(vMirrors, noPos, "while evaluating the set of all mirrors");
auto mirrorList = vMirrors.attrs->get(state.ctx.symbols.create(mirrorName));
auto mirrorList = vMirrors.attrs()->get(state.ctx.symbols.create(mirrorName));
if (!mirrorList) {
throw Error("unknown mirror name '%s'", mirrorName);
}
state.forceList(*mirrorList->value, noPos, "while evaluating one mirror configuration");
state.forceList(mirrorList->value, noPos, "while evaluating one mirror configuration");
if (mirrorList->value->listSize() < 1)
if (mirrorList->value.listSize() < 1) {
throw Error("mirror URL '%s' did not expand to anything", url);
}
std::string mirror(state.forceString(*mirrorList->value->listElems()[0], noPos, "while evaluating the first available mirror"));
std::string mirror(state.forceString(
mirrorList->value.listElems()[0], noPos, "while evaluating the first available mirror"
));
return mirror + (mirror.ends_with("/") ? "" : "/") + s.substr(p + 1);
}
@@ -207,30 +210,42 @@ static int main_nix_prefetch_url(AsyncIoRoot & aio, std::string programName, Str
evaluator->paths.resolveExprPath(
aio.blockOn(lookupFileArg(*evaluator, args.empty() ? "." : args[0])).unwrap()),
vRoot);
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
Value v(findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
state->forceAttrs(v, noPos, "while evaluating the source attribute to prefetch");
/* Extract the URL. */
auto * attr = v.attrs->get(evaluator->symbols.create("urls"));
auto * attr = v.attrs()->get(evaluator->symbols.create("urls"));
if (!attr)
throw Error("attribute 'urls' missing");
state->forceList(*attr->value, noPos, "while evaluating the urls to prefetch");
if (attr->value->listSize() < 1)
state->forceList(attr->value, noPos, "while evaluating the urls to prefetch");
if (attr->value.listSize() < 1) {
throw Error("'urls' list is empty");
url = state->forceString(*attr->value->listElems()[0], noPos, "while evaluating the first url from the urls list");
}
url = state->forceString(
attr->value.listElems()[0],
noPos,
"while evaluating the first url from the urls list"
);
/* Extract the hash mode. */
auto attr2 = v.attrs->get(evaluator->symbols.create("outputHashMode"));
auto attr2 = v.attrs()->get(evaluator->symbols.create("outputHashMode"));
if (!attr2)
printInfo("warning: this does not look like a fetchurl call");
else
unpack = state->forceString(*attr2->value, noPos, "while evaluating the outputHashMode of the source to prefetch") == "recursive";
unpack = state->forceString(
attr2->value,
noPos,
"while evaluating the outputHashMode of the source to prefetch"
)
== "recursive";
/* Extract the name. */
if (!name) {
auto attr3 = v.attrs->get(evaluator->symbols.create("name"));
auto attr3 = v.attrs()->get(evaluator->symbols.create("name"));
if (!attr3)
name = state->forceString(*attr3->value, noPos, "while evaluating the name of the source to prefetch");
name = state->forceString(
attr3->value, noPos, "while evaluating the name of the source to prefetch"
);
}
}
+5 -5
View File
@@ -66,15 +66,15 @@ struct CmdRepl : RawInstallablesCommand
if (file){
auto [val, pos] = installable.toValue(*state);
auto what = installable.what();
state->forceValue(*val, pos);
state->forceValue(val, pos);
auto autoArgs = getAutoArgs(*evaluator);
auto valPost = evaluator->mem.allocValue();
state->autoCallFunction(*autoArgs, *val, *valPost, pos);
state->forceValue(*valPost, pos);
Value valPost;
state->autoCallFunction(*autoArgs, val, valPost, pos);
state->forceValue(valPost, pos);
values.push_back( {valPost, what });
} else {
auto [val, pos] = installable.toValue(*state);
values.push_back( {val, what} );
values.push_back({val, what});
}
}
return values;
+6 -4
View File
@@ -292,12 +292,14 @@ struct CmdUpgradeNix : MixDryRun, EvalCommand
auto evaluator = std::make_unique<Evaluator>(aio(), SearchPath{}, store);
auto state = evaluator->begin(aio());
auto v = evaluator->mem.allocValue();
state->eval(evaluator->parseExprFromString(data, CanonPath("/no-such-path")), *v);
Value v;
state->eval(evaluator->parseExprFromString(data, CanonPath("/no-such-path")), v);
Bindings & bindings(*evaluator->mem.allocBindings(0));
auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first;
auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, v).first;
return store->parseStorePath(state->forceString(*v2, noPos, "while evaluating the path tho latest nix version"));
return store->parseStorePath(
state->forceString(v2, noPos, "while evaluating the path tho latest nix version")
);
}
};
+25 -25
View File
@@ -41,9 +41,9 @@
#include "buffered-io.hh"
#include "eval-args.hh"
static nix::Value *releaseExprTopLevelValue(nix::EvalState &state,
nix::Bindings &autoArgs,
MyArgs &args) {
static nix::Value releaseExprTopLevelValue(nix::EvalState &state,
nix::Bindings &autoArgs,
MyArgs &args) {
nix::Value vTop;
if (args.fromArgs) {
@@ -57,9 +57,9 @@ static nix::Value *releaseExprTopLevelValue(nix::EvalState &state,
vTop);
}
auto vRoot = state.ctx.mem.allocValue();
nix::Value vRoot;
state.autoCallFunction(autoArgs, vTop, *vRoot, {});
state.autoCallFunction(autoArgs, vTop, vRoot, {});
return vRoot;
}
@@ -78,13 +78,13 @@ static std::string attrPathJoin(nix::JSON input) {
static std::optional<Constituents>
readConstituents(const nix::Value *v, nix::box_ptr<nix::EvalState> &state,
nix::ref<nix::eval_cache::CachingEvaluator> &evaluator) {
auto a = v->attrs->get(state->ctx.symbols.create("_hydraAggregate"));
if (a && state->forceBool(*a->value, a->pos,
auto a = v->attrs()->get(state->ctx.symbols.create("_hydraAggregate"));
if (a && state->forceBool(a->value, a->pos,
"while evaluating the "
"`_hydraAggregate` attribute")) {
std::vector<std::string> constituents;
std::vector<std::string> namedConstituents;
auto a = v->attrs->get(state->ctx.symbols.create("constituents"));
auto a = v->attrs()->get(state->ctx.symbols.create("constituents"));
if (!a)
state->ctx.errors
.make<nix::EvalError>("derivation must have a constituents "
@@ -92,7 +92,7 @@ readConstituents(const nix::Value *v, nix::box_ptr<nix::EvalState> &state,
.debugThrow(nix::always_progresses); // we can't have a debugger here
nix::NixStringContext context;
state->coerceToString(a->pos, *a->value, context,
state->coerceToString(a->pos, a->value, context,
"while evaluating the `constituents` attribute",
nix::StringCoercionMode::ToString, false);
for (auto &c : context)
@@ -106,14 +106,14 @@ readConstituents(const nix::Value *v, nix::box_ptr<nix::EvalState> &state,
},
c.raw);
state->forceList(*a->value, a->pos,
state->forceList(a->value, a->pos,
"while evaluating the "
"`constituents` attribute");
for (unsigned int n = 0; n < a->value->listSize(); ++n) {
auto v = a->value->listElems()[n];
state->forceValue(*v, nix::noPos);
if (v->type() == nix::nString)
namedConstituents.emplace_back(v->str());
for (unsigned int n = 0; n < a->value.listSize(); ++n) {
auto v = a->value.listElems()[n];
state->forceValue(v, nix::noPos);
if (v.type() == nix::nString)
namedConstituents.emplace_back(v.str());
}
return Constituents(constituents, namedConstituents);
@@ -126,7 +126,7 @@ void worker(nix::ref<nix::eval_cache::CachingEvaluator> evaluator,
nix::Bindings &autoArgs, nix::AutoCloseFD &to,
nix::AutoCloseFD &from, MyArgs &args, nix::AsyncIoRoot &aio) {
nix::Value *vRoot = [&]() {
nix::Value vRoot = [&]() {
auto state = evaluator->begin(aio);
if (args.flake) {
auto [flakeRef, fragment, outputSpec] =
@@ -168,18 +168,18 @@ void worker(nix::ref<nix::eval_cache::CachingEvaluator> evaluator,
nix::JSON{{"attr", attrPathS}, {"attrPath", path}};
try {
auto vTmp =
nix::findAlongAttrPath(*state, attrPathS, autoArgs, *vRoot)
nix::findAlongAttrPath(*state, attrPathS, autoArgs, vRoot)
.first;
auto v = evaluator->mem.allocValue();
state->autoCallFunction(autoArgs, *vTmp, *v, {});
nix::Value v;
state->autoCallFunction(autoArgs, vTmp, v, {});
if (v->type() == nix::nAttrs) {
if (auto drvInfo = nix::getDerivation(*state, *v, false)) {
if (v.type() == nix::nAttrs) {
if (auto drvInfo = nix::getDerivation(*state, v, false)) {
std::optional<Constituents> maybeConstituents;
if (args.constituents) {
maybeConstituents =
readConstituents(v, state, evaluator);
readConstituents(&v, state, evaluator);
}
auto drv = Drv(attrPathS, *state, *drvInfo, args,
maybeConstituents);
@@ -197,16 +197,16 @@ void worker(nix::ref<nix::eval_cache::CachingEvaluator> evaluator,
// = true;` for top-level attrset
for (auto &i :
v->attrs->lexicographicOrder(evaluator->symbols)) {
v.attrs()->lexicographicOrder(evaluator->symbols)) {
const std::string_view name = evaluator->symbols[i->name];
attrs.emplace_back(name);
if (name == "recurseForDerivations" &&
!args.forceRecurse) {
auto attrv = v->attrs->get(
auto attrv = v.attrs()->get(
evaluator->s.recurseForDerivations);
recurse = state->forceBool(
*attrv->value, attrv->pos,
attrv->value, attrv->pos,
"while evaluating recurseForDerivations");
}
}
+1 -1
View File
@@ -1 +1 @@
[ null <PRIMOP> <PRIMOP-APP> <LAMBDA> [ [ «repeated» ] ] ]
[ null <PRIMOP> <PRIMOP-APP> <LAMBDA> [ «repeated» ] ]
@@ -0,0 +1 @@
[ [ 0 1 3 7 15 31 63 127 255 511 1023 2047 4095 8191 16383 32767 65535 131071 262143 524287 1048575 2097151 4194303 8388607 16777215 33554431 67108863 134217727 268435455 536870911 1073741823 2147483647 4294967295 8589934591 17179869183 34359738367 68719476735 137438953471 274877906943 549755813887 1099511627775 2199023255551 4398046511103 8796093022207 17592186044415 35184372088831 70368744177663 140737488355327 281474976710655 562949953421311 1125899906842623 2251799813685247 4503599627370495 9007199254740991 18014398509481983 36028797018963967 72057594037927935 144115188075855871 288230376151711743 576460752303423487 1152921504606846975 2305843009213693951 4611686018427387903 9223372036854775807 ] [ -1 -2 -4 -8 -16 -32 -64 -128 -256 -512 -1024 -2048 -4096 -8192 -16384 -32768 -65536 -131072 -262144 -524288 -1048576 -2097152 -4194304 -8388608 -16777216 -33554432 -67108864 -134217728 -268435456 -536870912 -1073741824 -2147483648 -4294967296 -8589934592 -17179869184 -34359738368 -68719476736 -137438953472 -274877906944 -549755813888 -1099511627776 -2199023255552 -4398046511104 -8796093022208 -17592186044416 -35184372088832 -70368744177664 -140737488355328 -281474976710656 -562949953421312 -1125899906842624 -2251799813685248 -4503599627370496 -9007199254740992 -18014398509481984 -36028797018963968 -72057594037927936 -144115188075855872 -288230376151711744 -576460752303423488 -1152921504606846976 -2305843009213693952 -4611686018427387904 -9223372036854775808 ] ]
@@ -0,0 +1,8 @@
let
positive = n: acc: if n == 0 then [] else [ acc ] ++ positive (n - 1) (acc * 2 + 1);
negative = n: acc: if n == 0 then [] else [ acc ] ++ negative (n - 1) (acc * 2);
in
[
(positive 64 0)
(negative 64 (-1))
]
@@ -1,6 +1,10 @@
[[test]]
runner = "eval-okay"
[[test]]
runner = "eval-okay"
in = "in-int-range.nix"
[[test]]
runner = "eval-okay"
in = "in-override.nix"
@@ -46,6 +46,7 @@ class NixSettings:
config = dedent(f"""
show-trace = true
sandbox = true
substituters =
extra-sandbox-paths = {" ".join(env.path.to_sandbox_paths())}
""")
# Note: newline at the end is required due to nix being nix;
+7 -7
View File
@@ -84,35 +84,35 @@ namespace nix {
if (arg.type() != nInt) {
return false;
}
return arg.integer.value == v;
return arg.integer().value == v;
}
MATCHER_P(IsFloatEq, v, fmt("The float is equal to \"%1%\"", v)) {
if (arg.type() != nFloat) {
return false;
}
return arg.fpoint == v;
return arg.fpoint() == v;
}
MATCHER(IsTrue, "") {
if (arg.type() != nBool) {
return false;
}
return arg.boolean == true;
return arg.boolean() == true;
}
MATCHER(IsFalse, "") {
if (arg.type() != nBool) {
return false;
}
return arg.boolean == false;
return arg.boolean() == false;
}
MATCHER_P(IsPathEq, p, fmt("Is a path equal to \"%1%\"", p)) {
if (arg.type() != nPath) {
*result_listener << "Expected a path got " << arg.type();
return false;
} else if (std::string_view(arg._path) != p) {
} else if (std::string_view(arg.string().content) != p) {
*result_listener << "Expected a path that equals \"" << p
<< "\" but got: " << arg.path();
return false;
@@ -136,8 +136,8 @@ namespace nix {
if (arg.type() != nAttrs) {
*result_listener << "Expected set got " << arg.type();
return false;
} else if (arg.attrs->size() != (size_t)n) {
*result_listener << "Expected a set with " << n << " attributes but got " << arg.attrs->size();
} else if (arg.attrs()->size() != (size_t)n) {
*result_listener << "Expected a set with " << n << " attributes but got " << arg.attrs()->size();
return false;
}
return true;
+2 -2
View File
@@ -15,7 +15,7 @@ namespace nix {
class AttrPathEval : public LibExprTest
{
public:
std::pair<Value *, PosIdx> testFindAlongAttrPath(std::string expr, std::string path);
std::pair<Value, PosIdx> testFindAlongAttrPath(std::string expr, std::string path);
};
RC_GTEST_PROP(AttrPath, prop_round_trip, ())
@@ -29,7 +29,7 @@ RC_GTEST_PROP(AttrPath, prop_round_trip, ())
RC_ASSERT(strings == unparsedReparsed);
}
std::pair<Value *, PosIdx> AttrPathEval::testFindAlongAttrPath(std::string expr, std::string path)
std::pair<Value, PosIdx> AttrPathEval::testFindAlongAttrPath(std::string expr, std::string path)
{
auto v = eval(expr);
auto bindings = evalState().ctx.buildBindings(0).finish();
+6 -6
View File
@@ -27,9 +27,9 @@ RC_GTEST_FIXTURE_PROP(
prop_opaque_path_round_trip,
(const SingleDerivedPath::Opaque & o))
{
auto * v = evaluator.mem.allocValue();
evaluator.paths.mkStorePathString(o.path, *v);
auto d = state.coerceToSingleDerivedPath(noPos, *v, "");
Value v;
evaluator.paths.mkStorePathString(o.path, v);
auto d = state.coerceToSingleDerivedPath(noPos, v, "");
RC_ASSERT(SingleDerivedPath { o } == d);
}
@@ -41,9 +41,9 @@ RC_GTEST_FIXTURE_PROP(
prop_derived_path_built_out_path_round_trip,
(const SingleDerivedPath::Built & b, const StorePath & outPath))
{
auto * v = evaluator.mem.allocValue();
state.mkOutputString(*v, b, outPath);
auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, *v, "");
Value v;
state.mkOutputString(v, b, outPath);
auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, v, "");
RC_ASSERT(SingleDerivedPath { b } == d);
}
+92 -92
View File
@@ -71,22 +71,22 @@ namespace nix {
auto v = eval("builtins.tryEval (throw \"\")");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto s = createSymbol("success");
auto p = v.attrs->get(s);
auto p = v.attrs()->get(s);
ASSERT_NE(p, nullptr);
ASSERT_THAT(*p->value, IsFalse());
ASSERT_THAT(p->value, IsFalse());
}
TEST_F(PrimOpTest, tryEvalSuccess) {
auto v = eval("builtins.tryEval 123");
ASSERT_THAT(v, IsAttrs());
auto s = createSymbol("success");
auto p = v.attrs->get(s);
auto p = v.attrs()->get(s);
ASSERT_NE(p, nullptr);
ASSERT_THAT(*p->value, IsTrue());
ASSERT_THAT(p->value, IsTrue());
s = createSymbol("value");
p = v.attrs->get(s);
p = v.attrs()->get(s);
ASSERT_NE(p, nullptr);
ASSERT_THAT(*p->value, IsIntEq(123));
ASSERT_THAT(p->value, IsIntEq(123));
}
TEST_F(PrimOpTest, getEnv) {
@@ -134,8 +134,8 @@ namespace nix {
TEST_F(PrimOpTest, attrValues) {
auto v = eval("builtins.attrValues { x = \"foo\"; a = 1; }");
ASSERT_THAT(v, IsListOfSize(2));
ASSERT_THAT(*v.listElems()[0], IsIntEq(1));
ASSERT_THAT(*v.listElems()[1], IsStringEq("foo"));
ASSERT_THAT(v.listElems()[0], IsIntEq(1));
ASSERT_THAT(v.listElems()[1], IsStringEq("foo"));
}
TEST_F(PrimOpTest, getAttr) {
@@ -184,14 +184,14 @@ namespace nix {
TEST_F(PrimOpTest, removeAttrsRetains) {
auto v = eval("builtins.removeAttrs { x = 1; y = 2; } [\"x\"]");
ASSERT_THAT(v, IsAttrsOfSize(1));
ASSERT_NE(v.attrs->get(createSymbol("y")), nullptr);
ASSERT_NE(v.attrs()->get(createSymbol("y")), nullptr);
}
TEST_F(PrimOpTest, listToAttrsEmptyList) {
auto v = eval("builtins.listToAttrs []");
ASSERT_THAT(v, IsAttrsOfSize(0));
ASSERT_EQ(v.type(), nAttrs);
ASSERT_EQ(v.attrs->size(), 0);
ASSERT_EQ(v.attrs()->size(), 0);
}
TEST_F(PrimOpTest, listToAttrsNotFieldName) {
@@ -201,54 +201,54 @@ namespace nix {
TEST_F(PrimOpTest, listToAttrs) {
auto v = eval("builtins.listToAttrs [ { name = \"key\"; value = 123; } ]");
ASSERT_THAT(v, IsAttrsOfSize(1));
auto key = v.attrs->get(createSymbol("key"));
auto key = v.attrs()->get(createSymbol("key"));
ASSERT_NE(key, nullptr);
ASSERT_THAT(*key->value, IsIntEq(123));
ASSERT_THAT(key->value, IsIntEq(123));
}
TEST_F(PrimOpTest, intersectAttrs) {
auto v = eval("builtins.intersectAttrs { a = 1; b = 2; } { b = 3; c = 4; }");
ASSERT_THAT(v, IsAttrsOfSize(1));
auto b = v.attrs->get(createSymbol("b"));
auto b = v.attrs()->get(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(3));
ASSERT_THAT(b->value, IsIntEq(3));
}
TEST_F(PrimOpTest, catAttrs) {
auto v = eval("builtins.catAttrs \"a\" [{a = 1;} {b = 0;} {a = 2;}]");
ASSERT_THAT(v, IsListOfSize(2));
ASSERT_THAT(*v.listElems()[0], IsIntEq(1));
ASSERT_THAT(*v.listElems()[1], IsIntEq(2));
ASSERT_THAT(v.listElems()[0], IsIntEq(1));
ASSERT_THAT(v.listElems()[1], IsIntEq(2));
}
TEST_F(PrimOpTest, functionArgs) {
auto v = eval("builtins.functionArgs ({ x, y ? 123}: 1)");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto x = v.attrs->get(createSymbol("x"));
auto x = v.attrs()->get(createSymbol("x"));
ASSERT_NE(x, nullptr);
ASSERT_THAT(*x->value, IsFalse());
ASSERT_THAT(x->value, IsFalse());
auto y = v.attrs->get(createSymbol("y"));
auto y = v.attrs()->get(createSymbol("y"));
ASSERT_NE(y, nullptr);
ASSERT_THAT(*y->value, IsTrue());
ASSERT_THAT(y->value, IsTrue());
}
TEST_F(PrimOpTest, mapAttrs) {
auto v = eval("builtins.mapAttrs (name: value: value * 10) { a = 1; b = 2; }");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto a = v.attrs->get(createSymbol("a"));
auto a = v.attrs()->get(createSymbol("a"));
ASSERT_NE(a, nullptr);
ASSERT_THAT(*a->value, IsThunk());
state.forceValue(*a->value, noPos);
ASSERT_THAT(*a->value, IsIntEq(10));
ASSERT_THAT(a->value, IsThunk());
state.forceValue(a->value, noPos);
ASSERT_THAT(a->value, IsIntEq(10));
auto b = v.attrs->get(createSymbol("b"));
auto b = v.attrs()->get(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsThunk());
state.forceValue(*b->value, noPos);
ASSERT_THAT(*b->value, IsIntEq(20));
ASSERT_THAT(b->value, IsThunk());
state.forceValue(b->value, noPos);
ASSERT_THAT(b->value, IsIntEq(20));
}
TEST_F(PrimOpTest, isList) {
@@ -288,7 +288,7 @@ namespace nix {
auto v = eval("builtins.tail [ 3 2 1 0 ]");
ASSERT_THAT(v, IsListOfSize(3));
for (const auto [n, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsIntEq(2 - static_cast<int>(n)));
ASSERT_THAT(elem, IsIntEq(2 - static_cast<int>(n)));
}
TEST_F(PrimOpTest, tailEmpty) {
@@ -299,26 +299,26 @@ namespace nix {
auto v = eval("map (x: \"foo\" + x) [ \"bar\" \"bla\" \"abc\" ]");
ASSERT_THAT(v, IsListOfSize(3));
auto elem = v.listElems()[0];
ASSERT_THAT(*elem, IsThunk());
state.forceValue(*elem, noPos);
ASSERT_THAT(*elem, IsStringEq("foobar"));
ASSERT_THAT(elem, IsThunk());
state.forceValue(elem, noPos);
ASSERT_THAT(elem, IsStringEq("foobar"));
elem = v.listElems()[1];
ASSERT_THAT(*elem, IsThunk());
state.forceValue(*elem, noPos);
ASSERT_THAT(*elem, IsStringEq("foobla"));
ASSERT_THAT(elem, IsThunk());
state.forceValue(elem, noPos);
ASSERT_THAT(elem, IsStringEq("foobla"));
elem = v.listElems()[2];
ASSERT_THAT(*elem, IsThunk());
state.forceValue(*elem, noPos);
ASSERT_THAT(*elem, IsStringEq("fooabc"));
ASSERT_THAT(elem, IsThunk());
state.forceValue(elem, noPos);
ASSERT_THAT(elem, IsStringEq("fooabc"));
}
TEST_F(PrimOpTest, filter) {
auto v = eval("builtins.filter (x: x == 2) [ 3 2 3 2 3 2 ]");
ASSERT_THAT(v, IsListOfSize(3));
for (const auto elem : v.listItems())
ASSERT_THAT(*elem, IsIntEq(2));
ASSERT_THAT(elem, IsIntEq(2));
}
TEST_F(PrimOpTest, elemTrue) {
@@ -335,7 +335,7 @@ namespace nix {
auto v = eval("builtins.concatLists [[1 2] [3 4]]");
ASSERT_THAT(v, IsListOfSize(4));
for (const auto [i, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsIntEq(static_cast<int>(i)+1));
ASSERT_THAT(elem, IsIntEq(static_cast<int>(i) + 1));
}
TEST_F(PrimOpTest, length) {
@@ -373,9 +373,9 @@ namespace nix {
ASSERT_EQ(v.type(), nList);
ASSERT_EQ(v.listSize(), 3);
for (const auto [i, elem] : enumerate(v.listItems())) {
ASSERT_THAT(*elem, IsThunk());
state.forceValue(*elem, noPos);
ASSERT_THAT(*elem, IsIntEq(static_cast<int>(i)+1));
ASSERT_THAT(elem, IsThunk());
state.forceValue(elem, noPos);
ASSERT_THAT(elem, IsIntEq(static_cast<int>(i) + 1));
}
}
@@ -386,27 +386,27 @@ namespace nix {
const std::vector<int> numbers = { 42, 77, 147, 249, 483, 526 };
for (const auto [n, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsIntEq(numbers[n]));
ASSERT_THAT(elem, IsIntEq(numbers[n]));
}
TEST_F(PrimOpTest, partition) {
auto v = eval("builtins.partition (x: x > 10) [1 23 9 3 42]");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto right = v.attrs->get(createSymbol("right"));
auto right = v.attrs()->get(createSymbol("right"));
ASSERT_NE(right, nullptr);
ASSERT_THAT(*right->value, IsListOfSize(2));
ASSERT_THAT(*right->value->listElems()[0], IsIntEq(23));
ASSERT_THAT(*right->value->listElems()[1], IsIntEq(42));
ASSERT_THAT(right->value, IsListOfSize(2));
ASSERT_THAT(right->value.listElems()[0], IsIntEq(23));
ASSERT_THAT(right->value.listElems()[1], IsIntEq(42));
auto wrong = v.attrs->get(createSymbol("wrong"));
auto wrong = v.attrs()->get(createSymbol("wrong"));
ASSERT_NE(wrong, nullptr);
ASSERT_EQ(wrong->value->type(), nList);
ASSERT_EQ(wrong->value->listSize(), 3);
ASSERT_THAT(*wrong->value, IsListOfSize(3));
ASSERT_THAT(*wrong->value->listElems()[0], IsIntEq(1));
ASSERT_THAT(*wrong->value->listElems()[1], IsIntEq(9));
ASSERT_THAT(*wrong->value->listElems()[2], IsIntEq(3));
ASSERT_EQ(wrong->value.type(), nList);
ASSERT_EQ(wrong->value.listSize(), 3);
ASSERT_THAT(wrong->value, IsListOfSize(3));
ASSERT_THAT(wrong->value.listElems()[0], IsIntEq(1));
ASSERT_THAT(wrong->value.listElems()[1], IsIntEq(9));
ASSERT_THAT(wrong->value.listElems()[2], IsIntEq(3));
}
TEST_F(PrimOpTest, concatMap) {
@@ -416,7 +416,7 @@ namespace nix {
const std::vector<int> numbers = { 1, 2, 0, 3, 4, 0 };
for (const auto [n, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsIntEq(numbers[n]));
ASSERT_THAT(elem, IsIntEq(numbers[n]));
}
TEST_F(PrimOpTest, addInt) {
@@ -636,14 +636,14 @@ namespace nix {
auto v = eval("derivation");
ASSERT_EQ(v.type(), nFunction);
ASSERT_TRUE(v.isLambda());
ASSERT_NE(v.lambda.fun, nullptr);
ASSERT_TRUE(dynamic_cast<AttrsPattern *>(v.lambda.fun->pattern.get()));
ASSERT_NE(v.lambda().fun, nullptr);
ASSERT_TRUE(dynamic_cast<AttrsPattern *>(v.lambda().fun->pattern.get()));
}
TEST_F(PrimOpTest, currentTime) {
auto v = eval("builtins.currentTime");
ASSERT_EQ(v.type(), nInt);
ASSERT_TRUE(v.integer > 0);
ASSERT_TRUE(v.integer() > 0);
}
TEST_F(PrimOpTest, splitVersion) {
@@ -652,7 +652,7 @@ namespace nix {
const std::vector<std::string_view> strings = { "1", "2", "3", "git" };
for (const auto [n, p] : enumerate(v.listItems()))
ASSERT_THAT(*p, IsStringEq(strings[n]));
ASSERT_THAT(p, IsStringEq(strings[n]));
}
class CompareVersionsPrimOpTest :
@@ -704,13 +704,13 @@ namespace nix {
auto v = eval(expr);
ASSERT_THAT(v, IsAttrsOfSize(2));
auto name = v.attrs->get(createSymbol("name"));
auto name = v.attrs()->get(createSymbol("name"));
ASSERT_TRUE(name);
ASSERT_THAT(*name->value, IsStringEq(expectedName));
ASSERT_THAT(name->value, IsStringEq(expectedName));
auto version = v.attrs->get(createSymbol("version"));
auto version = v.attrs()->get(createSymbol("version"));
ASSERT_TRUE(version);
ASSERT_THAT(*version->value, IsStringEq(expectedVersion));
ASSERT_THAT(version->value, IsStringEq(expectedVersion));
}
INSTANTIATE_TEST_SUITE_P(
@@ -741,12 +741,12 @@ namespace nix {
auto v = eval("builtins.split \"(a)b\" \"abc\"");
ASSERT_THAT(v, IsListOfSize(3));
ASSERT_THAT(*v.listElems()[0], IsStringEq(""));
ASSERT_THAT(v.listElems()[0], IsStringEq(""));
ASSERT_THAT(*v.listElems()[1], IsListOfSize(1));
ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a"));
ASSERT_THAT(v.listElems()[1], IsListOfSize(1));
ASSERT_THAT(v.listElems()[1].listElems()[0], IsStringEq("a"));
ASSERT_THAT(*v.listElems()[2], IsStringEq("c"));
ASSERT_THAT(v.listElems()[2], IsStringEq("c"));
}
TEST_F(PrimOpTest, split2) {
@@ -754,17 +754,17 @@ namespace nix {
auto v = eval("builtins.split \"([ac])\" \"abc\"");
ASSERT_THAT(v, IsListOfSize(5));
ASSERT_THAT(*v.listElems()[0], IsStringEq(""));
ASSERT_THAT(v.listElems()[0], IsStringEq(""));
ASSERT_THAT(*v.listElems()[1], IsListOfSize(1));
ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a"));
ASSERT_THAT(v.listElems()[1], IsListOfSize(1));
ASSERT_THAT(v.listElems()[1].listElems()[0], IsStringEq("a"));
ASSERT_THAT(*v.listElems()[2], IsStringEq("b"));
ASSERT_THAT(v.listElems()[2], IsStringEq("b"));
ASSERT_THAT(*v.listElems()[3], IsListOfSize(1));
ASSERT_THAT(*v.listElems()[3]->listElems()[0], IsStringEq("c"));
ASSERT_THAT(v.listElems()[3], IsListOfSize(1));
ASSERT_THAT(v.listElems()[3].listElems()[0], IsStringEq("c"));
ASSERT_THAT(*v.listElems()[4], IsStringEq(""));
ASSERT_THAT(v.listElems()[4], IsStringEq(""));
}
TEST_F(PrimOpTest, split3) {
@@ -772,23 +772,23 @@ namespace nix {
ASSERT_THAT(v, IsListOfSize(5));
// First list element
ASSERT_THAT(*v.listElems()[0], IsStringEq(""));
ASSERT_THAT(v.listElems()[0], IsStringEq(""));
// 2nd list element is a list [ "" null ]
ASSERT_THAT(*v.listElems()[1], IsListOfSize(2));
ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a"));
ASSERT_THAT(*v.listElems()[1]->listElems()[1], IsNull());
ASSERT_THAT(v.listElems()[1], IsListOfSize(2));
ASSERT_THAT(v.listElems()[1].listElems()[0], IsStringEq("a"));
ASSERT_THAT(v.listElems()[1].listElems()[1], IsNull());
// 3rd element
ASSERT_THAT(*v.listElems()[2], IsStringEq("b"));
ASSERT_THAT(v.listElems()[2], IsStringEq("b"));
// 4th element is a list: [ null "c" ]
ASSERT_THAT(*v.listElems()[3], IsListOfSize(2));
ASSERT_THAT(*v.listElems()[3]->listElems()[0], IsNull());
ASSERT_THAT(*v.listElems()[3]->listElems()[1], IsStringEq("c"));
ASSERT_THAT(v.listElems()[3], IsListOfSize(2));
ASSERT_THAT(v.listElems()[3].listElems()[0], IsNull());
ASSERT_THAT(v.listElems()[3].listElems()[1], IsStringEq("c"));
// 5th element is the empty string
ASSERT_THAT(*v.listElems()[4], IsStringEq(""));
ASSERT_THAT(v.listElems()[4], IsStringEq(""));
}
TEST_F(PrimOpTest, split4) {
@@ -798,12 +798,12 @@ namespace nix {
auto second = v.listElems()[1];
auto third = v.listElems()[2];
ASSERT_THAT(*first, IsStringEq(" "));
ASSERT_THAT(first, IsStringEq(" "));
ASSERT_THAT(*second, IsListOfSize(1));
ASSERT_THAT(*second->listElems()[0], IsStringEq("FOO"));
ASSERT_THAT(second, IsListOfSize(1));
ASSERT_THAT(second.listElems()[0], IsStringEq("FOO"));
ASSERT_THAT(*third, IsStringEq(" "));
ASSERT_THAT(third, IsStringEq(" "));
}
TEST_F(PrimOpTest, match1) {
@@ -819,14 +819,14 @@ namespace nix {
TEST_F(PrimOpTest, match3) {
auto v = eval("builtins.match \"a(b)(c)\" \"abc\"");
ASSERT_THAT(v, IsListOfSize(2));
ASSERT_THAT(*v.listElems()[0], IsStringEq("b"));
ASSERT_THAT(*v.listElems()[1], IsStringEq("c"));
ASSERT_THAT(v.listElems()[0], IsStringEq("b"));
ASSERT_THAT(v.listElems()[1], IsStringEq("c"));
}
TEST_F(PrimOpTest, match4) {
auto v = eval("builtins.match \"[[:space:]]+([[:upper:]]+)[[:space:]]+\" \" FOO \"");
ASSERT_THAT(v, IsListOfSize(1));
ASSERT_THAT(*v.listElems()[0], IsStringEq("FOO"));
ASSERT_THAT(v.listElems()[0], IsStringEq("FOO"));
}
TEST_F(PrimOpTest, attrNames) {
@@ -836,7 +836,7 @@ namespace nix {
// ensure that the list is sorted
const std::vector<std::string_view> expected { "a", "x", "y", "z" };
for (const auto [n, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsStringEq(expected[n]));
ASSERT_THAT(elem, IsStringEq(expected[n]));
}
TEST_F(PrimOpTest, genericClosure_not_strict) {
+14 -14
View File
@@ -67,13 +67,13 @@ namespace nix {
TEST_F(TrivialExpressionTest, updateAttrs) {
auto v = eval("{ a = 1; } // { b = 2; a = 3; }");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto a = v.attrs->get(createSymbol("a"));
auto a = v.attrs()->get(createSymbol("a"));
ASSERT_NE(a, nullptr);
ASSERT_THAT(*a->value, IsIntEq(3));
ASSERT_THAT(a->value, IsIntEq(3));
auto b = v.attrs->get(createSymbol("b"));
auto b = v.attrs()->get(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(2));
ASSERT_THAT(b->value, IsIntEq(2));
}
TEST_F(TrivialExpressionTest, hasAttrOpFalse) {
@@ -168,21 +168,21 @@ namespace nix {
auto v = eval(expr);
ASSERT_THAT(v, IsAttrsOfSize(1));
auto a = v.attrs->get(createSymbol("a"));
auto a = v.attrs()->get(createSymbol("a"));
ASSERT_NE(a, nullptr);
ASSERT_THAT(*a->value, IsThunk());
state.forceValue(*a->value, noPos);
ASSERT_THAT(a->value, IsThunk());
state.forceValue(a->value, noPos);
ASSERT_THAT(*a->value, IsAttrsOfSize(2));
ASSERT_THAT(a->value, IsAttrsOfSize(2));
auto b = a->value->attrs->get(createSymbol("b"));
auto b = a->value.attrs()->get(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(1));
ASSERT_THAT(b->value, IsIntEq(1));
auto c = a->value->attrs->get(createSymbol("c"));
auto c = a->value.attrs()->get(createSymbol("c"));
ASSERT_NE(c, nullptr);
ASSERT_THAT(*c->value, IsIntEq(2));
ASSERT_THAT(c->value, IsIntEq(2));
}
INSTANTIATE_TEST_SUITE_P(
@@ -202,9 +202,9 @@ namespace nix {
TEST_F(TrivialExpressionTest, bindOr) {
auto v = eval("{ or = 1; }");
ASSERT_THAT(v, IsAttrsOfSize(1));
auto b = v.attrs->get(createSymbol("or"));
auto b = v.attrs()->get(createSymbol("or"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(1));
ASSERT_THAT(b->value, IsIntEq(1));
}
TEST_F(TrivialExpressionTest, orCantBeUsed) {
+116 -115
View File
@@ -1,3 +1,4 @@
#include "lix/libexpr/nixexpr.hh"
#include "lix/libutil/canon-path.hh"
#include "lix/libutil/source-path.hh"
#include "lix/libutil/terminal.hh"
@@ -65,8 +66,8 @@ TEST_F(ValuePrintingTests, tAttrs)
vTwo.mkInt(2);
BindingsBuilder builder = evaluator.buildBindings(10);
builder.insert(evaluator.symbols.create("one"), &vOne);
builder.insert(evaluator.symbols.create("two"), &vTwo);
builder.insert(evaluator.symbols.create("one"), vOne);
builder.insert(evaluator.symbols.create("two"), vTwo);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
@@ -82,33 +83,36 @@ TEST_F(ValuePrintingTests, tList)
Value vTwo;
vTwo.mkInt(2);
Value vList = evaluator.mem.newList(5);
vList.bigList.elems[0] = &vOne;
vList.bigList.elems[1] = &vTwo;
vList.bigList.size = 3;
auto vList = evaluator.mem.newList(5);
vList->elems[0] = vOne;
vList->elems[1] = vTwo;
vList->size = 3;
test(vList, "[ 1 2 «nullptr» ]");
test(Value(NewValueAs::list, vList), "[ 1 2 «invalid» ]");
}
TEST_F(ValuePrintingTests, vThunk)
{
Value vThunk;
ExprLiteral e(noPos, NewValueAs::integer, 0);
vThunk.mkThunk(nullptr, e);
EvalMemory mem;
Env env;
ExprInt e(noPos, 0);
Value vThunk{NewValueAs::thunk, mem, env, e};
test(vThunk, "«thunk»");
}
TEST_F(ValuePrintingTests, vApp)
{
Value vApp;
vApp.mkApp(nullptr, nullptr);
EvalMemory mem;
Value vFn{NewValueAs::null};
Value vApp{NewValueAs::app, mem, vFn, vFn};
test(vApp, "«thunk»");
}
TEST_F(ValuePrintingTests, vLambda)
{
EvalMemory mem;
Env env {
.up = nullptr,
.values = { }
@@ -116,11 +120,12 @@ TEST_F(ValuePrintingTests, vLambda)
PosTable::Origin origin = evaluator.positions.addOrigin(std::monostate(), 1);
auto posIdx = evaluator.positions.add(origin, 0);
ExprLambda eLambda(posIdx, std::make_unique<AttrsPattern>(), std::make_unique<ExprLiteral>(noPos, NewValueAs::integer, 0));
ExprLambda eLambda(
posIdx, std::make_unique<AttrsPattern>(), std::make_unique<ExprInt>(noPos, 0)
);
eLambda.pattern->name = createSymbol("a");
Value vLambda;
vLambda.mkLambda(&env, &eLambda);
Value vLambda{NewValueAs::lambda, mem, env, eLambda};
test(vLambda, "«lambda @ «none»:1:1»");
@@ -132,9 +137,7 @@ TEST_F(ValuePrintingTests, vLambda)
TEST_F(ValuePrintingTests, vPrimOp)
{
Value vPrimOp;
PrimOp primOp{
.name = "puppy"
};
PrimOp primOp{{.name = "puppy"}};
vPrimOp.mkPrimOp(&primOp);
test(vPrimOp, "«primop puppy»");
@@ -142,14 +145,12 @@ TEST_F(ValuePrintingTests, vPrimOp)
TEST_F(ValuePrintingTests, vPrimOpApp)
{
PrimOp primOp{
.name = "puppy"
};
EvalMemory mem;
PrimOp primOp{{.name = "puppy"}};
Value vPrimOp;
vPrimOp.mkPrimOp(&primOp);
Value vPrimOpApp;
vPrimOpApp.mkPrimOpApp(&vPrimOp, nullptr);
Value vPrimOpApp{NewValueAs::app, mem, vPrimOp, vPrimOp};
test(vPrimOpApp, "«partially applied primop puppy»");
}
@@ -189,8 +190,7 @@ TEST_F(ValuePrintingTests, vFloat)
TEST_F(ValuePrintingTests, vBlackhole)
{
Value vBlackhole;
vBlackhole.mkBlackhole();
Value vBlackhole{NewValueAs::blackhole};
test(vBlackhole, "«potential infinite recursion»");
}
@@ -210,23 +210,23 @@ TEST_F(ValuePrintingTests, depthAttrs)
vAttrsEmpty.mkAttrs(builderEmpty.finish());
BindingsBuilder builderNested = evaluator.buildBindings(1);
builderNested.insert(evaluator.symbols.create("zero"), &vZero);
builderNested.insert(evaluator.symbols.create("zero"), vZero);
Value vAttrsNested;
vAttrsNested.mkAttrs(builderNested.finish());
BindingsBuilder builder = evaluator.buildBindings(10);
builder.insert(evaluator.symbols.create("one"), &vOne);
builder.insert(evaluator.symbols.create("two"), &vTwo);
builder.insert(evaluator.symbols.create("empty"), &vAttrsEmpty);
builder.insert(evaluator.symbols.create("nested"), &vAttrsNested);
builder.insert(evaluator.symbols.create("one"), vOne);
builder.insert(evaluator.symbols.create("two"), vTwo);
builder.insert(evaluator.symbols.create("empty"), vAttrsEmpty);
builder.insert(evaluator.symbols.create("nested"), vAttrsNested);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
BindingsBuilder builder2 = evaluator.buildBindings(10);
builder2.insert(evaluator.symbols.create("one"), &vOne);
builder2.insert(evaluator.symbols.create("two"), &vTwo);
builder2.insert(evaluator.symbols.create("nested"), &vAttrs);
builder2.insert(evaluator.symbols.create("one"), vOne);
builder2.insert(evaluator.symbols.create("two"), vTwo);
builder2.insert(evaluator.symbols.create("nested"), vAttrs);
Value vNested;
vNested.mkAttrs(builder2.finish());
@@ -246,26 +246,27 @@ TEST_F(ValuePrintingTests, depthList)
vTwo.mkInt(2);
BindingsBuilder builder = evaluator.buildBindings(10);
builder.insert(evaluator.symbols.create("one"), &vOne);
builder.insert(evaluator.symbols.create("two"), &vTwo);
builder.insert(evaluator.symbols.create("one"), vOne);
builder.insert(evaluator.symbols.create("two"), vTwo);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
BindingsBuilder builder2 = evaluator.buildBindings(10);
builder2.insert(evaluator.symbols.create("one"), &vOne);
builder2.insert(evaluator.symbols.create("two"), &vTwo);
builder2.insert(evaluator.symbols.create("nested"), &vAttrs);
builder2.insert(evaluator.symbols.create("one"), vOne);
builder2.insert(evaluator.symbols.create("two"), vTwo);
builder2.insert(evaluator.symbols.create("nested"), vAttrs);
Value vNested;
vNested.mkAttrs(builder2.finish());
Value vList = evaluator.mem.newList(5);
vList.bigList.elems[0] = &vOne;
vList.bigList.elems[1] = &vTwo;
vList.bigList.elems[2] = &vNested;
vList.bigList.size = 3;
auto list = evaluator.mem.newList(5);
list->elems[0] = vOne;
list->elems[1] = vTwo;
list->elems[2] = vNested;
list->size = 3;
Value vList{NewValueAs::list, list};
test(vList, "[ 1 2 { ... } ]", PrintOptions { .maxDepth = 1 });
test(vList, "[ 1 2 { nested = { ... }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 2 });
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 3 });
@@ -309,8 +310,8 @@ TEST_F(ValuePrintingTests, attrsTypeFirst)
vApple.mkString("apple");
BindingsBuilder builder = evaluator.buildBindings(10);
builder.insert(evaluator.symbols.create("type"), &vType);
builder.insert(evaluator.symbols.create("apple"), &vApple);
builder.insert(evaluator.symbols.create("type"), vType);
builder.insert(evaluator.symbols.create("apple"), vApple);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
@@ -420,8 +421,8 @@ TEST_F(ValuePrintingTests, ansiColorsAttrs)
vTwo.mkInt(2);
BindingsBuilder builder = evaluator.buildBindings(10);
builder.insert(evaluator.symbols.create("one"), &vOne);
builder.insert(evaluator.symbols.create("two"), &vTwo);
builder.insert(evaluator.symbols.create("one"), vOne);
builder.insert(evaluator.symbols.create("two"), vTwo);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
@@ -439,7 +440,7 @@ TEST_F(ValuePrintingTests, ansiColorsDerivation)
vDerivation.mkString("derivation");
BindingsBuilder builder = evaluator.buildBindings(10);
builder.insert(evaluator.s.type, &vDerivation);
builder.insert(evaluator.s.type, vDerivation);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
@@ -466,14 +467,14 @@ TEST_F(ValuePrintingTests, ansiColorsError)
auto & e = evaluator.parseExprFromString("{ a = throw \"uh oh!\"; }", {CanonPath::root});
state.eval(e, vError);
test(*vError.attrs->begin()->value,
ANSI_RED
"«error: uh oh!»"
ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true,
});
test(
vError.attrs()->begin()->value,
ANSI_RED "«error: uh oh!»" ANSI_NORMAL,
PrintOptions{
.ansiColors = true,
.force = true,
}
);
}
TEST_F(ValuePrintingTests, ansiColorsDerivationError)
@@ -517,12 +518,11 @@ TEST_F(ValuePrintingTests, ansiColorsAssert)
state.eval(e, v);
ASSERT_EQ(v.type(), nAttrs);
test(*v.attrs->begin()->value,
ANSI_RED "«error: assertion failed»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true
});
test(
v.attrs()->begin()->value,
ANSI_RED "«error: assertion failed»" ANSI_NORMAL,
PrintOptions{.ansiColors = true, .force = true}
);
}
TEST_F(ValuePrintingTests, ansiColorsList)
@@ -533,20 +533,22 @@ TEST_F(ValuePrintingTests, ansiColorsList)
Value vTwo;
vTwo.mkInt(2);
Value vList = evaluator.mem.newList(5);
vList.bigList.elems[0] = &vOne;
vList.bigList.elems[1] = &vTwo;
vList.bigList.size = 3;
auto vList = evaluator.mem.newList(5);
vList->elems[0] = vOne;
vList->elems[1] = vTwo;
vList->size = 3;
test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_CYAN "2" ANSI_NORMAL " " ANSI_MAGENTA "«nullptr»" ANSI_NORMAL " ]",
PrintOptions {
.ansiColors = true
});
test(
Value(NewValueAs::list, vList),
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_CYAN "2" ANSI_NORMAL " " ANSI_MAGENTA
"«invalid»" ANSI_NORMAL " ]",
PrintOptions{.ansiColors = true}
);
}
TEST_F(ValuePrintingTests, ansiColorsLambda)
{
EvalMemory mem;
Env env {
.up = nullptr,
.values = { }
@@ -554,11 +556,12 @@ TEST_F(ValuePrintingTests, ansiColorsLambda)
PosTable::Origin origin = evaluator.positions.addOrigin(std::monostate(), 1);
auto posIdx = evaluator.positions.add(origin, 0);
ExprLambda eLambda(posIdx, std::make_unique<AttrsPattern>(), std::make_unique<ExprLiteral>(noPos, NewValueAs::integer, 0));
ExprLambda eLambda(
posIdx, std::make_unique<AttrsPattern>(), std::make_unique<ExprInt>(noPos, 0)
);
eLambda.pattern->name = createSymbol("a");
Value vLambda;
vLambda.mkLambda(&env, &eLambda);
Value vLambda{NewValueAs::lambda, mem, env, eLambda};
test(vLambda,
ANSI_BLUE "«lambda @ «none»:1:1»" ANSI_NORMAL,
@@ -579,9 +582,7 @@ TEST_F(ValuePrintingTests, ansiColorsLambda)
TEST_F(ValuePrintingTests, ansiColorsPrimOp)
{
PrimOp primOp{
.name = "puppy"
};
PrimOp primOp{{.name = "puppy"}};
Value v;
v.mkPrimOp(&primOp);
@@ -594,14 +595,12 @@ TEST_F(ValuePrintingTests, ansiColorsPrimOp)
TEST_F(ValuePrintingTests, ansiColorsPrimOpApp)
{
PrimOp primOp{
.name = "puppy"
};
EvalMemory mem;
PrimOp primOp{{.name = "puppy"}};
Value vPrimOp;
vPrimOp.mkPrimOp(&primOp);
Value v;
v.mkPrimOpApp(&vPrimOp, nullptr);
Value v{NewValueAs::app, mem, vPrimOp, vPrimOp};
test(v,
ANSI_BLUE "«partially applied primop puppy»" ANSI_NORMAL,
@@ -612,9 +611,10 @@ TEST_F(ValuePrintingTests, ansiColorsPrimOpApp)
TEST_F(ValuePrintingTests, ansiColorsThunk)
{
Value v;
ExprLiteral e(noPos, NewValueAs::integer, 0);
v.mkThunk(nullptr, e);
EvalMemory mem;
Env env;
ExprInt e(noPos, 0);
Value v{NewValueAs::thunk, mem, env, e};
test(v,
ANSI_MAGENTA "«thunk»" ANSI_NORMAL,
@@ -625,8 +625,7 @@ TEST_F(ValuePrintingTests, ansiColorsThunk)
TEST_F(ValuePrintingTests, ansiColorsBlackhole)
{
Value v;
v.mkBlackhole();
Value v{NewValueAs::blackhole};
test(v,
ANSI_RED "«potential infinite recursion»" ANSI_NORMAL,
@@ -641,14 +640,14 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsRepeated)
vZero.mkInt(0);
BindingsBuilder innerBuilder = evaluator.buildBindings(1);
innerBuilder.insert(evaluator.symbols.create("x"), &vZero);
innerBuilder.insert(evaluator.symbols.create("x"), vZero);
Value vInner;
vInner.mkAttrs(innerBuilder.finish());
BindingsBuilder builder = evaluator.buildBindings(10);
builder.insert(evaluator.symbols.create("a"), &vInner);
builder.insert(evaluator.symbols.create("b"), &vInner);
builder.insert(evaluator.symbols.create("a"), vInner);
builder.insert(evaluator.symbols.create("b"), vInner);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
@@ -666,21 +665,21 @@ TEST_F(ValuePrintingTests, ansiColorsListRepeated)
vZero.mkInt(0);
BindingsBuilder innerBuilder = evaluator.buildBindings(1);
innerBuilder.insert(evaluator.symbols.create("x"), &vZero);
innerBuilder.insert(evaluator.symbols.create("x"), vZero);
Value vInner;
vInner.mkAttrs(innerBuilder.finish());
Value vList = evaluator.mem.newList(3);
vList.bigList.elems[0] = &vInner;
vList.bigList.elems[1] = &vInner;
vList.bigList.size = 2;
auto vList = evaluator.mem.newList(3);
vList->elems[0] = vInner;
vList->elems[1] = vInner;
vList->size = 2;
test(vList,
"[ { x = " ANSI_CYAN "0" ANSI_NORMAL "; } " ANSI_MAGENTA "«repeated»" ANSI_NORMAL " ]",
PrintOptions {
.ansiColors = true
});
test(
Value(NewValueAs::list, vList),
"[ { x = " ANSI_CYAN "0" ANSI_NORMAL "; } " ANSI_MAGENTA "«repeated»" ANSI_NORMAL " ]",
PrintOptions{.ansiColors = true}
);
}
TEST_F(ValuePrintingTests, listRepeated)
@@ -689,16 +688,17 @@ TEST_F(ValuePrintingTests, listRepeated)
vZero.mkInt(0);
BindingsBuilder innerBuilder = evaluator.buildBindings(1);
innerBuilder.insert(evaluator.symbols.create("x"), &vZero);
innerBuilder.insert(evaluator.symbols.create("x"), vZero);
Value vInner;
vInner.mkAttrs(innerBuilder.finish());
Value vList = evaluator.mem.newList(3);
vList.bigList.elems[0] = &vInner;
vList.bigList.elems[1] = &vInner;
vList.bigList.size = 2;
auto list = evaluator.mem.newList(3);
list->elems[0] = vInner;
list->elems[1] = vInner;
list->size = 2;
Value vList(NewValueAs::list, list);
test(vList, "[ { x = 0; } «repeated» ]", PrintOptions { });
test(vList,
"[ { x = 0; } { x = 0; } ]",
@@ -716,8 +716,8 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
vTwo.mkInt(2);
BindingsBuilder builder = evaluator.buildBindings(10);
builder.insert(evaluator.symbols.create("one"), &vOne);
builder.insert(evaluator.symbols.create("two"), &vTwo);
builder.insert(evaluator.symbols.create("one"), vOne);
builder.insert(evaluator.symbols.create("two"), vTwo);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
@@ -732,7 +732,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
Value vThree;
vThree.mkInt(3);
builder.insert(evaluator.symbols.create("three"), &vThree);
builder.insert(evaluator.symbols.create("three"), vThree);
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
@@ -751,10 +751,11 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
Value vTwo;
vTwo.mkInt(2);
Value vList = evaluator.mem.newList(4);
vList.bigList.elems[0] = &vOne;
vList.bigList.elems[1] = &vTwo;
vList.bigList.size = 2;
auto list = evaluator.mem.newList(4);
Value vList{NewValueAs::list, list};
list->elems[0] = vOne;
list->elems[1] = vTwo;
list->size = 2;
test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«1 item elided»" ANSI_NORMAL " ]",
@@ -766,8 +767,8 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
Value vThree;
vThree.mkInt(3);
vList.bigList.elems[2] = &vThree;
vList.bigList.size = 3;
list->elems[2] = vThree;
list->size = 3;
test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«2 items elided»" ANSI_NORMAL " ]",
@@ -786,7 +787,7 @@ TEST_F(ValuePrintingTests, osc8InAttrSets)
auto vZero = Value{NewValueAs::integer, NixInt{0}};
builder.insert(evaluator.symbols.create("x"), &vZero, pos);
builder.insert(evaluator.symbols.create("x"), vZero, pos);
auto vAttrs = Value{NewValueAs::attrs, builder.finish()};
auto hyperlink = makeHyperlink("x", makeHyperlinkLocalPath("/dev/null", 1));