mirror of
https://github.com/NixOS/nix
synced 2025-07-16 02:01:59 +02:00
Merge remote-tracking branch 'origin/master' into lazy-trees
This commit is contained in:
commit
3621d0769d
97 changed files with 3007 additions and 727 deletions
|
@ -12,7 +12,9 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
|
@ -1891,7 +1893,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
|||
state.error("cannot add %1% to a float", showType(*vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
|
||||
} else if (firstType == nPath) {
|
||||
if (!first) {
|
||||
auto part = state.coerceToString(i_pos, *vTmp, context, false, false);
|
||||
auto part = state.coerceToString(i_pos, *vTmp, context, "while evaluating a path segment", false, false);
|
||||
if (sSize <= 1 && !hasPrefix(*part, "/") && accessor != state.rootFS.get_ptr())
|
||||
state.error(
|
||||
"cannot append non-absolute path '%1%' to '%2%' (hint: change it to '/%1%')",
|
||||
|
@ -1904,7 +1906,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
|||
}
|
||||
} else {
|
||||
if (s.empty()) s.reserve(es->size());
|
||||
auto part = state.coerceToString(i_pos, *vTmp, context, false, firstType == nString, "while evaluating a path segment");
|
||||
auto part = state.coerceToString(i_pos, *vTmp, context, "while evaluating a path segment", false, firstType == nString);
|
||||
sSize += part->size();
|
||||
s.emplace_back(std::move(part));
|
||||
}
|
||||
|
@ -2100,15 +2102,21 @@ std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value &
|
|||
if (i != v.attrs->end()) {
|
||||
Value v1;
|
||||
callFunction(*i->value, v, v1, pos);
|
||||
return coerceToString(pos, v1, context, coerceMore, copyToStore,
|
||||
"while evaluating the result of the `toString` attribute").toOwned();
|
||||
return coerceToString(pos, v1, context,
|
||||
"while evaluating the result of the `__toString` attribute",
|
||||
coerceMore, copyToStore).toOwned();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context,
|
||||
bool coerceMore, bool copyToStore, std::string_view errorCtx)
|
||||
BackedStringView EvalState::coerceToString(
|
||||
const PosIdx pos,
|
||||
Value & v,
|
||||
PathSet & context,
|
||||
std::string_view errorCtx,
|
||||
bool coerceMore,
|
||||
bool copyToStore)
|
||||
{
|
||||
forceValue(v, pos);
|
||||
|
||||
|
@ -2129,13 +2137,22 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
|
|||
if (maybeString)
|
||||
return std::move(*maybeString);
|
||||
auto i = v.attrs->find(sOutPath);
|
||||
if (i == v.attrs->end())
|
||||
error("cannot coerce a set to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
|
||||
return coerceToString(pos, *i->value, context, coerceMore, copyToStore, errorCtx);
|
||||
if (i == v.attrs->end()) {
|
||||
error("cannot coerce %1% to a string", showType(v))
|
||||
.withTrace(pos, errorCtx)
|
||||
.debugThrow<TypeError>();
|
||||
}
|
||||
return coerceToString(pos, *i->value, context, errorCtx, coerceMore, copyToStore);
|
||||
}
|
||||
|
||||
if (v.type() == nExternal)
|
||||
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore, errorCtx);
|
||||
if (v.type() == nExternal) {
|
||||
try {
|
||||
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore);
|
||||
} catch (Error & e) {
|
||||
e.addTrace(nullptr, errorCtx);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
if (coerceMore) {
|
||||
/* Note that `false' is represented as an empty string for
|
||||
|
@ -2150,8 +2167,9 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
|
|||
std::string result;
|
||||
for (auto [n, v2] : enumerate(v.listItems())) {
|
||||
try {
|
||||
result += *coerceToString(noPos, *v2, context, coerceMore, copyToStore,
|
||||
"while evaluating one element of the list");
|
||||
result += *coerceToString(noPos, *v2, context,
|
||||
"while evaluating one element of the list",
|
||||
coerceMore, copyToStore);
|
||||
} catch (Error & e) {
|
||||
e.addTrace(positions[pos], errorCtx);
|
||||
throw;
|
||||
|
@ -2165,7 +2183,9 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
|
|||
}
|
||||
}
|
||||
|
||||
error("cannot coerce %1% to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
|
||||
error("cannot coerce %1% to a string", showType(v))
|
||||
.withTrace(pos, errorCtx)
|
||||
.debugThrow<TypeError>();
|
||||
}
|
||||
|
||||
|
||||
|
@ -2215,7 +2235,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & contex
|
|||
|
||||
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
|
||||
{
|
||||
auto path = coerceToString(pos, v, context, false, false, errorCtx).toOwned();
|
||||
auto path = coerceToString(pos, v, context, errorCtx, false, false).toOwned();
|
||||
if (auto storePath = store->maybeParseStorePath(path))
|
||||
return *storePath;
|
||||
error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
|
||||
|
@ -2421,13 +2441,11 @@ void EvalState::printStats()
|
|||
}
|
||||
|
||||
|
||||
std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const
|
||||
std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
|
||||
{
|
||||
auto e = TypeError({
|
||||
throw TypeError({
|
||||
.msg = hintfmt("cannot coerce %1% to a string", showType())
|
||||
});
|
||||
e.addTrace(pos, errorCtx);
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -206,6 +206,9 @@ public:
|
|||
throw std::move(error);
|
||||
}
|
||||
|
||||
// This is dangerous, but gets in line with the idea that error creation and
|
||||
// throwing should not allocate on the stack of hot functions.
|
||||
// as long as errors are immediately thrown, it works.
|
||||
ErrorBuilder * errorBuilder;
|
||||
|
||||
template<typename... Args>
|
||||
|
@ -386,8 +389,8 @@ public:
|
|||
booleans and lists to a string. If `copyToStore' is set,
|
||||
referenced paths are copied to the Nix store as a side effect. */
|
||||
BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context,
|
||||
bool coerceMore = false, bool copyToStore = true,
|
||||
std::string_view errorCtx = "");
|
||||
std::string_view errorCtx,
|
||||
bool coerceMore = false, bool copyToStore = true);
|
||||
|
||||
StorePath copyPathToStore(PathSet & context, const SourcePath & path);
|
||||
|
||||
|
|
|
@ -202,7 +202,7 @@ static Flake readFlake(
|
|||
PathSet emptyContext = {};
|
||||
flake.config.settings.emplace(
|
||||
state.symbols[setting.name],
|
||||
state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, "").toOwned());
|
||||
state.coerceToString(setting.pos, *setting.value, emptyContext, "", false, true).toOwned());
|
||||
}
|
||||
else if (setting.value->type() == nInt)
|
||||
flake.config.settings.emplace(
|
||||
|
|
|
@ -239,15 +239,15 @@ std::pair<ref<InputAccessor>, FlakeRef> FlakeRef::lazyFetch(ref<Store> store) co
|
|||
return {accessor, FlakeRef(std::move(lockedInput), subdir)};
|
||||
}
|
||||
|
||||
std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec(
|
||||
std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
|
||||
const std::string & url,
|
||||
const std::optional<Path> & baseDir,
|
||||
bool allowMissing,
|
||||
bool isFlake)
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec(url);
|
||||
auto [flakeRef, fragment] = parseFlakeRefWithFragment(prefix, baseDir, allowMissing, isFlake);
|
||||
return {std::move(flakeRef), fragment, outputsSpec};
|
||||
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(url);
|
||||
auto [flakeRef, fragment] = parseFlakeRefWithFragment(std::string { prefix }, baseDir, allowMissing, isFlake);
|
||||
return {std::move(flakeRef), fragment, extendedOutputsSpec};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
|||
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
|
||||
const std::string & url, const std::optional<Path> & baseDir = {});
|
||||
|
||||
std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec(
|
||||
std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
|
||||
const std::string & url,
|
||||
const std::optional<Path> & baseDir = {},
|
||||
bool allowMissing = false,
|
||||
|
|
|
@ -54,7 +54,7 @@ StringMap EvalState::realiseContext(const PathSet & context)
|
|||
[&](const NixStringContextElem::Built & b) {
|
||||
drvs.push_back(DerivedPath::Built {
|
||||
.drvPath = b.drvPath,
|
||||
.outputs = std::set { b.output },
|
||||
.outputs = OutputsSpec::Names { b.output },
|
||||
});
|
||||
ensureValid(b.drvPath);
|
||||
},
|
||||
|
@ -85,16 +85,12 @@ StringMap EvalState::realiseContext(const PathSet & context)
|
|||
store->buildPaths(buildReqs);
|
||||
|
||||
/* Get all the output paths corresponding to the placeholders we had */
|
||||
for (auto & [drvPath, outputs] : drvs) {
|
||||
const auto outputPaths = store->queryDerivationOutputMap(drvPath);
|
||||
for (auto & outputName : outputs) {
|
||||
auto outputPath = get(outputPaths, outputName);
|
||||
if (!outputPath)
|
||||
debugThrowLastTrace(Error("derivation '%s' does not have an output named '%s'",
|
||||
store->printStorePath(drvPath), outputName));
|
||||
for (auto & drv : drvs) {
|
||||
auto outputs = resolveDerivedPath(*store, drv);
|
||||
for (auto & [outputName, outputPath] : outputs) {
|
||||
res.insert_or_assign(
|
||||
downstreamPlaceholder(*store, drvPath, outputName),
|
||||
store->printStorePath(*outputPath)
|
||||
downstreamPlaceholder(*store, drv.drvPath, outputName),
|
||||
store->printStorePath(outputPath)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -356,26 +352,22 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|||
auto elems = args[0]->listElems();
|
||||
auto count = args[0]->listSize();
|
||||
if (count == 0)
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("at least one argument to 'exec' required"),
|
||||
.errPos = state.positions[pos]
|
||||
}));
|
||||
state.error("at least one argument to 'exec' required").atPos(pos).debugThrow<EvalError>();
|
||||
PathSet context;
|
||||
auto program = state.coerceToString(pos, *elems[0], context, false, false,
|
||||
"while evaluating the first element of the argument passed to builtins.exec").toOwned();
|
||||
auto program = state.coerceToString(pos, *elems[0], context,
|
||||
"while evaluating the first element of the argument passed to builtins.exec",
|
||||
false, false).toOwned();
|
||||
Strings commandArgs;
|
||||
for (unsigned int i = 1; i < args[0]->listSize(); ++i) {
|
||||
commandArgs.push_back(state.coerceToString(pos, *elems[i], context, false, false,
|
||||
"while evaluating an element of the argument passed to builtins.exec").toOwned());
|
||||
commandArgs.push_back(
|
||||
state.coerceToString(pos, *elems[i], context,
|
||||
"while evaluating an element of the argument passed to builtins.exec",
|
||||
false, false).toOwned());
|
||||
}
|
||||
try {
|
||||
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
|
||||
} catch (InvalidPathError & e) {
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("cannot execute '%1%', since path '%2%' is not valid",
|
||||
program, e.path),
|
||||
.errPos = state.positions[pos]
|
||||
}));
|
||||
state.error("cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow<EvalError>();
|
||||
}
|
||||
|
||||
auto output = runProgram(program, true, commandArgs);
|
||||
|
@ -605,7 +597,8 @@ struct CompareValues
|
|||
state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow<EvalError>();
|
||||
}
|
||||
} catch (Error & e) {
|
||||
e.addTrace(nullptr, errorCtx);
|
||||
if (!errorCtx.empty())
|
||||
e.addTrace(nullptr, errorCtx);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -627,15 +620,7 @@ static Bindings::iterator getAttr(
|
|||
{
|
||||
Bindings::iterator value = attrSet->find(attrSym);
|
||||
if (value == attrSet->end()) {
|
||||
throw TypeError({
|
||||
.msg = hintfmt("attribute '%s' missing %s", state.symbols[attrSym], normaltxt(errorCtx)),
|
||||
.errPos = state.positions[attrSet->pos],
|
||||
});
|
||||
// TODO XXX
|
||||
// Adding another trace for the function name to make it clear
|
||||
// which call received wrong arguments.
|
||||
//e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", funcName));
|
||||
//state.debugThrowLastTrace(e);
|
||||
state.error("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow<TypeError>();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
@ -809,7 +794,8 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
|
|||
} catch (Error & e) {
|
||||
PathSet context;
|
||||
e.addTrace(nullptr, state.decodePaths(*state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the error message passed to 'builtins.addErrorContext'")));
|
||||
"while evaluating the error message passed to 'builtins.addErrorContext'",
|
||||
false, false)));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
@ -1013,6 +999,7 @@ static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Val
|
|||
* Derivations
|
||||
*************************************************************/
|
||||
|
||||
static void derivationStrictInternal(EvalState & state, const std::string & name, Bindings * attrs, Value & v);
|
||||
|
||||
/* Construct (as a unobservable side effect) a Nix derivation
|
||||
expression that performs the derivation described by the argument
|
||||
|
@ -1023,32 +1010,68 @@ static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Val
|
|||
derivation. */
|
||||
static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
using nlohmann::json;
|
||||
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.derivationStrict");
|
||||
|
||||
Bindings * attrs = args[0]->attrs;
|
||||
|
||||
/* Figure out the name first (for stack backtraces). */
|
||||
Bindings::iterator attr = getAttr(state, state.sName, args[0]->attrs, "in the attrset passed as argument to builtins.derivationStrict");
|
||||
Bindings::iterator nameAttr = getAttr(state, state.sName, attrs, "in the attrset passed as argument to builtins.derivationStrict");
|
||||
|
||||
std::string drvName;
|
||||
const auto posDrvName = attr->pos;
|
||||
try {
|
||||
drvName = state.forceStringNoCtx(*attr->value, pos, "while evaluating the `name` attribute passed to builtins.derivationStrict");
|
||||
drvName = state.forceStringNoCtx(*nameAttr->value, pos, "while evaluating the `name` attribute passed to builtins.derivationStrict");
|
||||
} catch (Error & e) {
|
||||
e.addTrace(state.positions[posDrvName], "while evaluating the derivation attribute 'name'");
|
||||
e.addTrace(state.positions[nameAttr->pos], "while evaluating the derivation attribute 'name'");
|
||||
throw;
|
||||
}
|
||||
|
||||
try {
|
||||
derivationStrictInternal(state, drvName, attrs, v);
|
||||
} catch (Error & e) {
|
||||
Pos pos = state.positions[nameAttr->pos];
|
||||
/*
|
||||
* Here we make two abuses of the error system
|
||||
*
|
||||
* 1. We print the location as a string to avoid a code snippet being
|
||||
* printed. While the location of the name attribute is a good hint, the
|
||||
* exact code there is irrelevant.
|
||||
*
|
||||
* 2. We mark this trace as a frame trace, meaning that we stop printing
|
||||
* less important traces from now on. In particular, this prevents the
|
||||
* display of the automatic "while calling builtins.derivationStrict"
|
||||
* trace, which is of little use for the public we target here.
|
||||
*
|
||||
* Please keep in mind that error reporting is done on a best-effort
|
||||
* basis in nix. There is no accurate location for a derivation, as it
|
||||
* often results from the composition of several functions
|
||||
* (derivationStrict, derivation, mkDerivation, mkPythonModule, etc.)
|
||||
*/
|
||||
e.addTrace(nullptr, hintfmt(
|
||||
"while evaluating derivation '%s'\n"
|
||||
" whose name attribute is located at %s",
|
||||
drvName, pos), true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static void derivationStrictInternal(EvalState & state, const std::string &
|
||||
drvName, Bindings * attrs, Value & v)
|
||||
{
|
||||
/* Check whether attributes should be passed as a JSON file. */
|
||||
using nlohmann::json;
|
||||
std::optional<json> jsonObject;
|
||||
attr = args[0]->attrs->find(state.sStructuredAttrs);
|
||||
if (attr != args[0]->attrs->end() && state.forceBool(*attr->value, pos, "while evaluating the `__structuredAttrs` attribute passed to builtins.derivationStrict"))
|
||||
auto attr = attrs->find(state.sStructuredAttrs);
|
||||
if (attr != attrs->end() &&
|
||||
state.forceBool(*attr->value, noPos,
|
||||
"while evaluating the `__structuredAttrs` "
|
||||
"attribute passed to builtins.derivationStrict"))
|
||||
jsonObject = json::object();
|
||||
|
||||
/* Check whether null attributes should be ignored. */
|
||||
bool ignoreNulls = false;
|
||||
attr = args[0]->attrs->find(state.sIgnoreNulls);
|
||||
if (attr != args[0]->attrs->end())
|
||||
ignoreNulls = state.forceBool(*attr->value, pos, "while evaluating the `__ignoreNulls` attribute passed to builtins.derivationStrict");
|
||||
attr = attrs->find(state.sIgnoreNulls);
|
||||
if (attr != attrs->end())
|
||||
ignoreNulls = state.forceBool(*attr->value, noPos, "while evaluating the `__ignoreNulls` attribute " "passed to builtins.derivationStrict");
|
||||
|
||||
/* Build the derivation expression by processing the attributes. */
|
||||
Derivation drv;
|
||||
|
@ -1065,7 +1088,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
StringSet outputs;
|
||||
outputs.insert("out");
|
||||
|
||||
for (auto & i : args[0]->attrs->lexicographicOrder(state.symbols)) {
|
||||
for (auto & i : attrs->lexicographicOrder(state.symbols)) {
|
||||
if (i->name == state.sIgnoreNulls) continue;
|
||||
const std::string & key = state.symbols[i->name];
|
||||
vomit("processing attribute '%1%'", key);
|
||||
|
@ -1076,7 +1099,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
else
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
};
|
||||
|
||||
|
@ -1086,7 +1109,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
if (outputs.find(j) != outputs.end())
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("duplicate derivation output '%1%'", j),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
/* !!! Check whether j is a valid attribute
|
||||
name. */
|
||||
|
@ -1096,34 +1119,35 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
if (j == "drv")
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("invalid derivation output name 'drv'" ),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
outputs.insert(j);
|
||||
}
|
||||
if (outputs.empty())
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("derivation cannot have an empty set of outputs"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
};
|
||||
|
||||
try {
|
||||
// This try-catch block adds context for most errors.
|
||||
// Use this empty error context to signify that we defer to it.
|
||||
const std::string_view context_below("");
|
||||
|
||||
if (ignoreNulls) {
|
||||
state.forceValue(*i->value, pos);
|
||||
state.forceValue(*i->value, noPos);
|
||||
if (i->value->type() == nNull) continue;
|
||||
}
|
||||
|
||||
if (i->name == state.sContentAddressed) {
|
||||
contentAddressed = state.forceBool(*i->value, pos,
|
||||
"while evaluating the `__contentAddressed` attribute passed to builtins.derivationStrict");
|
||||
contentAddressed = state.forceBool(*i->value, noPos, context_below);
|
||||
if (contentAddressed)
|
||||
settings.requireExperimentalFeature(Xp::CaDerivations);
|
||||
}
|
||||
|
||||
else if (i->name == state.sImpure) {
|
||||
isImpure = state.forceBool(*i->value, pos,
|
||||
"while evaluating the 'impure' attribute passed to builtins.derivationStrict");
|
||||
isImpure = state.forceBool(*i->value, noPos, context_below);
|
||||
if (isImpure)
|
||||
settings.requireExperimentalFeature(Xp::ImpureDerivations);
|
||||
}
|
||||
|
@ -1131,11 +1155,11 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
/* The `args' attribute is special: it supplies the
|
||||
command-line arguments to the builder. */
|
||||
else if (i->name == state.sArgs) {
|
||||
state.forceList(*i->value, pos,
|
||||
"while evaluating the `args` attribute passed to builtins.derivationStrict");
|
||||
state.forceList(*i->value, noPos, context_below);
|
||||
for (auto elem : i->value->listItems()) {
|
||||
auto s = state.coerceToString(posDrvName, *elem, context, true,
|
||||
"while evaluating an element of the `args` argument passed to builtins.derivationStrict").toOwned();
|
||||
auto s = state.coerceToString(noPos, *elem, context,
|
||||
"while evaluating an element of the argument list",
|
||||
true).toOwned();
|
||||
drv.args.push_back(s);
|
||||
}
|
||||
}
|
||||
|
@ -1148,29 +1172,29 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
|
||||
if (i->name == state.sStructuredAttrs) continue;
|
||||
|
||||
(*jsonObject)[key] = printValueAsJSON(state, true, *i->value, pos, context);
|
||||
(*jsonObject)[key] = printValueAsJSON(state, true, *i->value, noPos, context);
|
||||
|
||||
if (i->name == state.sBuilder)
|
||||
drv.builder = state.forceString(*i->value, context, posDrvName, "while evaluating the `builder` attribute passed to builtins.derivationStrict");
|
||||
drv.builder = state.forceString(*i->value, context, noPos, context_below);
|
||||
else if (i->name == state.sSystem)
|
||||
drv.platform = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `system` attribute passed to builtins.derivationStrict");
|
||||
drv.platform = state.forceStringNoCtx(*i->value, noPos, context_below);
|
||||
else if (i->name == state.sOutputHash)
|
||||
outputHash = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHash` attribute passed to builtins.derivationStrict");
|
||||
outputHash = state.forceStringNoCtx(*i->value, noPos, context_below);
|
||||
else if (i->name == state.sOutputHashAlgo)
|
||||
outputHashAlgo = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHashAlgo` attribute passed to builtins.derivationStrict");
|
||||
outputHashAlgo = state.forceStringNoCtx(*i->value, noPos, context_below);
|
||||
else if (i->name == state.sOutputHashMode)
|
||||
handleHashMode(state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHashMode` attribute passed to builtins.derivationStrict"));
|
||||
handleHashMode(state.forceStringNoCtx(*i->value, noPos, context_below));
|
||||
else if (i->name == state.sOutputs) {
|
||||
/* Require ‘outputs’ to be a list of strings. */
|
||||
state.forceList(*i->value, posDrvName, "while evaluating the `outputs` attribute passed to builtins.derivationStrict");
|
||||
state.forceList(*i->value, noPos, context_below);
|
||||
Strings ss;
|
||||
for (auto elem : i->value->listItems())
|
||||
ss.emplace_back(state.forceStringNoCtx(*elem, posDrvName, "while evaluating an element of the `outputs` attribute passed to builtins.derivationStrict"));
|
||||
ss.emplace_back(state.forceStringNoCtx(*elem, noPos, context_below));
|
||||
handleOutputs(ss);
|
||||
}
|
||||
|
||||
} else {
|
||||
auto s = state.coerceToString(i->pos, *i->value, context, true, "while evaluating an attribute passed to builtins.derivationStrict").toOwned();
|
||||
auto s = state.coerceToString(noPos, *i->value, context, context_below, true).toOwned();
|
||||
drv.env.emplace(key, s);
|
||||
if (i->name == state.sBuilder) drv.builder = std::move(s);
|
||||
else if (i->name == state.sSystem) drv.platform = std::move(s);
|
||||
|
@ -1184,8 +1208,8 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
}
|
||||
|
||||
} catch (Error & e) {
|
||||
e.addTrace(nullptr,
|
||||
hintfmt("while evaluating the attribute '%1%' of the derivation '%2%'", key, drvName),
|
||||
e.addTrace(state.positions[i->pos],
|
||||
hintfmt("while evaluating attribute '%1%' of derivation '%2%'", key, drvName),
|
||||
true);
|
||||
throw;
|
||||
}
|
||||
|
@ -1230,20 +1254,20 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
if (drv.builder == "")
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("required attribute 'builder' missing"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
|
||||
if (drv.platform == "")
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("required attribute 'system' missing"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
|
||||
/* Check whether the derivation name is valid. */
|
||||
if (isDerivation(drvName))
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("derivation names are not allowed to end in '%s'", drvExtension),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
|
||||
if (outputHash) {
|
||||
|
@ -1254,7 +1278,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
if (outputs.size() != 1 || *(outputs.begin()) != "out")
|
||||
state.debugThrowLastTrace(Error({
|
||||
.msg = hintfmt("multiple outputs are not supported in fixed-output derivations"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
}));
|
||||
|
||||
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
|
||||
|
@ -1275,7 +1299,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
if (contentAddressed && isImpure)
|
||||
throw EvalError({
|
||||
.msg = hintfmt("derivation cannot be both content-addressed and impure"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
.errPos = state.positions[noPos]
|
||||
});
|
||||
|
||||
auto ht = parseHashTypeOpt(outputHashAlgo).value_or(htSHA256);
|
||||
|
@ -1319,7 +1343,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
if (!h)
|
||||
throw AssertionError({
|
||||
.msg = hintfmt("derivation produced no hash for output '%s'", i),
|
||||
.errPos = state.positions[posDrvName],
|
||||
.errPos = state.positions[noPos],
|
||||
});
|
||||
auto outPath = state.store->makeOutputPath(i, *h, drvName);
|
||||
drv.env[i] = state.store->printStorePath(outPath);
|
||||
|
@ -1352,11 +1376,12 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
drvHashes.lock()->insert_or_assign(drvPath, h);
|
||||
}
|
||||
|
||||
auto attrs = state.buildBindings(1 + drv.outputs.size());
|
||||
attrs.alloc(state.sDrvPath).mkString(drvPathS, {"=" + drvPathS});
|
||||
auto result = state.buildBindings(1 + drv.outputs.size());
|
||||
result.alloc(state.sDrvPath).mkString(drvPathS, {"=" + drvPathS});
|
||||
for (auto & i : drv.outputs)
|
||||
mkOutputString(state, attrs, drvPath, drv, i);
|
||||
v.mkAttrs(attrs);
|
||||
mkOutputString(state, result, drvPath, drv, i);
|
||||
|
||||
v.mkAttrs(result);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_derivationStrict(RegisterPrimOp::Info {
|
||||
|
@ -1502,7 +1527,9 @@ static RegisterPrimOp primop_pathExists({
|
|||
static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
PathSet context;
|
||||
v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.baseNameOf")), context);
|
||||
v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the first argument passed to builtins.baseNameOf",
|
||||
false, false)), context);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_baseNameOf({
|
||||
|
@ -1527,8 +1554,9 @@ static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Valu
|
|||
auto path = args[0]->path();
|
||||
v.mkPath(path.path.isRoot() ? path : path.parent());
|
||||
} else {
|
||||
auto path = state.coerceToString(pos, *args[0], context, false, false,
|
||||
"while evaluating the first argument passed to 'builtins.dirOf'");
|
||||
auto path = state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the first argument passed to 'builtins.dirOf'",
|
||||
false, false);
|
||||
auto dir = dirOf(*path);
|
||||
v.mkString(dir, context);
|
||||
}
|
||||
|
@ -1596,8 +1624,9 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
|
|||
i = getAttr(state, state.sPath, v2->attrs, "in an element of the __nixPath");
|
||||
|
||||
PathSet context;
|
||||
auto path = state.coerceToString(pos, *i->value, context, false, false,
|
||||
"while evaluating the `path` attribute of an element of the list passed to builtins.findFile").toOwned();
|
||||
auto path = state.coerceToString(pos, *i->value, context,
|
||||
"while evaluating the `path` attribute of an element of the list passed to builtins.findFile",
|
||||
false, false).toOwned();
|
||||
|
||||
try {
|
||||
auto rewrites = state.realiseContext(context);
|
||||
|
@ -1660,18 +1689,56 @@ static std::string_view fileTypeToString(InputAccessor::Type type)
|
|||
"unknown";
|
||||
}
|
||||
|
||||
static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
auto path = realisePath(state, pos, *args[0]);
|
||||
/* Retrieve the directory entry type and stringize it. */
|
||||
v.mkString(fileTypeToString(path.lstat().type));
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_readFileType({
|
||||
.name = "__readFileType",
|
||||
.args = {"p"},
|
||||
.doc = R"(
|
||||
Determine the directory entry type of a filesystem node, being
|
||||
one of "directory", "regular", "symlink", or "unknown".
|
||||
)",
|
||||
.fun = prim_readFileType,
|
||||
});
|
||||
|
||||
/* Read a directory (without . or ..) */
|
||||
static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
auto path = realisePath(state, pos, *args[0]);
|
||||
|
||||
// Retrieve directory entries for all nodes in a directory.
|
||||
// This is similar to `getFileType` but is optimized to reduce system calls
|
||||
// on many systems.
|
||||
auto entries = path.readDirectory();
|
||||
auto attrs = state.buildBindings(entries.size());
|
||||
|
||||
// If we hit unknown directory entry types we may need to fallback to
|
||||
// using `getFileType` on some systems.
|
||||
// In order to reduce system calls we make each lookup lazy by using
|
||||
// `builtins.readFileType` application.
|
||||
Value * readFileType = nullptr;
|
||||
|
||||
for (auto & [name, type] : entries) {
|
||||
if (!type)
|
||||
type = (path + name).lstat().type;
|
||||
attrs.alloc(name).mkString(fileTypeToString(*type));
|
||||
auto & attr = attrs.alloc(name);
|
||||
if (!type) {
|
||||
// Some filesystems or operating systems may not be able to return
|
||||
// detailed node info quickly in this case we produce a thunk to
|
||||
// query the file type lazily.
|
||||
auto epath = state.allocValue();
|
||||
epath->mkPath(path + name);
|
||||
if (!readFileType)
|
||||
readFileType = &state.getBuiltin("readFileType");
|
||||
attr.mkApp(readFileType, epath);
|
||||
} else {
|
||||
// This branch of the conditional is much more likely.
|
||||
// Here we just stringize the directory entry type.
|
||||
attr.mkString(fileTypeToString(*type));
|
||||
}
|
||||
}
|
||||
|
||||
v.mkAttrs(attrs);
|
||||
|
@ -2665,14 +2732,9 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg
|
|||
|
||||
for (unsigned int n = 0; n < listSize; ++n) {
|
||||
Value * vElem = listElems[n];
|
||||
try {
|
||||
state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith");
|
||||
for (auto & attr : *vElem->attrs)
|
||||
attrsSeen[attr.name].first++;
|
||||
} catch (TypeError & e) {
|
||||
e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "zipAttrsWith"));
|
||||
state.debugThrowLastTrace(e);
|
||||
}
|
||||
state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith");
|
||||
for (auto & attr : *vElem->attrs)
|
||||
attrsSeen[attr.name].first++;
|
||||
}
|
||||
|
||||
auto attrs = state.buildBindings(attrsSeen.size());
|
||||
|
@ -3056,13 +3118,13 @@ static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Va
|
|||
auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList");
|
||||
|
||||
if (len < 0)
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("cannot create list of size %1%", len),
|
||||
.errPos = state.positions[pos]
|
||||
}));
|
||||
state.error("cannot create list of size %1%", len).debugThrow<EvalError>();
|
||||
|
||||
// More strict than striclty (!) necessary, but acceptable
|
||||
// as evaluating map without accessing any values makes little sense.
|
||||
state.forceFunction(*args[0], noPos, "while evaluating the first argument passed to builtins.genList");
|
||||
|
||||
state.mkList(v, len);
|
||||
|
||||
for (unsigned int n = 0; n < (unsigned int) len; ++n) {
|
||||
auto arg = state.allocValue();
|
||||
arg->mkInt(n);
|
||||
|
@ -3110,6 +3172,8 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
auto comparator = [&](Value * a, Value * b) {
|
||||
/* Optimization: if the comparator is lessThan, bypass
|
||||
callFunction. */
|
||||
/* TODO: (layus) this is absurd. An optimisation like this
|
||||
should be outside the lambda creation */
|
||||
if (args[0]->isPrimOp() && args[0]->primOp->fun == prim_lessThan)
|
||||
return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b);
|
||||
|
||||
|
@ -3270,12 +3334,7 @@ static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args,
|
|||
for (unsigned int n = 0; n < nrLists; ++n) {
|
||||
Value * vElem = args[1]->listElems()[n];
|
||||
state.callFunction(*args[0], *vElem, lists[n], pos);
|
||||
try {
|
||||
state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "while evaluating the return value of the function passed to buitlins.concatMap");
|
||||
} catch (TypeError &e) {
|
||||
e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "concatMap"));
|
||||
state.debugThrowLastTrace(e);
|
||||
}
|
||||
state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "while evaluating the return value of the function passed to buitlins.concatMap");
|
||||
len += lists[n].listSize();
|
||||
}
|
||||
|
||||
|
@ -3455,7 +3514,7 @@ static void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, V
|
|||
state.forceValue(*args[0], pos);
|
||||
state.forceValue(*args[1], pos);
|
||||
// pos is exact here, no need for a message.
|
||||
CompareValues comp(state, pos, "");
|
||||
CompareValues comp(state, noPos, "");
|
||||
v.mkBool(comp(args[0], args[1]));
|
||||
}
|
||||
|
||||
|
@ -3482,7 +3541,9 @@ static RegisterPrimOp primop_lessThan({
|
|||
static void prim_toString(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
PathSet context;
|
||||
auto s = state.coerceToString(pos, *args[0], context, true, false, "while evaluating the first argument passed to builtins.toString");
|
||||
auto s = state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the first argument passed to builtins.toString",
|
||||
true, false);
|
||||
v.mkString(*s, context);
|
||||
}
|
||||
|
||||
|
@ -3834,21 +3895,18 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
|
|||
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.replaceStrings");
|
||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.replaceStrings");
|
||||
if (args[0]->listSize() != args[1]->listSize())
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"),
|
||||
.errPos = state.positions[pos]
|
||||
}));
|
||||
state.error("'from' and 'to' arguments passed to builtins.replaceStrings have different lengths").atPos(pos).debugThrow<EvalError>();
|
||||
|
||||
std::vector<std::string> from;
|
||||
from.reserve(args[0]->listSize());
|
||||
for (auto elem : args[0]->listItems())
|
||||
from.emplace_back(state.forceString(*elem, pos, "while evaluating one of the strings to replace in builtins.replaceStrings"));
|
||||
from.emplace_back(state.forceString(*elem, pos, "while evaluating one of the strings to replace passed to builtins.replaceStrings"));
|
||||
|
||||
std::vector<std::pair<std::string, PathSet>> to;
|
||||
to.reserve(args[1]->listSize());
|
||||
for (auto elem : args[1]->listItems()) {
|
||||
PathSet ctx;
|
||||
auto s = state.forceString(*elem, ctx, pos, "while evaluating one of the replacement strings of builtins.replaceStrings");
|
||||
auto s = state.forceString(*elem, ctx, pos, "while evaluating one of the replacement strings passed to builtins.replaceStrings");
|
||||
to.emplace_back(s, std::move(ctx));
|
||||
}
|
||||
|
||||
|
|
|
@ -83,15 +83,13 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
|
|||
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
|
||||
auto contextInfos = std::map<StorePath, ContextInfo>();
|
||||
for (const auto & p : context) {
|
||||
Path drv;
|
||||
std::string output;
|
||||
NixStringContextElem ctx = NixStringContextElem::parse(*state.store, p);
|
||||
std::visit(overloaded {
|
||||
[&](NixStringContextElem::DrvDeep & d) {
|
||||
contextInfos[d.drvPath].allOutputs = true;
|
||||
},
|
||||
[&](NixStringContextElem::Built & b) {
|
||||
contextInfos[b.drvPath].outputs.emplace_back(std::move(output));
|
||||
contextInfos[b.drvPath].outputs.emplace_back(std::move(b.output));
|
||||
},
|
||||
[&](NixStringContextElem::Opaque & o) {
|
||||
contextInfos[o.path].path = true;
|
||||
|
|
|
@ -22,7 +22,9 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
|
|||
for (auto & attr : *args[0]->attrs) {
|
||||
std::string_view n(state.symbols[attr.name]);
|
||||
if (n == "url")
|
||||
url = state.coerceToString(attr.pos, *attr.value, context, false, false, "while evaluating the `url` attribute passed to builtins.fetchMercurial").toOwned();
|
||||
url = state.coerceToString(attr.pos, *attr.value, context,
|
||||
"while evaluating the `url` attribute passed to builtins.fetchMercurial",
|
||||
false, false).toOwned();
|
||||
else if (n == "rev") {
|
||||
// Ugly: unlike fetchGit, here the "rev" attribute can
|
||||
// be both a revision or a branch/tag name.
|
||||
|
@ -48,7 +50,9 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
|
|||
});
|
||||
|
||||
} else
|
||||
url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.fetchMercurial").toOwned();
|
||||
url = state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the first argument passed to builtins.fetchMercurial",
|
||||
false, false).toOwned();
|
||||
|
||||
// FIXME: git externals probably can be used to bypass the URI
|
||||
// whitelist. Ah well.
|
||||
|
|
|
@ -140,7 +140,7 @@ static void fetchTree(
|
|||
state.forceValue(*attr.value, attr.pos);
|
||||
|
||||
if (attr.value->type() == nPath || attr.value->type() == nString) {
|
||||
auto s = state.coerceToString(attr.pos, *attr.value, context, false, false, "").toOwned();
|
||||
auto s = state.coerceToString(attr.pos, *attr.value, context, "", false, false).toOwned();
|
||||
attrs.emplace(state.symbols[attr.name],
|
||||
state.symbols[attr.name] == "url"
|
||||
? type == "git"
|
||||
|
@ -166,7 +166,9 @@ static void fetchTree(
|
|||
|
||||
input = fetchers::Input::fromAttrs(std::move(attrs));
|
||||
} else {
|
||||
auto url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to the fetcher").toOwned();
|
||||
auto url = state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the first argument passed to the fetcher",
|
||||
false, false).toOwned();
|
||||
|
||||
if (type == "git") {
|
||||
fetchers::Attrs attrs;
|
||||
|
@ -255,6 +257,9 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
|
|||
} else
|
||||
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
|
||||
|
||||
if (who == "fetchTarball")
|
||||
url = evalSettings.resolvePseudoUrl(*url);
|
||||
|
||||
state.checkURI(*url);
|
||||
|
||||
if (name == "")
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -90,7 +90,7 @@ class ExternalValueBase
|
|||
/* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
|
||||
* error.
|
||||
*/
|
||||
virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const;
|
||||
virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const;
|
||||
|
||||
/* Compare to another value of the same type. Defaults to uncomparable,
|
||||
* i.e. always false.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue