1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-09 18:41:03 +01:00

Merge commit 'b24757f08a' into sync-2.24.2

This commit is contained in:
Eelco Dolstra 2024-08-08 15:34:12 +02:00
commit c1d27763c6
330 changed files with 4907 additions and 1814 deletions

View file

@ -134,7 +134,7 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
return {SourcePath{path.accessor, CanonPath(fn.substr(0, colon))}, lineno};
} catch (std::invalid_argument & e) {
fail();
abort();
unreachable();
}
}

View file

@ -95,7 +95,7 @@ struct AttrDb
{
try {
auto state(_state->lock());
if (!failed)
if (!failed && state->txn->active)
state->txn->commit();
state->txn.reset();
} catch (...) {

View file

@ -92,6 +92,14 @@ void EvalErrorBuilder<T>::debugThrow()
throw error;
}
template<class T>
void EvalErrorBuilder<T>::panic()
{
logError(error.info());
printError("This is a bug! An unexpected condition occurred, causing the Nix evaluator to have to stop. If you could share a reproducible example or a core dump, please open an issue at https://github.com/NixOS/nix/issues");
abort();
}
template class EvalErrorBuilder<EvalBaseError>;
template class EvalErrorBuilder<EvalError>;
template class EvalErrorBuilder<AssertionError>;

View file

@ -110,6 +110,12 @@ public:
* Delete the `EvalErrorBuilder` and throw the underlying exception.
*/
[[gnu::noinline, gnu::noreturn]] void debugThrow();
/**
* A programming error or fatal condition occurred. Abort the process for core dump and debugging.
* This does not print a proper backtrace, because unwinding the stack is destructive.
*/
[[gnu::noinline, gnu::noreturn]] void panic();
};
}

View file

@ -1,5 +1,7 @@
#include "error.hh"
#include "environment-variables.hh"
#include "eval-settings.hh"
#include "config-global.hh"
#include "serialise.hh"
#include "eval-gc.hh"
@ -84,14 +86,17 @@ void fixupBoehmStackPointer(void ** sp_ptr, void * _pthread_id)
{
void *& sp = *sp_ptr;
auto pthread_id = reinterpret_cast<pthread_t>(_pthread_id);
# ifndef __APPLE__
pthread_attr_t pattr;
# endif
size_t osStackSize;
void * osStackLow;
// The low address of the stack, which grows down.
void * osStackLimit;
void * osStackBase;
# ifdef __APPLE__
osStackSize = pthread_get_stacksize_np(pthread_id);
osStackLow = pthread_get_stackaddr_np(pthread_id);
osStackLimit = pthread_get_stackaddr_np(pthread_id);
# else
if (pthread_attr_init(&pattr)) {
throw Error("fixupBoehmStackPointer: pthread_attr_init failed");
@ -110,18 +115,18 @@ void fixupBoehmStackPointer(void ** sp_ptr, void * _pthread_id)
# else
# error "Need one of `pthread_attr_get_np` or `pthread_getattr_np`"
# endif
if (pthread_attr_getstack(&pattr, &osStackLow, &osStackSize)) {
if (pthread_attr_getstack(&pattr, &osStackLimit, &osStackSize)) {
throw Error("fixupBoehmStackPointer: pthread_attr_getstack failed");
}
if (pthread_attr_destroy(&pattr)) {
throw Error("fixupBoehmStackPointer: pthread_attr_destroy failed");
}
# endif
osStackBase = (char *) osStackLow + osStackSize;
osStackBase = (char *) osStackLimit + osStackSize;
// NOTE: We assume the stack grows down, as it does on all architectures we support.
// Architectures that grow the stack up are rare.
if (sp >= osStackBase || sp < osStackLow) { // lo is outside the os stack
sp = osStackBase;
if (sp >= osStackBase || sp < osStackLimit) { // sp is outside the os stack
sp = osStackLimit;
}
}
@ -155,6 +160,10 @@ static inline void initGCReal()
there. */
GC_set_no_dls(1);
/* Enable perf measurements. This is just a setting; not much of a
start of something. */
GC_start_performance_measurement();
GC_INIT();
GC_set_oom_fn(oomHandler);
@ -202,6 +211,14 @@ static inline void initGCReal()
}
}
static size_t gcCyclesAfterInit = 0;
size_t getGCCycles()
{
assertGCInitialized();
return static_cast<size_t>(GC_get_gc_no()) - gcCyclesAfterInit;
}
#endif
static bool gcInitialised = false;
@ -213,8 +230,16 @@ void initGC()
#if HAVE_BOEHMGC
initGCReal();
gcCyclesAfterInit = GC_get_gc_no();
#endif
// NIX_PATH must override the regular setting
// See the comment in applyConfig
if (auto nixPathEnv = getEnv("NIX_PATH")) {
globalConfig.set("nix-path", concatStringsSep(" ", EvalSettings::parseNixPath(nixPathEnv.value())));
}
gcInitialised = true;
}
@ -223,4 +248,4 @@ void assertGCInitialized()
assert(gcInitialised);
}
}
} // namespace nix

View file

@ -1,6 +1,8 @@
#pragma once
///@file
#include <cstddef>
namespace nix {
/**
@ -13,4 +15,11 @@ void initGC();
*/
void assertGCInitialized();
}
#ifdef HAVE_BOEHMGC
/**
* The number of GC cycles since initGC().
*/
size_t getGCCycles();
#endif
} // namespace nix

View file

@ -8,7 +8,7 @@ namespace nix {
/* Very hacky way to parse $NIX_PATH, which is colon-separated, but
can contain URLs (e.g. "nixpkgs=https://bla...:foo=https://"). */
static Strings parseNixPath(const std::string & s)
Strings EvalSettings::parseNixPath(const std::string & s)
{
Strings res;
@ -48,15 +48,12 @@ EvalSettings::EvalSettings(bool & readOnlyMode, EvalSettings::LookupPathHooks lo
: readOnlyMode{readOnlyMode}
, lookupPathHooks{lookupPathHooks}
{
auto var = getEnv("NIX_PATH");
if (var) nixPath = parseNixPath(*var);
var = getEnv("NIX_ABORT_ON_WARN");
auto var = getEnv("NIX_ABORT_ON_WARN");
if (var && (var == "1" || var == "yes" || var == "true"))
builtinsAbortOnWarn = true;
}
Strings EvalSettings::getDefaultNixPath() const
Strings EvalSettings::getDefaultNixPath()
{
Strings res;
auto add = [&](const Path & p, const std::string & s = std::string()) {
@ -69,11 +66,9 @@ Strings EvalSettings::getDefaultNixPath() const
}
};
if (!restrictEval && !pureEval) {
add(getNixDefExpr() + "/channels");
add(rootChannelsDir() + "/nixpkgs", "nixpkgs");
add(rootChannelsDir());
}
add(getNixDefExpr() + "/channels");
add(rootChannelsDir() + "/nixpkgs", "nixpkgs");
add(rootChannelsDir());
return res;
}

View file

@ -43,10 +43,12 @@ struct EvalSettings : Config
bool & readOnlyMode;
Strings getDefaultNixPath() const;
static Strings getDefaultNixPath();
static bool isPseudoUrl(std::string_view s);
static Strings parseNixPath(const std::string & s);
static std::string resolvePseudoUrl(std::string_view url);
LookupPathHooks lookupPathHooks;
@ -63,7 +65,7 @@ struct EvalSettings : Config
extern "C" typedef void (*ValueInitialiser) (EvalState & state, Value & v);
```
The [Nix C++ API documentation](@docroot@/contributing/documentation.md#api-documentation) has more details on evaluator internals.
The [Nix C++ API documentation](@docroot@/development/documentation.md#api-documentation) has more details on evaluator internals.
- `builtins.exec` *arguments*
@ -71,25 +73,30 @@ struct EvalSettings : Config
)"};
Setting<Strings> nixPath{
this, getDefaultNixPath(), "nix-path",
this, {}, "nix-path",
R"(
List of search paths to use for [lookup path](@docroot@/language/constructs/lookup-path.md) resolution.
This setting determines the value of
[`builtins.nixPath`](@docroot@/language/builtins.md#builtins-nixPath) and can be used with [`builtins.findFile`](@docroot@/language/builtins.md#builtins-findFile).
The default value is
- The configuration setting is overridden by the [`NIX_PATH`](@docroot@/command-ref/env-common.md#env-NIX_PATH)
environment variable.
- `NIX_PATH` is overridden by [specifying the setting as the command line flag](@docroot@/command-ref/conf-file.md#command-line-flags) `--nix-path`.
- Any current value is extended by the [`-I` option](@docroot@/command-ref/opt-common.md#opt-I) or `--extra-nix-path`.
```
$HOME/.nix-defexpr/channels
nixpkgs=$NIX_STATE_DIR/profiles/per-user/root/channels/nixpkgs
$NIX_STATE_DIR/profiles/per-user/root/channels
```
If the respective paths are accessible, the default values are:
It can be overridden with the [`NIX_PATH` environment variable](@docroot@/command-ref/env-common.md#env-NIX_PATH) or the [`-I` command line option](@docroot@/command-ref/opt-common.md#opt-I).
- `$HOME/.nix-defexpr/channels`
- `nixpkgs=$NIX_STATE_DIR/profiles/per-user/root/channels/nixpkgs`
- `$NIX_STATE_DIR/profiles/per-user/root/channels`
See [`NIX_STATE_DIR`](@docroot@/command-ref/env-common.md#env-NIX_STATE_DIR) for details.
> **Note**
>
> If [pure evaluation](#conf-pure-eval) is enabled, `nixPath` evaluates to the empty list `[ ]`.
> If [restricted evaluation](@docroot@/command-ref/conf-file.md#conf-restrict-eval) is enabled, the default value is empty.
>
> If [pure evaluation](#conf-pure-eval) is enabled, `builtins.nixPath` *always* evaluates to the empty list `[ ]`.
)", {}, false};
Setting<std::string> currentSystem{

View file

@ -149,7 +149,7 @@ std::string_view showType(ValueType type, bool withArticle)
case nFloat: return WA("a", "float");
case nThunk: return WA("a", "thunk");
}
abort();
unreachable();
}
@ -215,7 +215,7 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
static constexpr size_t BASE_ENV_SIZE = 128;
EvalState::EvalState(
const LookupPath & _lookupPath,
const LookupPath & lookupPathFromArguments,
ref<Store> store,
const fetchers::Settings & fetchSettings,
const EvalSettings & settings,
@ -331,12 +331,21 @@ EvalState::EvalState(
vStringSymlink.mkString("symlink");
vStringUnknown.mkString("unknown");
/* Initialise the Nix expression search path. */
/* Construct the Nix expression search path. */
assert(lookupPath.elements.empty());
if (!settings.pureEval) {
for (auto & i : _lookupPath.elements)
for (auto & i : lookupPathFromArguments.elements) {
lookupPath.elements.emplace_back(LookupPath::Elem {i});
for (auto & i : settings.nixPath.get())
}
/* $NIX_PATH overriding regular settings is implemented as a hack in `initGC()` */
for (auto & i : settings.nixPath.get()) {
lookupPath.elements.emplace_back(LookupPath::Elem::parse(i));
}
if (!settings.restrictEval) {
for (auto & i : EvalSettings::getDefaultNixPath()) {
lookupPath.elements.emplace_back(LookupPath::Elem::parse(i));
}
}
}
/* Allow access to all paths in the search path. */
@ -771,7 +780,7 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
case ReplExitStatus::Continue:
break;
default:
abort();
unreachable();
}
}
}
@ -1140,7 +1149,7 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx po
void Expr::eval(EvalState & state, Env & env, Value & v)
{
abort();
unreachable();
}
@ -1573,7 +1582,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
.withFrame(*fun.payload.lambda.env, lambda)
.debugThrow();
}
abort(); // can't happen
unreachable();
}
}
@ -1825,9 +1834,24 @@ void ExprIf::eval(EvalState & state, Env & env, Value & v)
void ExprAssert::eval(EvalState & state, Env & env, Value & v)
{
if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
std::ostringstream out;
cond->show(state.symbols, out);
state.error<AssertionError>("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow();
auto exprStr = ({
std::ostringstream out;
cond->show(state.symbols, out);
out.str();
});
if (auto eq = dynamic_cast<ExprOpEq *>(cond)) {
try {
Value v1; eq->e1->eval(state, env, v1);
Value v2; eq->e2->eval(state, env, v2);
state.assertEqValues(v1, v2, eq->pos, "in an equality assertion");
} catch (AssertionError & e) {
e.addTrace(state.positions[pos], "while evaluating the condition of the assertion '%s'", exprStr);
throw;
}
}
state.error<AssertionError>("assertion '%1%' failed", exprStr).atPos(pos).withFrame(env, *this).debugThrow();
}
body->eval(state, env, v);
}
@ -2484,6 +2508,214 @@ SingleDerivedPath EvalState::coerceToSingleDerivedPath(const PosIdx pos, Value &
}
// NOTE: This implementation must match eqValues!
// We accept this burden because informative error messages for
// `assert a == b; x` are critical for our users' testing UX.
void EvalState::assertEqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx)
{
// This implementation must match eqValues.
forceValue(v1, pos);
forceValue(v2, pos);
if (&v1 == &v2)
return;
// Special case type-compatibility between float and int
if ((v1.type() == nInt || v1.type() == nFloat) && (v2.type() == nInt || v2.type() == nFloat)) {
if (eqValues(v1, v2, pos, errorCtx)) {
return;
} else {
error<AssertionError>(
"%s with value '%s' is not equal to %s with value '%s'",
showType(v1),
ValuePrinter(*this, v1, errorPrintOptions),
showType(v2),
ValuePrinter(*this, v2, errorPrintOptions))
.debugThrow();
}
}
if (v1.type() != v2.type()) {
error<AssertionError>(
"%s of value '%s' is not equal to %s of value '%s'",
showType(v1),
ValuePrinter(*this, v1, errorPrintOptions),
showType(v2),
ValuePrinter(*this, v2, errorPrintOptions))
.debugThrow();
}
switch (v1.type()) {
case nInt:
if (v1.integer() != v2.integer()) {
error<AssertionError>("integer '%d' is not equal to integer '%d'", v1.integer(), v2.integer()).debugThrow();
}
return;
case nBool:
if (v1.boolean() != v2.boolean()) {
error<AssertionError>(
"boolean '%s' is not equal to boolean '%s'",
ValuePrinter(*this, v1, errorPrintOptions),
ValuePrinter(*this, v2, errorPrintOptions))
.debugThrow();
}
return;
case nString:
if (strcmp(v1.c_str(), v2.c_str()) != 0) {
error<AssertionError>(
"string '%s' is not equal to string '%s'",
ValuePrinter(*this, v1, errorPrintOptions),
ValuePrinter(*this, v2, errorPrintOptions))
.debugThrow();
}
return;
case nPath:
if (v1.payload.path.accessor != v2.payload.path.accessor) {
error<AssertionError>(
"path '%s' is not equal to path '%s' because their accessors are different",
ValuePrinter(*this, v1, errorPrintOptions),
ValuePrinter(*this, v2, errorPrintOptions))
.debugThrow();
}
if (strcmp(v1.payload.path.path, v2.payload.path.path) != 0) {
error<AssertionError>(
"path '%s' is not equal to path '%s'",
ValuePrinter(*this, v1, errorPrintOptions),
ValuePrinter(*this, v2, errorPrintOptions))
.debugThrow();
}
return;
case nNull:
return;
case nList:
if (v1.listSize() != v2.listSize()) {
error<AssertionError>(
"list of size '%d' is not equal to list of size '%d', left hand side is '%s', right hand side is '%s'",
v1.listSize(),
v2.listSize(),
ValuePrinter(*this, v1, errorPrintOptions),
ValuePrinter(*this, v2, errorPrintOptions))
.debugThrow();
}
for (size_t n = 0; n < v1.listSize(); ++n) {
try {
assertEqValues(*v1.listElems()[n], *v2.listElems()[n], pos, errorCtx);
} catch (Error & e) {
e.addTrace(positions[pos], "while comparing list element %d", n);
throw;
}
}
return;
case nAttrs: {
if (isDerivation(v1) && isDerivation(v2)) {
auto i = v1.attrs()->get(sOutPath);
auto j = v2.attrs()->get(sOutPath);
if (i && j) {
try {
assertEqValues(*i->value, *j->value, pos, errorCtx);
return;
} catch (Error & e) {
e.addTrace(positions[pos], "while comparing a derivation by its '%s' attribute", "outPath");
throw;
}
assert(false);
}
}
if (v1.attrs()->size() != v2.attrs()->size()) {
error<AssertionError>(
"attribute names of attribute set '%s' differs from attribute set '%s'",
ValuePrinter(*this, v1, errorPrintOptions),
ValuePrinter(*this, v2, errorPrintOptions))
.debugThrow();
}
// Like normal comparison, we compare the attributes in non-deterministic Symbol index order.
// This function is called when eqValues has found a difference, so to reliably
// report about its result, we should follow in its literal footsteps and not
// try anything fancy that could lead to an error.
Bindings::const_iterator i, j;
for (i = v1.attrs()->begin(), j = v2.attrs()->begin(); i != v1.attrs()->end(); ++i, ++j) {
if (i->name != j->name) {
// A difference in a sorted list means that one attribute is not contained in the other, but we don't
// know which. Let's find out. Could use <, but this is more clear.
if (!v2.attrs()->get(i->name)) {
error<AssertionError>(
"attribute name '%s' is contained in '%s', but not in '%s'",
symbols[i->name],
ValuePrinter(*this, v1, errorPrintOptions),
ValuePrinter(*this, v2, errorPrintOptions))
.debugThrow();
}
if (!v1.attrs()->get(j->name)) {
error<AssertionError>(
"attribute name '%s' is missing in '%s', but is contained in '%s'",
symbols[j->name],
ValuePrinter(*this, v1, errorPrintOptions),
ValuePrinter(*this, v2, errorPrintOptions))
.debugThrow();
}
assert(false);
}
try {
assertEqValues(*i->value, *j->value, pos, errorCtx);
} catch (Error & e) {
// The order of traces is reversed, so this presents as
// where left hand side is
// at <pos>
// where right hand side is
// at <pos>
// while comparing attribute '<name>'
if (j->pos != noPos)
e.addTrace(positions[j->pos], "where right hand side is");
if (i->pos != noPos)
e.addTrace(positions[i->pos], "where left hand side is");
e.addTrace(positions[pos], "while comparing attribute '%s'", symbols[i->name]);
throw;
}
}
return;
}
case nFunction:
error<AssertionError>("distinct functions and immediate comparisons of identical functions compare as unequal")
.debugThrow();
case nExternal:
if (!(*v1.external() == *v2.external())) {
error<AssertionError>(
"external value '%s' is not equal to external value '%s'",
ValuePrinter(*this, v1, errorPrintOptions),
ValuePrinter(*this, v2, errorPrintOptions))
.debugThrow();
}
return;
case nFloat:
// !!!
if (!(v1.fpoint() == v2.fpoint())) {
error<AssertionError>("float '%f' is not equal to float '%f'", v1.fpoint(), v2.fpoint()).debugThrow();
}
return;
case nThunk: // Must not be left by forceValue
assert(false);
default: // Note that we pass compiler flags that should make `default:` unreachable.
// Also note that this probably ran after `eqValues`, which implements
// the same logic more efficiently (without having to unwind stacks),
// so maybe `assertEqValues` and `eqValues` are out of sync. Check it for solutions.
error<EvalError>("assertEqValues: cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).panic();
}
}
// This implementation must match assertEqValues
bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx)
{
forceValue(v1, pos);
@ -2557,11 +2789,13 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
return *v1.external() == *v2.external();
case nFloat:
// !!!
return v1.fpoint() == v2.fpoint();
case nThunk: // Must not be left by forceValue
default:
error<EvalError>("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow();
assert(false);
default: // Note that we pass compiler flags that should make `default:` unreachable.
error<EvalError>("eqValues: cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).panic();
}
}
@ -2610,6 +2844,11 @@ void EvalState::printStatistics()
#if HAVE_BOEHMGC
GC_word heapSize, totalBytes;
GC_get_heap_usage_safe(&heapSize, 0, 0, 0, &totalBytes);
double gcFullOnlyTime = ({
auto ms = GC_get_full_gc_total_time();
ms * 0.001;
});
auto gcCycles = getGCCycles();
#endif
auto outPath = getEnv("NIX_SHOW_STATS_PATH").value_or("-");
@ -2620,6 +2859,15 @@ void EvalState::printStatistics()
#ifndef _WIN32 // TODO implement
topObj["cpuTime"] = cpuTime;
#endif
topObj["time"] = {
#ifndef _WIN32 // TODO implement
{"cpu", cpuTime},
#endif
#if HAVE_BOEHMGC
{GC_is_incremental_mode() ? "gcNonIncremental" : "gc", gcFullOnlyTime},
{GC_is_incremental_mode() ? "gcNonIncrementalFraction" : "gcFraction", gcFullOnlyTime / cpuTime},
#endif
};
topObj["envs"] = {
{"number", nrEnvs},
{"elements", nrValuesInEnvs},
@ -2661,6 +2909,7 @@ void EvalState::printStatistics()
topObj["gc"] = {
{"heapSize", heapSize},
{"totalBytes", totalBytes},
{"cycles", gcCycles},
};
#endif

View file

@ -130,7 +130,7 @@ struct Constant
typedef std::map<std::string, Value *> ValMap;
#endif
typedef std::map<PosIdx, DocComment> DocCommentMap;
typedef std::unordered_map<PosIdx, DocComment> DocCommentMap;
struct Env
{
@ -335,7 +335,7 @@ private:
* Associate source positions of certain AST nodes with their preceding doc comment, if they have one.
* Grouped by file.
*/
std::map<SourcePath, DocCommentMap> positionToDocComment;
std::unordered_map<SourcePath, DocCommentMap> positionToDocComment;
LookupPath lookupPath;
@ -655,6 +655,15 @@ public:
*/
bool eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx);
/**
* Like `eqValues`, but throws an `AssertionError` if not equal.
*
* WARNING:
* Callers should call `eqValues` first and report if `assertEqValues` behaves
* incorrectly. (e.g. if it doesn't throw if eqValues returns false or vice versa)
*/
void assertEqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx);
bool isFunctor(Value & fun);
// FIXME: use std::span

View file

@ -42,7 +42,7 @@ class JSONSax : nlohmann::json_sax<json> {
auto attrs2 = state.buildBindings(attrs.size());
for (auto & i : attrs)
attrs2.insert(i.first, i.second);
parent->value(state).mkAttrs(attrs2.alreadySorted());
parent->value(state).mkAttrs(attrs2);
return std::move(parent);
}
void add() override { v = nullptr; }
@ -80,42 +80,42 @@ class JSONSax : nlohmann::json_sax<json> {
public:
JSONSax(EvalState & state, Value & v) : state(state), rs(new JSONState(&v)) {};
bool null()
bool null() override
{
rs->value(state).mkNull();
rs->add();
return true;
}
bool boolean(bool val)
bool boolean(bool val) override
{
rs->value(state).mkBool(val);
rs->add();
return true;
}
bool number_integer(number_integer_t val)
bool number_integer(number_integer_t val) override
{
rs->value(state).mkInt(val);
rs->add();
return true;
}
bool number_unsigned(number_unsigned_t val)
bool number_unsigned(number_unsigned_t val) override
{
rs->value(state).mkInt(val);
rs->add();
return true;
}
bool number_float(number_float_t val, const string_t & s)
bool number_float(number_float_t val, const string_t & s) override
{
rs->value(state).mkFloat(val);
rs->add();
return true;
}
bool string(string_t & val)
bool string(string_t & val) override
{
rs->value(state).mkString(val);
rs->add();
@ -123,7 +123,7 @@ public:
}
#if NLOHMANN_JSON_VERSION_MAJOR >= 3 && NLOHMANN_JSON_VERSION_MINOR >= 8
bool binary(binary_t&)
bool binary(binary_t&) override
{
// This function ought to be unreachable
assert(false);
@ -131,35 +131,35 @@ public:
}
#endif
bool start_object(std::size_t len)
bool start_object(std::size_t len) override
{
rs = std::make_unique<JSONObjectState>(std::move(rs));
return true;
}
bool key(string_t & name)
bool key(string_t & name) override
{
dynamic_cast<JSONObjectState*>(rs.get())->key(name, state);
return true;
}
bool end_object() {
bool end_object() override {
rs = rs->resolve(state);
rs->add();
return true;
}
bool end_array() {
bool end_array() override {
return end_object();
}
bool start_array(size_t len) {
bool start_array(size_t len) override {
rs = std::make_unique<JSONListState>(std::move(rs),
len != std::numeric_limits<size_t>::max() ? len : 128);
return true;
}
bool parse_error(std::size_t, const std::string&, const nlohmann::detail::exception& ex) {
bool parse_error(std::size_t, const std::string&, const nlohmann::detail::exception& ex) override {
throw JSONParseError("%s", ex.what());
}
};

View file

@ -67,6 +67,14 @@ static StringToken unescapeStr(SymbolTable & symbols, char * s, size_t length)
return {result, size_t(t - result)};
}
static void requireExperimentalFeature(const ExperimentalFeature & feature, const Pos & pos)
{
if (!experimentalFeatureSettings.isEnabled(feature))
throw ParseError(ErrorInfo{
.msg = HintFmt("experimental Nix feature '%1%' is disabled; add '--extra-experimental-features %1%' to enable it", showExperimentalFeature(feature)),
.pos = pos,
});
}
}
@ -119,6 +127,12 @@ or { return OR_KW; }
\-\> { return IMPL; }
\/\/ { return UPDATE; }
\+\+ { return CONCAT; }
\<\| { requireExperimentalFeature(Xp::PipeOperators, state->positions[CUR_POS]);
return PIPE_FROM;
}
\|\> { requireExperimentalFeature(Xp::PipeOperators, state->positions[CUR_POS]);
return PIPE_INTO;
}
{ID} { yylval->id = {yytext, (size_t) yyleng}; return ID; }
{INT} { errno = 0;

View file

@ -32,6 +32,7 @@ subdir('build-utils-meson/threads')
boost = dependency(
'boost',
modules : ['container', 'context'],
include_type: 'system',
)
# boost is a public dependency, but not a pkg-config dependency unfortunately, so we
# put in `deps_other`.
@ -55,7 +56,12 @@ if bdw_gc.found()
endif
configdata.set('HAVE_BOEHMGC', bdw_gc.found().to_int())
toml11 = dependency('toml11', version : '>=3.7.0', method : 'cmake')
toml11 = dependency(
'toml11',
version : '>=3.7.0',
method : 'cmake',
include_type: 'system',
)
deps_other += toml11
config_h = configure_file(

View file

@ -25,7 +25,7 @@ std::ostream & operator <<(std::ostream & str, const SymbolStr & symbol)
void Expr::show(const SymbolTable & symbols, std::ostream & str) const
{
abort();
unreachable();
}
void ExprInt::show(const SymbolTable & symbols, std::ostream & str) const
@ -82,7 +82,9 @@ void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) co
return sa < sb;
});
std::vector<Symbol> inherits;
std::map<ExprInheritFrom *, std::vector<Symbol>> inheritsFrom;
// We can use the displacement as a proxy for the order in which the symbols were parsed.
// The assignment of displacements should be deterministic, so that showBindings is deterministic.
std::map<Displacement, std::vector<Symbol>> inheritsFrom;
for (auto & i : sorted) {
switch (i->second.kind) {
case AttrDef::Kind::Plain:
@ -93,7 +95,7 @@ void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) co
case AttrDef::Kind::InheritedFrom: {
auto & select = dynamic_cast<ExprSelect &>(*i->second.e);
auto & from = dynamic_cast<ExprInheritFrom &>(*select.e);
inheritsFrom[&from].push_back(i->first);
inheritsFrom[from.displ].push_back(i->first);
break;
}
}
@ -105,7 +107,7 @@ void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) co
}
for (const auto & [from, syms] : inheritsFrom) {
str << "inherit (";
(*inheritFromExprs)[from->displ]->show(symbols, str);
(*inheritFromExprs)[from]->show(symbols, str);
str << ")";
for (auto sym : syms) str << " " << symbols[sym];
str << "; ";
@ -269,7 +271,7 @@ std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
void Expr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
abort();
unreachable();
}
void ExprInt::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)

View file

@ -186,7 +186,7 @@ struct ExprInheritFrom : ExprVar
this->fromWith = nullptr;
}
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env);
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override;
};
struct ExprSelect : Expr
@ -203,7 +203,7 @@ struct ExprSelect : Expr
*
* @param[out] v The attribute set that should contain the last attribute name (if it exists).
* @return The last attribute name in `attrPath`
*
*
* @note This does *not* evaluate the final attribute, and does not fail if that's the only attribute that does not exist.
*/
Symbol evalExceptFinalSelect(EvalState & state, Env & env, Value & attrs);

View file

@ -102,12 +102,8 @@ mkMesonDerivation (finalAttrs: {
LDFLAGS = "-fuse-ld=gold";
};
enableParallelBuilding = true;
separateDebugInfo = !stdenv.hostPlatform.isStatic;
strictDeps = true;
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
meta = {

View file

@ -64,7 +64,7 @@ struct LexerState
/**
* @brief Maps some positions to a DocComment, where the comment is relevant to the location.
*/
std::map<PosIdx, DocComment> & positionToDocComment;
std::unordered_map<PosIdx, DocComment> & positionToDocComment;
PosTable & positions;
PosTable::Origin origin;
@ -291,12 +291,23 @@ inline Expr * ParserState::stripIndentation(const PosIdx pos,
s2 = std::string(s2, 0, p + 1);
}
es2->emplace_back(i->first, new ExprString(std::move(s2)));
// Ignore empty strings for a minor optimisation and AST simplification
if (s2 != "") {
es2->emplace_back(i->first, new ExprString(std::move(s2)));
}
};
for (; i != es.end(); ++i, --n) {
std::visit(overloaded { trimExpr, trimString }, i->second);
}
// If there is nothing at all, return the empty string directly.
// This also ensures that equivalent empty strings result in the same ast, which is helpful when testing formatters.
if (es2->size() == 0) {
auto *const result = new ExprString("");
delete es2;
return result;
}
/* If this is a single string, then don't do a concatenation. */
if (es2->size() == 1 && dynamic_cast<ExprString *>((*es2)[0].second)) {
auto *const result = (*es2)[0].second;

View file

@ -48,7 +48,7 @@
namespace nix {
typedef std::map<PosIdx, DocComment> DocCommentMap;
typedef std::unordered_map<PosIdx, DocComment> DocCommentMap;
Expr * parseExprFromBuf(
char * text,
@ -99,6 +99,14 @@ static void setDocPosition(const LexerState & lexerState, ExprLambda * lambda, P
}
}
static Expr * makeCall(PosIdx pos, Expr * fn, Expr * arg) {
if (auto e2 = dynamic_cast<ExprCall *>(fn)) {
e2->args.push_back(arg);
return fn;
}
return new ExprCall(pos, fn, {arg});
}
%}
@ -123,6 +131,7 @@ static void setDocPosition(const LexerState & lexerState, ExprLambda * lambda, P
%type <e> start expr expr_function expr_if expr_op
%type <e> expr_select expr_simple expr_app
%type <e> expr_pipe_from expr_pipe_into
%type <list> expr_list
%type <attrs> binds
%type <formals> formals
@ -140,6 +149,7 @@ static void setDocPosition(const LexerState & lexerState, ExprLambda * lambda, P
%token <path> PATH HPATH SPATH PATH_END
%token <uri> URI
%token IF THEN ELSE ASSERT WITH LET IN_KW REC INHERIT EQ NEQ AND OR IMPL OR_KW
%token PIPE_FROM PIPE_INTO /* <| and |> */
%token DOLLAR_CURLY /* == ${ */
%token IND_STRING_OPEN IND_STRING_CLOSE
%token ELLIPSIS
@ -206,9 +216,21 @@ expr_function
expr_if
: IF expr THEN expr ELSE expr { $$ = new ExprIf(CUR_POS, $2, $4, $6); }
| expr_pipe_from
| expr_pipe_into
| expr_op
;
expr_pipe_from
: expr_op PIPE_FROM expr_pipe_from { $$ = makeCall(state->at(@2), $1, $3); }
| expr_op PIPE_FROM expr_op { $$ = makeCall(state->at(@2), $1, $3); }
;
expr_pipe_into
: expr_pipe_into PIPE_INTO expr_op { $$ = makeCall(state->at(@2), $3, $1); }
| expr_op PIPE_INTO expr_op { $$ = makeCall(state->at(@2), $3, $1); }
;
expr_op
: '!' expr_op %prec NOT { $$ = new ExprOpNot($2); }
| '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(state->s.sub), {new ExprInt(0), $2}); }
@ -233,13 +255,7 @@ expr_op
;
expr_app
: expr_app expr_select {
if (auto e2 = dynamic_cast<ExprCall *>($1)) {
e2->args.push_back($2);
$$ = $1;
} else
$$ = new ExprCall(CUR_POS, $1, {$2});
}
: expr_app expr_select { $$ = makeCall(CUR_POS, $1, $2); }
| expr_select
;

View file

@ -1,6 +1,7 @@
#pragma once
#include <cinttypes>
#include <functional>
namespace nix {
@ -8,6 +9,7 @@ class PosIdx
{
friend struct LazyPosAcessors;
friend class PosTable;
friend class std::hash<PosIdx>;
private:
uint32_t id;
@ -37,8 +39,26 @@ public:
{
return id == other.id;
}
size_t hash() const noexcept
{
return std::hash<uint32_t>{}(id);
}
};
inline PosIdx noPos = {};
}
namespace std {
template<>
struct hash<nix::PosIdx>
{
std::size_t operator()(nix::PosIdx pos) const noexcept
{
return pos.hash();
}
};
} // namespace std

View file

@ -426,7 +426,7 @@ static void prim_typeOf(EvalState & state, const PosIdx pos, Value * * args, Val
t = args[0]->external()->typeOf();
break;
case nFloat: t = "float"; break;
case nThunk: abort();
case nThunk: unreachable();
}
v.mkString(t);
}
@ -719,7 +719,7 @@ static RegisterPrimOp primop_genericClosure(PrimOp {
.doc = R"(
`builtins.genericClosure` iteratively computes the transitive closure over an arbitrary relation defined by a function.
It takes *attrset* with two attributes named `startSet` and `operator`, and returns a list of attrbute sets:
It takes *attrset* with two attributes named `startSet` and `operator`, and returns a list of attribute sets:
- `startSet`:
The initial list of attribute sets.
@ -1843,34 +1843,6 @@ static RegisterPrimOp primop_findFile(PrimOp {
.doc = R"(
Find *lookup-path* in *search-path*.
A search path is represented list of [attribute sets](./types.md#attribute-set) with two attributes:
- `prefix` is a relative path.
- `path` denotes a file system location
The exact syntax depends on the command line interface.
Examples of search path attribute sets:
- ```
{
prefix = "nixos-config";
path = "/etc/nixos/configuration.nix";
}
```
- ```
{
prefix = "";
path = "/nix/var/nix/profiles/per-user/root/channels";
}
```
The lookup algorithm checks each entry until a match is found, returning a [path value](@docroot@/language/types.md#type-path) of the match:
- If *lookup-path* matches `prefix`, then the remainder of *lookup-path* (the "suffix") is searched for within the directory denoted by `path`.
Note that the `path` may need to be downloaded at this point to look inside.
- If the suffix is found inside that directory, then the entry is a match.
The combined absolute path of the directory (now downloaded if need be) and the suffix is returned.
[Lookup path](@docroot@/language/constructs/lookup-path.md) expressions are [desugared](https://en.wikipedia.org/wiki/Syntactic_sugar) using this and [`builtins.nixPath`](#builtins-nixPath):
```nix
@ -1882,6 +1854,119 @@ static RegisterPrimOp primop_findFile(PrimOp {
```nix
builtins.findFile builtins.nixPath "nixpkgs"
```
A search path is represented as a list of [attribute sets](./types.md#attribute-set) with two attributes:
- `prefix` is a relative path.
- `path` denotes a file system location
Examples of search path attribute sets:
- ```
{
prefix = "";
path = "/nix/var/nix/profiles/per-user/root/channels";
}
```
- ```
{
prefix = "nixos-config";
path = "/etc/nixos/configuration.nix";
}
```
- ```
{
prefix = "nixpkgs";
path = "https://github.com/NixOS/nixpkgs/tarballs/master";
}
```
- ```
{
prefix = "nixpkgs";
path = "channel:nixpkgs-unstable";
}
```
- ```
{
prefix = "flake-compat";
path = "flake:github:edolstra/flake-compat";
}
```
The lookup algorithm checks each entry until a match is found, returning a [path value](@docroot@/language/types.md#type-path) of the match:
- If a prefix of `lookup-path` matches `prefix`, then the remainder of *lookup-path* (the "suffix") is searched for within the directory denoted by `path`.
The contents of `path` may need to be downloaded at this point to look inside.
- If the suffix is found inside that directory, then the entry is a match.
The combined absolute path of the directory (now downloaded if need be) and the suffix is returned.
> **Example**
>
> A *search-path* value
>
> ```
> [
> {
> prefix = "";
> path = "/home/eelco/Dev";
> }
> {
> prefix = "nixos-config";
> path = "/etc/nixos";
> }
> ]
> ```
>
> and a *lookup-path* value `"nixos-config"` will cause Nix to try `/home/eelco/Dev/nixos-config` and `/etc/nixos` in that order and return the first path that exists.
If `path` starts with `http://` or `https://`, it is interpreted as the URL of a tarball that will be downloaded and unpacked to a temporary location.
The tarball must consist of a single top-level directory.
The URLs of the tarballs from the official `nixos.org` channels can be abbreviated as `channel:<channel-name>`.
See [documentation on `nix-channel`](@docroot@/command-ref/nix-channel.md) for details about channels.
> **Example**
>
> These two search path entries are equivalent:
>
> - ```
> {
> prefix = "nixpkgs";
> path = "channel:nixpkgs-unstable";
> }
> ```
> - ```
> {
> prefix = "nixpkgs";
> path = "https://nixos.org/channels/nixos-unstable/nixexprs.tar.xz";
> }
> ```
Search paths can also point to source trees using [flake URLs](@docroot@/command-ref/new-cli/nix3-flake.md#url-like-syntax).
> **Example**
>
> The search path entry
>
> ```
> {
> prefix = "nixpkgs";
> path = "flake:nixpkgs";
> }
> ```
> specifies that the prefix `nixpkgs` shall refer to the source tree downloaded from the `nixpkgs` entry in the flake registry.
>
> Similarly
>
> ```
> {
> prefix = "nixpkgs";
> path = "flake:github:nixos/nixpkgs/nixos-22.05";
> }
> ```
>
> makes `<nixpkgs>` refer to a particular branch of the `NixOS/nixpkgs` repository on GitHub.
)",
.fun = prim_findFile,
});
@ -4731,6 +4816,13 @@ void EvalState::createBaseEnv()
.doc = R"(
The value of the [`nix-path` configuration setting](@docroot@/command-ref/conf-file.md#conf-nix-path): a list of search path entries used to resolve [lookup paths](@docroot@/language/constructs/lookup-path.md).
> **Example**
>
> ```bash
> $ NIX_PATH= nix-instantiate --eval --expr "builtins.nixPath" -I foo=bar --no-pure-eval
> [ { path = "bar"; prefix = "foo"; } ]
> ```
Lookup path expressions are [desugared](https://en.wikipedia.org/wiki/Syntactic_sugar) using this and
[`builtins.findFile`](./builtins.html#builtins-findFile):

View file

@ -1,6 +1,31 @@
/* This is the implementation of the derivation builtin function.
It's actually a wrapper around the derivationStrict primop. */
# This is the implementation of the derivation builtin function.
# It's actually a wrapper around the derivationStrict primop.
# Note that the following comment will be shown in :doc in the repl, but not in the manual.
/**
Create a derivation.
# Inputs
The single argument is an attribute set that describes what to build and how to build it.
See https://nix.dev/manual/nix/2.23/language/derivations
# Output
The result is an attribute set that describes the derivation.
Notably it contains the outputs, which in the context of the Nix language are special strings that refer to the output paths, which may not yet exist.
The realisation of these outputs only occurs when needed; for example
* When `nix-build` or a similar command is run, it realises the outputs that were requested on its command line.
See https://nix.dev/manual/nix/2.23/command-ref/nix-build
* When `import`, `readFile`, `readDir` or some other functions are called, they have to realise the outputs they depend on.
This is referred to as "import from derivation".
See https://nix.dev/manual/nix/2.23/language/import-from-derivation
Note that `derivation` is very bare-bones, and provides almost no commands during the build.
Most likely, you'll want to use functions like `stdenv.mkDerivation` in Nixpkgs to set up a basic environment.
*/
drvAttrs @ { outputs ? [ "out" ], ... }:
let

View file

@ -523,9 +523,19 @@ static void prim_fetchurl(EvalState & state, const PosIdx pos, Value * * args, V
static RegisterPrimOp primop_fetchurl({
.name = "__fetchurl",
.args = {"url"},
.args = {"arg"},
.doc = R"(
Download the specified URL and return the path of the downloaded file.
`arg` can be either a string denoting the URL, or an attribute set with the following attributes:
- `url`
The URL of the file to download.
- `name` (default: the last path component of the URL)
A name for the file in the store. This can be useful if the URL has any
characters that are invalid for the store.
Not available in [restricted evaluation mode](@docroot@/command-ref/conf-file.md#conf-restrict-eval).
)",
@ -543,11 +553,11 @@ static RegisterPrimOp primop_fetchTarball({
.doc = R"(
Download the specified URL, unpack it and return the path of the
unpacked tree. The file must be a tape archive (`.tar`) compressed
with `gzip`, `bzip2` or `xz`. The top-level path component of the
files in the tarball is removed, so it is best if the tarball
contains a single directory at top level. The typical use of the
function is to obtain external Nix expression dependencies, such as
a particular version of Nixpkgs, e.g.
with `gzip`, `bzip2` or `xz`. If the tarball consists of a
single directory, then the top-level path component of the files
in the tarball is removed. The typical use of the function is to
obtain external Nix expression dependencies, such as a
particular version of Nixpkgs, e.g.
```nix
with import (fetchTarball https://github.com/NixOS/nixpkgs/archive/nixos-14.12.tar.gz) {};
@ -654,12 +664,12 @@ static RegisterPrimOp primop_fetchGit({
Whether to check `rev` for a signature matching `publicKey` or `publicKeys`.
If `verifyCommit` is enabled, then `fetchGit` cannot use a local repository with uncommitted changes.
Requires the [`verified-fetches` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-verified-fetches).
Requires the [`verified-fetches` experimental feature](@docroot@/development/experimental-features.md#xp-feature-verified-fetches).
- `publicKey`
The public key against which `rev` is verified if `verifyCommit` is enabled.
Requires the [`verified-fetches` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-verified-fetches).
Requires the [`verified-fetches` experimental feature](@docroot@/development/experimental-features.md#xp-feature-verified-fetches).
- `keytype` (default: `"ssh-ed25519"`)
@ -671,7 +681,7 @@ static RegisterPrimOp primop_fetchGit({
- `"ssh-ed25519"`
- `"ssh-ed25519-sk"`
- `"ssh-rsa"`
Requires the [`verified-fetches` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-verified-fetches).
Requires the [`verified-fetches` experimental feature](@docroot@/development/experimental-features.md#xp-feature-verified-fetches).
- `publicKeys`
@ -685,7 +695,7 @@ static RegisterPrimOp primop_fetchGit({
}
```
Requires the [`verified-fetches` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-verified-fetches).
Requires the [`verified-fetches` experimental feature](@docroot@/development/experimental-features.md#xp-feature-verified-fetches).
Here are some examples of how to use `fetchGit`.

View file

@ -2,6 +2,7 @@
#include "eval-inline.hh"
#include <sstream>
#include <toml.hpp>
namespace nix {

View file

@ -94,7 +94,7 @@ void printAmbiguous(
break;
default:
printError("Nix evaluator internal error: printAmbiguous: invalid value type");
abort();
unreachable();
}
}

View file

@ -299,6 +299,9 @@ private:
output << ANSI_NORMAL;
}
/**
* @note This may force items.
*/
bool shouldPrettyPrintAttrs(AttrVec & v)
{
if (!options.shouldPrettyPrint() || v.empty()) {
@ -315,6 +318,9 @@ private:
return true;
}
// It is ok to force the item(s) here, because they will be printed anyway.
state.forceValue(*item, item->determinePos(noPos));
// Pretty-print single-item attrsets only if they contain nested
// structures.
auto itemType = item->type();
@ -371,6 +377,9 @@ private:
}
}
/**
* @note This may force items.
*/
bool shouldPrettyPrintList(std::span<Value * const> list)
{
if (!options.shouldPrettyPrint() || list.empty()) {
@ -387,6 +396,9 @@ private:
return true;
}
// It is ok to force the item(s) here, because they will be printed anyway.
state.forceValue(*item, item->determinePos(noPos));
// Pretty-print single-item lists only if they contain nested
// structures.
auto itemType = item->type();
@ -463,7 +475,7 @@ private:
else
output << "primop";
} else {
abort();
unreachable();
}
output << "»";
@ -492,7 +504,7 @@ private:
if (options.ansiColors)
output << ANSI_NORMAL;
} else {
abort();
unreachable();
}
}

View file

@ -7,6 +7,7 @@
#include "types.hh"
#include "chunked-vector.hh"
#include "error.hh"
namespace nix {
@ -69,6 +70,8 @@ public:
auto operator<=>(const Symbol other) const { return id <=> other.id; }
bool operator==(const Symbol other) const { return id == other.id; }
friend class std::hash<Symbol>;
};
/**
@ -113,7 +116,7 @@ public:
SymbolStr operator[](Symbol s) const
{
if (s.id == 0 || s.id > store.size())
abort();
unreachable();
return SymbolStr(store[s.id - 1]);
}
@ -132,3 +135,12 @@ public:
};
}
template<>
struct std::hash<nix::Symbol>
{
std::size_t operator()(const nix::Symbol & s) const noexcept
{
return std::hash<decltype(s.id)>{}(s.id);
}
};

View file

@ -285,7 +285,7 @@ public:
if (invalidIsThunk)
return nThunk;
else
abort();
unreachable();
}
inline void finishValue(InternalType newType, Payload newPayload)
@ -494,11 +494,11 @@ void Value::mkBlackhole()
#if HAVE_BOEHMGC
typedef std::vector<Value *, traceable_allocator<Value *>> ValueVector;
typedef std::map<Symbol, Value *, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, Value *>>> ValueMap;
typedef std::unordered_map<Symbol, Value *, std::hash<Symbol>, std::equal_to<Symbol>, traceable_allocator<std::pair<const Symbol, Value *>>> ValueMap;
typedef std::map<Symbol, ValueVector, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, ValueVector>>> ValueVectorMap;
#else
typedef std::vector<Value *> ValueVector;
typedef std::map<Symbol, Value *> ValueMap;
typedef std::unordered_map<Symbol, Value *> ValueMap;
typedef std::map<Symbol, ValueVector> ValueVectorMap;
#endif