diff options
Diffstat (limited to 'src/libexpr')
-rw-r--r-- | src/libexpr/eval.cc | 9 | ||||
-rw-r--r-- | src/libexpr/flake/flakeref.cc | 11 | ||||
-rw-r--r-- | src/libexpr/gc-alloc.hh | 4 | ||||
-rw-r--r-- | src/libexpr/get-drvs.cc | 288 | ||||
-rw-r--r-- | src/libexpr/get-drvs.hh | 3 | ||||
-rw-r--r-- | src/libexpr/json-to-value.cc | 28 | ||||
-rw-r--r-- | src/libexpr/parser/parser.cc | 1 | ||||
-rw-r--r-- | src/libexpr/value.hh | 398 |
8 files changed, 623 insertions, 119 deletions
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index c0e7a9a2e..a925ce2d8 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -494,6 +494,14 @@ std::ostream & operator<<(std::ostream & output, PrimOp & primOp) } +Value::Value(primop_t, PrimOp & primop) + : internalType(tPrimOp) + , primOp(&primop) + , _primop_pad(0) +{ + primop.check(); +} + PrimOp * Value::primOpAppPrimOp() const { Value * left = primOpApp.left; @@ -506,7 +514,6 @@ PrimOp * Value::primOpAppPrimOp() const return left->primOp; } - void Value::mkPrimOp(PrimOp * p) { p->check(); diff --git a/src/libexpr/flake/flakeref.cc b/src/libexpr/flake/flakeref.cc index a95df04ba..3be4ea550 100644 --- a/src/libexpr/flake/flakeref.cc +++ b/src/libexpr/flake/flakeref.cc @@ -169,14 +169,13 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment( if (subdir != "") { if (parsedURL.query.count("dir")) throw Error("flake URL '%s' has an inconsistent 'dir' parameter", url); - parsedURL.query.insert_or_assign("dir", subdir); } if (pathExists(flakeRoot + "/.git/shallow")) parsedURL.query.insert_or_assign("shallow", "1"); return std::make_pair( - FlakeRef(Input::fromURL(parsedURL, isFlake), getOr(parsedURL.query, "dir", "")), + FlakeRef(Input::fromURL(parsedURL, isFlake), subdir), fragment); } @@ -204,7 +203,13 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment( std::string fragment; std::swap(fragment, parsedURL.fragment); - auto input = Input::fromURL(parsedURL, isFlake); + // This has a special meaning for flakes and must not be passed to libfetchers. + // Of course this means that libfetchers cannot have fetchers + // expecting an argument `dir` 🫠+ ParsedURL urlForFetchers(parsedURL); + urlForFetchers.query.erase("dir"); + + auto input = Input::fromURL(urlForFetchers, isFlake); input.parent = baseDir; return std::make_pair( diff --git a/src/libexpr/gc-alloc.hh b/src/libexpr/gc-alloc.hh index 04ac28ea8..fc034045f 100644 --- a/src/libexpr/gc-alloc.hh +++ b/src/libexpr/gc-alloc.hh @@ -10,6 +10,8 @@ #include <string_view> #include <vector> +#include "checked-arithmetic.hh" + #if HAVE_BOEHMGC #include <functional> // std::less #include <utility> // std::pair @@ -18,8 +20,6 @@ #include <gc/gc_allocator.h> #include <gc/gc_cpp.h> -#include "checked-arithmetic.hh" - /// calloc, transparently GC-enabled. #define LIX_GC_CALLOC(size) GC_MALLOC(size) diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 8e3969aac..d7869d09b 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -1,6 +1,7 @@ #include "get-drvs.hh" #include "eval-inline.hh" #include "derivations.hh" +#include "eval.hh" #include "store-api.hh" #include "path-with-outputs.hh" @@ -101,66 +102,134 @@ StorePath DrvInfo::queryOutPath() return *outPath; } +void DrvInfo::fillOutputs(bool withPaths) +{ + auto fillDefault = [&]() { + std::optional<StorePath> outPath = std::nullopt; + if (withPaths) { + outPath.emplace(this->queryOutPath()); + } + this->outputs.emplace("out", outPath); + }; + + // lol. lmao even. + if (this->attrs == nullptr) { + fillDefault(); + return; + } + + Attr * outputs = this->attrs->get(this->state->sOutputs); + if (outputs == nullptr) { + fillDefault(); + return; + } + + // NOTE(Qyriad): I don't think there is any codepath that can cause this to error. + this->state->forceList( + *outputs->value, + outputs->pos, + "while evaluating the 'outputs' attribute of a derivation" + ); + + 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 + ); + + if (withPaths) { + // Find the attr with this output's name... + Attr * out = this->attrs->get(this->state->symbols.create(outputName)); + if (out == nullptr) { + // FIXME: throw error? + continue; + } + + // Meanwhile we couldn't figure out any circumstances + // that cause this to error. + state->forceAttrs(*out->value, outputs->pos, errMsg); + + // ...and evaluate its `outPath` attribute. + Attr * outPath = out->value->attrs->get(this->state->sOutPath); + if (outPath == nullptr) { + continue; + // FIXME: throw error? + } + + 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 + ); + this->outputs.emplace(outputName, storePath); + } else { + this->outputs.emplace(outputName, std::nullopt); + } + } +} DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall) { + // If we haven't already cached the outputs set, then do so now. if (outputs.empty()) { - /* Get the ‘outputs’ list. */ - Bindings::iterator i; - if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) { - state->forceList(*i->value, i->pos, "while evaluating the 'outputs' attribute of a derivation"); - - /* For each output... */ - for (auto elem : i->value->listItems()) { - std::string output(state->forceStringNoCtx(*elem, i->pos, "while evaluating the name of an output of a derivation")); - - if (withPaths) { - /* Evaluate the corresponding set. */ - Bindings::iterator out = attrs->find(state->symbols.create(output)); - if (out == attrs->end()) continue; // FIXME: throw error? - state->forceAttrs(*out->value, i->pos, "while evaluating an output of a derivation"); - - /* And evaluate its ‘outPath’ attribute. */ - Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); - if (outPath == out->value->attrs->end()) continue; // FIXME: throw error? - NixStringContext context; - outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation")); - } else - outputs.emplace(output, std::nullopt); - } - } else - outputs.emplace("out", withPaths ? std::optional{queryOutPath()} : std::nullopt); + // FIXME: this behavior seems kind of busted, since whether or not this + // DrvInfo will have paths is forever determined by the *first* call to + // this function?? + fillOutputs(withPaths); } + // Things that operate on derivations like packages, like `nix-env` and `nix build`, + // allow derivations to specify which outputs should be used in those user-facing + // cases if the user didn't specify an output explicitly. + // If the caller just wanted all the outputs for this derivation, though, + // then we're done here. if (!onlyOutputsToInstall || !attrs) return outputs; - Bindings::iterator i; - if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos, "while evaluating the 'outputSpecified' attribute of a derivation")) { - Outputs result; - auto out = outputs.find(queryOutputName()); - if (out == outputs.end()) - throw Error("derivation does not have output '%s'", queryOutputName()); - result.insert(*out); - return result; + // Regardless of `meta.outputsToInstall`, though, you can select into a derivation + // output by its attribute, e.g. `pkgs.lix.dev`, which (lol?) sets the magic + // attribute `outputSpecified = true`, and changes the `outputName` attr to the + // explicitly selected-into output. + if (Attr * outSpecAttr = attrs->get(state->sOutputSpecified)) { + bool outputSpecified = this->state->forceBool( + *outSpecAttr->value, + outSpecAttr->pos, + "while evaluating the 'outputSpecified' attribute of a derivation" + ); + if (outputSpecified) { + auto maybeOut = outputs.find(queryOutputName()); + if (maybeOut == outputs.end()) { + throw Error("derivation does not have output '%s'", queryOutputName()); + } + return Outputs{*maybeOut}; + } } - else { - /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */ - const Value * outTI = queryMeta("outputsToInstall"); - if (!outTI) return outputs; - auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'"); - /* ^ this shows during `nix-env -i` right under the bad derivation */ - if (!outTI->isList()) throw errMsg; - Outputs result; - for (auto elem : outTI->listItems()) { - if (elem->type() != nString) throw errMsg; - auto out = outputs.find(elem->string.s); - if (out == outputs.end()) throw errMsg; - result.insert(*out); - } - return result; + /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */ + const Value * outTI = queryMeta("outputsToInstall"); + if (!outTI) return outputs; + auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'"); + /* ^ this shows during `nix-env -i` right under the bad derivation */ + if (!outTI->isList()) throw errMsg; + Outputs result; + for (auto elem : outTI->listItems()) { + if (elem->type() != nString) throw errMsg; + auto out = outputs.find(elem->string.s); + if (out == outputs.end()) throw errMsg; + result.insert(*out); } + return result; } @@ -350,56 +419,95 @@ static void getDerivations(EvalState & state, Value & vIn, Value v; state.autoCallFunction(autoArgs, vIn, v); - /* Process the expression. */ - if (!getDerivation(state, v, pathPrefix, drvs, ignoreAssertionFailures)) ; - - else if (v.type() == nAttrs) { - - /* Dont consider sets we've already seen, e.g. y in - `rec { x.d = derivation {...}; y = x; }`. */ - if (!done.insert(v.attrs).second) return; - - /* !!! undocumented hackery to support combining channels in - nix-env.cc. */ - bool combineChannels = v.attrs->find(state.symbols.create("_combineChannels")) != v.attrs->end(); - - /* 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 & i : v.attrs->lexicographicOrder(state.symbols)) { - debug("evaluating attribute '%1%'", state.symbols[i->name]); - if (!std::regex_match(std::string(state.symbols[i->name]), attrRegex)) - continue; - std::string pathPrefix2 = addToPath(pathPrefix, state.symbols[i->name]); - if (combineChannels) - getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); - else if (getDerivation(state, *i->value, pathPrefix2, 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 (i->value->type() == nAttrs) { - Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations); - if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`")) - getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); - } - } - } + bool shouldRecurse = getDerivation(state, v, pathPrefix, drvs, ignoreAssertionFailures); + if (!shouldRecurse) { + // We're done here. + return; } - else if (v.type() == nList) { + 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())) { - std::string pathPrefix2 = addToPath(pathPrefix, fmt("%d", n)); - if (getDerivation(state, *elem, pathPrefix2, drvs, ignoreAssertionFailures)) - getDerivations(state, *elem, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); + std::string joinedAttrPath = addToPath(pathPrefix, fmt("%d", n)); + bool shouldRecurse = getDerivation(state, *elem, joinedAttrPath, drvs, ignoreAssertionFailures); + if (shouldRecurse) { + getDerivations( + state, + *elem, + joinedAttrPath, + autoArgs, + drvs, + done, + ignoreAssertionFailures + ); + } } + + return; + } else if (v.type() != nAttrs) { + state.error<TypeError>( + "expression was expected to be a derivation or collection of derivations, but instead was %s", + showType(v.type(), true) + ).debugThrow(); + } + + /* Dont consider sets we've already seen, e.g. y in + `rec { x.d = derivation {...}; y = x; }`. */ + auto const &[_, didInsert] = done.insert(v.attrs); + if (!didInsert) { + return; } - else - state.error<TypeError>("expression does not evaluate to a derivation (or a set or list of those)").debugThrow(); + // FIXME: what the fuck??? + /* !!! undocumented hackery to support combining channels in + nix-env.cc. */ + bool combineChannels = v.attrs->find(state.symbols.create("_combineChannels")) != v.attrs->end(); + + /* 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.symbols)) { + debug("evaluating attribute '%1%'", state.symbols[attr->name]); + // FIXME: only consider attrs with identifier-like names?? Why??? + if (!std::regex_match(std::string(state.symbols[attr->name]), attrRegex)) { + continue; + } + std::string joinedAttrPath = addToPath(pathPrefix, state.symbols[attr->name]); + if (combineChannels) { + getDerivations(state, *attr->value, joinedAttrPath, autoArgs, drvs, done, 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) { + Attr * recurseForDrvs = attr->value->attrs->get(state.sRecurseForDerivations); + if (recurseForDrvs == nullptr) { + continue; + } + bool shouldRecurse = state.forceBool( + *recurseForDrvs->value, + attr->pos, + fmt("while evaluating the '%s' attribute", Magenta("recurseForDerivations")) + ); + if (!shouldRecurse) { + continue; + } + + getDerivations( + state, + *attr->value, + joinedAttrPath, + autoArgs, + drvs, + done, + ignoreAssertionFailures + ); + } + } + } } diff --git a/src/libexpr/get-drvs.hh b/src/libexpr/get-drvs.hh index 44aac3015..fd927b9e5 100644 --- a/src/libexpr/get-drvs.hh +++ b/src/libexpr/get-drvs.hh @@ -37,13 +37,14 @@ private: bool checkMeta(Value & v); + void fillOutputs(bool withPaths = true); + public: /** * path towards the derivation */ std::string attrPath; - DrvInfo(EvalState & state) : state(&state) { }; DrvInfo(EvalState & state, std::string attrPath, Bindings * attrs); DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs); diff --git a/src/libexpr/json-to-value.cc b/src/libexpr/json-to-value.cc index b69216a48..2309b6c97 100644 --- a/src/libexpr/json-to-value.cc +++ b/src/libexpr/json-to-value.cc @@ -82,28 +82,28 @@ 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 { if (val_ > std::numeric_limits<NixInt::Inner>::max()) { throw Error("unsigned json number %1% outside of Nix integer range", val_); @@ -114,14 +114,14 @@ public: 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(); @@ -129,7 +129,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); @@ -137,35 +137,37 @@ 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()); } }; diff --git a/src/libexpr/parser/parser.cc b/src/libexpr/parser/parser.cc index e60cf967f..a00586c36 100644 --- a/src/libexpr/parser/parser.cc +++ b/src/libexpr/parser/parser.cc @@ -43,7 +43,6 @@ error_message_for(p::one<'}'>) = "expecting '}'"; error_message_for(p::one<'"'>) = "expecting '\"'"; error_message_for(p::one<';'>) = "expecting ';'"; error_message_for(p::one<')'>) = "expecting ')'"; -error_message_for(p::one<'='>) = "expecting '='"; error_message_for(p::one<']'>) = "expecting ']'"; error_message_for(p::one<':'>) = "expecting ':'"; error_message_for(p::string<'\'', '\''>) = "expecting \"''\""; diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index c35f88f8d..57485aa0a 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -3,6 +3,9 @@ #include <cassert> #include <climits> +#include <functional> +#include <ranges> +#include <span> #include "gc-alloc.hh" #include "symbol-table.hh" @@ -11,6 +14,7 @@ #include "source-path.hh" #include "print-options.hh" #include "checked-arithmetic.hh" +#include "concepts.hh" #include <nlohmann/json_fwd.hpp> @@ -132,6 +136,55 @@ class ExternalValueBase std::ostream & operator << (std::ostream & str, const ExternalValueBase & v); +extern ExprBlackHole eBlackHole; + +struct NewValueAs +{ + struct integer_t { }; + constexpr static integer_t integer{}; + + struct floating_t { }; + constexpr static floating_t floating{}; + + struct boolean_t { }; + constexpr static boolean_t boolean{}; + + struct string_t { }; + constexpr static string_t string{}; + + struct path_t { }; + constexpr static path_t path{}; + + struct list_t { }; + constexpr static list_t list{}; + + struct attrs_t { }; + constexpr static attrs_t attrs{}; + + struct thunk_t { }; + constexpr static thunk_t thunk{}; + + struct null_t { }; + constexpr static null_t null{}; + + struct app_t { }; + constexpr static app_t app{}; + + struct primop_t { }; + constexpr static primop_t primop{}; + + struct primOpApp_t { }; + constexpr static primOpApp_t primOpApp{}; + + struct lambda_t { }; + constexpr static lambda_t lambda{}; + + struct external_t { }; + constexpr static external_t external{}; + + struct blackhole_t { }; + constexpr static blackhole_t blackhole{}; +}; struct Value { @@ -142,6 +195,315 @@ private: public: + // Discount `using NewValueAs::*;` +#define USING_VALUETYPE(name) using name = NewValueAs::name + USING_VALUETYPE(integer_t); + USING_VALUETYPE(floating_t); + USING_VALUETYPE(boolean_t); + USING_VALUETYPE(string_t); + USING_VALUETYPE(path_t); + USING_VALUETYPE(list_t); + USING_VALUETYPE(attrs_t); + USING_VALUETYPE(thunk_t); + USING_VALUETYPE(primop_t); + USING_VALUETYPE(app_t); + USING_VALUETYPE(null_t); + USING_VALUETYPE(primOpApp_t); + USING_VALUETYPE(lambda_t); + USING_VALUETYPE(external_t); + USING_VALUETYPE(blackhole_t); +#undef USING_VALUETYPE + + /// Default constructor which is still used in the codebase but should not + /// be used in new code. Zero initializes its members. + [[deprecated]] Value() + : internalType(static_cast<InternalType>(0)) + , _empty{ 0, 0 } + { } + + /// Constructs a nix language value of type "int", with the integral value + /// of @ref i. + Value(integer_t, NixInt i) + : internalType(tInt) + , _empty{ 0, 0 } + { + // the NixInt ctor here is is special because NixInt has a ctor too, so + // we're not allowed to have it as an anonymous aggreagte member. we do + // however still have the option to clear the data members using _empty + // and leaving the second word of data cleared by setting only integer. + integer = i; + } + + /// Constructs a nix language value of type "float", with the floating + /// point value of @ref f. + Value(floating_t, NixFloat f) + : internalType(tFloat) + , fpoint(f) + , _float_pad(0) + { } + + /// Constructs a nix language value of type "bool", with the boolean + /// value of @ref b. + Value(boolean_t, bool b) + : internalType(tBool) + , boolean(b) + , _bool_pad(0) + { } + + /// Constructs a nix language value of type "string", with the value of the + /// C-string pointed to by @ref strPtr, and optionally with an array of + /// string context pointed to by @ref contextPtr. + /// + /// Neither the C-string nor the context array are copied; this constructor + /// assumes suitable memory has already been allocated (with the GC if + /// enabled), and string and context data copied into that memory. + Value(string_t, char const * strPtr, char const ** contextPtr = nullptr) + : internalType(tString) + , string({ .s = strPtr, .context = contextPtr }) + { } + + /// Constructx a nix language value of type "string", with a copy of the + /// string data viewed by @ref copyFrom. + /// + /// The string data *is* copied from @ref copyFrom, and this constructor + /// performs a dynamic (GC) allocation to do so. + Value(string_t, std::string_view copyFrom, NixStringContext const & context = {}) + : internalType(tString) + , string({ .s = gcCopyStringIfNeeded(copyFrom), .context = nullptr }) + { + if (context.empty()) { + // It stays nullptr. + return; + } + + // Copy the context. + this->string.context = gcAllocType<char const *>(context.size() + 1); + + size_t n = 0; + for (NixStringContextElem const & contextElem : context) { + this->string.context[n] = gcCopyStringIfNeeded(contextElem.to_string()); + n += 1; + } + + // Terminator sentinel. + this->string.context[n] = nullptr; + } + + /// Constructx a nix language value of type "string", with the value of the + /// C-string pointed to by @ref strPtr, and optionally with a set of string + /// context @ref context. + /// + /// The C-string is not copied; this constructor assumes suitable memory + /// has already been allocated (with the GC if enabled), and string data + /// has been copied into that memory. The context data *is* copied from + /// @ref context, and this constructor performs a dynamic (GC) allocation + /// to do so. + Value(string_t, char const * strPtr, NixStringContext const & context) + : internalType(tString) + , string({ .s = strPtr, .context = nullptr }) + { + if (context.empty()) { + // It stays nullptr + return; + } + + // Copy the context. + this->string.context = gcAllocType<char const *>(context.size() + 1); + + size_t n = 0; + for (NixStringContextElem const & contextElem : context) { + this->string.context[n] = gcCopyStringIfNeeded(contextElem.to_string()); + n += 1; + } + + // Terminator sentinel. + this->string.context[n] = nullptr; + } + + /// Constructs a nix language value of type "path", with the value of the + /// C-string pointed to by @ref strPtr. + /// + /// The C-string is not copied; this constructor assumes suitable memory + /// has already been allocated (with the GC if enabled), and string data + /// has been copied into that memory. + Value(path_t, char const * strPtr) + : internalType(tPath) + , _path(strPtr) + , _path_pad(0) + { } + + /// Constructs a nix language value of type "path", with the path + /// @ref path. + /// + /// The data from @ref path *is* copied, and this constructor performs a + /// dynamic (GC) allocation to do so. + Value(path_t, SourcePath const & path) + : internalType(tPath) + , _path(gcCopyStringIfNeeded(path.path.abs())) + , _path_pad(0) + { } + + /// Constructs a nix language value of type "list", with element array + /// @ref items. + /// + /// Generally, the data in @ref items is neither deep copied nor shallow + /// copied. This construct assumes the std::span @ref items is a region of + /// memory that has already been allocated (with the GC if enabled), and + /// an array of valid Value pointers has been copied into that memory. + /// + /// Howver, as an implementation detail, if @ref items is only 2 items or + /// smaller, the list is stored inline, and the Value pointers in + /// @ref items are shallow copied into this structure, without dynamically + /// allocating memory. + Value(list_t, std::span<Value *> items) + { + if (items.size() == 1) { + this->internalType = tList1; + this->smallList[0] = items[0]; + this->smallList[1] = nullptr; + } else if (items.size() == 2) { + this->internalType = tList2; + this->smallList[0] = items[0]; + this->smallList[1] = items[1]; + } else { + this->internalType = tListN; + this->bigList.size = items.size(); + this->bigList.elems = items.data(); + } + } + + /// Constructs a nix language value of type "list", with an element array + /// initialized by applying @ref transformer to each element in @ref items. + /// + /// This allows "in-place" construction of a nix list when some logic is + /// needed to get each Value pointer. This constructor dynamically (GC) + /// allocates memory for the size of @ref items, and the Value pointers + /// returned by @ref transformer are shallow copied into it. + template< + std::ranges::sized_range SizedIterableT, + InvocableR<Value *, typename SizedIterableT::value_type const &> TransformerT + > + Value(list_t, SizedIterableT & items, TransformerT const & transformer) + { + if (items.size() == 1) { + this->internalType = tList1; + this->smallList[0] = transformer(*items.begin()); + this->smallList[1] = nullptr; + } else if (items.size() == 2) { + this->internalType = tList2; + auto it = items.begin(); + this->smallList[0] = transformer(*it); + it++; + this->smallList[1] = transformer(*it); + } else { + this->internalType = tListN; + this->bigList.size = items.size(); + this->bigList.elems = gcAllocType<Value *>(items.size()); + auto it = items.begin(); + for (size_t i = 0; i < items.size(); i++, it++) { + this->bigList.elems[i] = transformer(*it); + } + } + } + + /// Constructs a nix language value of the singleton type "null". + Value(null_t) + : internalType(tNull) + , _empty{0, 0} + { } + + /// Constructs a nix language value of type "set", with the attribute + /// bindings pointed to by @ref bindings. + /// + /// The bindings are not not copied; this constructor assumes @ref bindings + /// has already been suitably allocated by something like nix::buildBindings. + Value(attrs_t, Bindings * bindings) + : internalType(tAttrs) + , attrs(bindings) + , _attrs_pad(0) + { } + + /// Constructs a nix language lazy delayed computation, or "thunk". + /// + /// The thunk stores the environment it will be computed in @ref env, and + /// the expression that will need to be evaluated @ref expr. + Value(thunk_t, Env & env, Expr & expr) + : internalType(tThunk) + , thunk({ .env = &env, .expr = &expr }) + { } + + /// Constructs a nix language value of type "lambda", which represents + /// a builtin, primitive operation ("primop"), from the primop + /// implemented by @ref primop. + Value(primop_t, PrimOp & primop); + + /// Constructs a nix language value of type "lambda", which represents a + /// partially applied primop. + Value(primOpApp_t, Value & lhs, Value & rhs) + : internalType(tPrimOpApp) + , primOpApp({ .left = &lhs, .right = &rhs }) + { } + + /// Constructs a nix language value of type "lambda", which represents a + /// lazy partial application of another lambda. + Value(app_t, Value & lhs, Value & rhs) + : internalType(tApp) + , app({ .left = &lhs, .right = &rhs }) + { } + + /// Constructs a nix language value of type "external", which is only used + /// by plugins. Do any existing plugins even use this mechanism? + Value(external_t, ExternalValueBase & external) + : internalType(tExternal) + , external(&external) + , _external_pad(0) + { } + + /// Constructs a nix language value of type "lambda", which represents a + /// run of the mill lambda defined in nix code. + /// + /// This takes the environment the lambda is closed over @ref env, and + /// the lambda expression itself @ref lambda, which will not be evaluated + /// until it is applied. + Value(lambda_t, Env & env, ExprLambda & lambda) + : internalType(tLambda) + , lambda({ .env = &env, .fun = &lambda }) + { } + + /// Constructs an evil thunk, whose evaluation represents infinite recursion. + explicit Value(blackhole_t) + : internalType(tThunk) + , thunk({ .env = nullptr, .expr = reinterpret_cast<Expr *>(&eBlackHole) }) + { } + + Value(Value const & rhs) = default; + + /// Move constructor. Does the same thing as the copy constructor, but + /// also zeroes out the other Value. + Value(Value && rhs) + : internalType(rhs.internalType) + , _empty{ 0, 0 } + { + *this = std::move(rhs); + } + + Value & operator=(Value const & rhs) = default; + + /// Move assignment operator. + /// Does the same thing as the copy assignment operator, but also zeroes out + /// the rhs. + inline Value & operator=(Value && rhs) + { + *this = static_cast<const Value &>(rhs); + if (this != &rhs) { + // Kill `rhs`, because non-destructive move lol. + rhs.internalType = static_cast<InternalType>(0); + rhs._empty[0] = 0; + rhs._empty[1] = 0; + } + return *this; + } + void print(EvalState &state, std::ostream &str, PrintOptions options = PrintOptions {}); // Functions needed to distinguish the type @@ -160,8 +522,15 @@ public: union { + /// Dummy field, which takes up as much space as the largest union variants + /// to set the union's memory to zeroed memory. + uintptr_t _empty[2]; + NixInt integer; - bool boolean; + struct { + bool boolean; + uintptr_t _bool_pad; + }; /** * Strings in the evaluator carry a so-called `context` which @@ -190,8 +559,14 @@ public: const char * * context; // must be in sorted order } string; - const char * _path; - Bindings * attrs; + struct { + const char * _path; + uintptr_t _path_pad; + }; + struct { + Bindings * attrs; + uintptr_t _attrs_pad; + }; struct { size_t size; Value * * elems; @@ -208,12 +583,21 @@ public: Env * env; ExprLambda * fun; } lambda; - PrimOp * primOp; + struct { + PrimOp * primOp; + uintptr_t _primop_pad; + }; struct { Value * left, * right; } primOpApp; - ExternalValueBase * external; - NixFloat fpoint; + struct { + ExternalValueBase * external; + uintptr_t _external_pad; + }; + struct { + NixFloat fpoint; + uintptr_t _float_pad; + }; }; /** @@ -449,8 +833,6 @@ public: }; -extern ExprBlackHole eBlackHole; - bool Value::isBlackhole() const { return internalType == tThunk && thunk.expr == (Expr*) &eBlackHole; |