diff --git a/.gitignore b/.gitignore index 58e7377fb..db363aefd 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,7 @@ perl/Makefile.config /tests/shell.drv /tests/config.nix /tests/ca/config.nix +/tests/repl-result-out # /tests/lang/ /tests/lang/*.out diff --git a/.version b/.version index 6533b6687..f3ac133c5 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.8.0 \ No newline at end of file +2.9.0 \ No newline at end of file diff --git a/doc/manual/src/SUMMARY.md.in b/doc/manual/src/SUMMARY.md.in index f0f9457d2..860222337 100644 --- a/doc/manual/src/SUMMARY.md.in +++ b/doc/manual/src/SUMMARY.md.in @@ -72,6 +72,7 @@ - [CLI guideline](contributing/cli-guideline.md) - [Release Notes](release-notes/release-notes.md) - [Release X.Y (202?-??-??)](release-notes/rl-next.md) + - [Release 2.8 (2022-04-19)](release-notes/rl-2.8.md) - [Release 2.7 (2022-03-07)](release-notes/rl-2.7.md) - [Release 2.6 (2022-01-24)](release-notes/rl-2.6.md) - [Release 2.5 (2021-12-13)](release-notes/rl-2.5.md) diff --git a/doc/manual/src/release-notes/rl-2.8.md b/doc/manual/src/release-notes/rl-2.8.md new file mode 100644 index 000000000..9778e8c3a --- /dev/null +++ b/doc/manual/src/release-notes/rl-2.8.md @@ -0,0 +1,53 @@ +# Release 2.8 (2022-04-19) + +* New experimental command: `nix fmt`, which applies a formatter + defined by the `formatter.` flake output to the Nix + expressions in a flake. + +* Various Nix commands can now read expressions from standard input + using `--file -`. + +* New experimental builtin function `builtins.fetchClosure` that + copies a closure from a binary cache at evaluation time and rewrites + it to content-addressed form (if it isn't already). Like + `builtins.storePath`, this allows importing pre-built store paths; + the difference is that it doesn't require the user to configure + binary caches and trusted public keys. + + This function is only available if you enable the experimental + feature `fetch-closure`. + +* New experimental feature: *impure derivations*. These are + derivations that can produce a different result every time they're + built. Here is an example: + + ```nix + stdenv.mkDerivation { + name = "impure"; + __impure = true; # marks this derivation as impure + buildCommand = "date > $out"; + } + ``` + + Running `nix build` twice on this expression will build the + derivation twice, producing two different content-addressed store + paths. Like fixed-output derivations, impure derivations have access + to the network. Only fixed-output derivations and impure derivations + can depend on an impure derivation. + +* `nix store make-content-addressable` has been renamed to `nix store + make-content-addressed`. + +* The `nixosModule` flake output attribute has been renamed consistent + with the `.default` renames in Nix 2.7. + + * `nixosModule` → `nixosModules.default` + + As before, the old output will continue to work, but `nix flake check` will + issue a warning about it. + +* `nix run` is now stricter in what it accepts: members of the `apps` + flake output are now required to be apps (as defined in [the + manual](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-run.html#apps)), + and members of `packages` or `legacyPackages` must be derivations + (not apps). diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 8c8c0fd41..7b3ad4e58 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -1,42 +1,16 @@ # Release X.Y (202?-??-??) -* Various nix commands can now read expressions from stdin with `--file -`. +* Nix now provides better integration with zsh's run-help feature. It is now + included in the Nix installation in the form of an autoloadable shell + function, run-help-nix. It picks up Nix subcommands from the currently typed + in command and directs the user to the associated man pages. -* `nix store make-content-addressable` has been renamed to `nix store - make-content-addressed`. +* `nix repl` has a new build-'n-link (`:bl`) command that builds a derivation + while creating GC root symlinks. -* New experimental builtin function `builtins.fetchClosure` that - copies a closure from a binary cache at evaluation time and rewrites - it to content-addressed form (if it isn't already). Like - `builtins.storePath`, this allows importing pre-built store paths; - the difference is that it doesn't require the user to configure - binary caches and trusted public keys. +* The path produced by `builtins.toFile` is now allowed to be imported or read + even with restricted evaluation. Note that this will not work with a + read-only store. - This function is only available if you enable the experimental - feature `fetch-closure`. - -* New experimental feature: *impure derivations*. These are - derivations that can produce a different result every time they're - built. Here is an example: - - ```nix - stdenv.mkDerivation { - name = "impure"; - __impure = true; # marks this derivation as impure - buildCommand = "date > $out"; - } - ``` - - Running `nix build` twice on this expression will build the - derivation twice, producing two different content-addressed store - paths. Like fixed-output derivations, impure derivations have access - to the network. Only fixed-output derivations and impure derivations - can depend on an impure derivation. - -* The `nixosModule` flake output attribute has been renamed consistent - with the `.default` renames in nix 2.7. - - * `nixosModule` → `nixosModules.default` - - As before, the old output will continue to work, but `nix flake check` will - issue a warning about it. +* `nix build` has a new `--print-out-paths` flag to print the resulting output paths. + This matches the default behaviour of `nix-build`. diff --git a/docker.nix b/docker.nix index 251bd2f46..0cd64856f 100644 --- a/docker.nix +++ b/docker.nix @@ -22,6 +22,7 @@ let findutils iana-etc git + openssh ]; users = { diff --git a/flake.nix b/flake.nix index 87b00edf4..dd3a25e9e 100644 --- a/flake.nix +++ b/flake.nix @@ -23,7 +23,7 @@ crossSystems = [ "armv6l-linux" "armv7l-linux" ]; - stdenvs = [ "gccStdenv" "clangStdenv" "clang11Stdenv" "stdenv" ]; + stdenvs = [ "gccStdenv" "clangStdenv" "clang11Stdenv" "stdenv" "libcxxStdenv" ]; forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); forAllSystemsAndStdenvs = f: forAllSystems (system: diff --git a/misc/bash/completion.sh b/misc/bash/completion.sh index 045053dee..9af695f5a 100644 --- a/misc/bash/completion.sh +++ b/misc/bash/completion.sh @@ -15,7 +15,7 @@ function _complete_nix { else COMPREPLY+=("$completion") fi - done < <(NIX_GET_COMPLETIONS=$cword "${words[@]/#\~/$HOME}" 2>/dev/null) + done < <(NIX_GET_COMPLETIONS=$cword "${words[@]}" 2>/dev/null) __ltrim_colon_completions "$cur" } diff --git a/misc/zsh/local.mk b/misc/zsh/local.mk index 418fb1377..0b4e294fb 100644 --- a/misc/zsh/local.mk +++ b/misc/zsh/local.mk @@ -1 +1,2 @@ $(eval $(call install-file-as, $(d)/completion.zsh, $(datarootdir)/zsh/site-functions/_nix, 0644)) +$(eval $(call install-file-as, $(d)/run-help-nix, $(datarootdir)/zsh/site-functions/run-help-nix, 0644)) diff --git a/misc/zsh/run-help-nix b/misc/zsh/run-help-nix new file mode 100644 index 000000000..f91a304eb --- /dev/null +++ b/misc/zsh/run-help-nix @@ -0,0 +1,42 @@ +emulate -L zsh + +# run-help is a zsh widget that can be bound to a key. It mainly looks up the +# man page for the currently typed in command. +# +# Although run-help works for any command without requiring special support, +# it can only deduce the right man page based solely on the name of the +# command. Programs like Nix provide better integration with run-help by +# helping zsh identify Nix subcommands and their corresponding man pages. This +# is what this function does. +# +# To actually use run-help on zsh, place the following lines in your .zshrc: +# +# (( $+aliases[run-help] )) && unalias run-help +# autoload -Uz run-help run-help-nix +# +# Then also assign run-help to any key of choice: +# +# bindkey '^[h' run-help + +while [[ "$#" != 0 && "$1" == -* ]]; do + shift +done + +local -a subcommands; subcommands=( nix3 ) + +local arg +for arg in "$@"; do + if man -w "${(j:-:)subcommands}-$arg" >/dev/null 2>&1; then + subcommands+="$arg" + else + break + fi +done + +if (( $#subcommands > 1 )); then + man "${(j:-:)subcommands}" +else + man nix +fi + +return $? diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc index 5ef3b4bc6..82b35d16f 100644 --- a/src/libcmd/command.cc +++ b/src/libcmd/command.cc @@ -228,17 +228,17 @@ void StorePathCommand::run(ref store, std::vector && storePath run(store, *storePaths.begin()); } -Strings editorFor(const Pos & pos) +Strings editorFor(const Path & file, uint32_t line) { auto editor = getEnv("EDITOR").value_or("cat"); auto args = tokenizeString(editor); - if (pos.line > 0 && ( + if (line > 0 && ( editor.find("emacs") != std::string::npos || editor.find("nano") != std::string::npos || editor.find("vim") != std::string::npos || editor.find("kak") != std::string::npos)) - args.push_back(fmt("+%d", pos.line)); - args.push_back(pos.file); + args.push_back(fmt("+%d", line)); + args.push_back(file); return args; } diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh index 826531a18..db4d4c023 100644 --- a/src/libcmd/command.hh +++ b/src/libcmd/command.hh @@ -137,7 +137,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand std::optional getFlakeRefForCompletion() override { - return parseFlakeRef(_installable, absPath(".")); + return parseFlakeRefWithFragment(_installable, absPath(".")).first; } private: @@ -221,7 +221,7 @@ static RegisterCommand registerCommand2(std::vector && name) /* Helper function to generate args that invoke $EDITOR on filename:lineno. */ -Strings editorFor(const Pos & pos); +Strings editorFor(const Path & file, uint32_t line); struct MixProfile : virtual StoreCommand { diff --git a/src/libexpr/common-eval-args.cc b/src/libcmd/common-eval-args.cc similarity index 95% rename from src/libexpr/common-eval-args.cc rename to src/libcmd/common-eval-args.cc index e50ff244c..5b6e82388 100644 --- a/src/libexpr/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -7,6 +7,7 @@ #include "registry.hh" #include "flake/flakeref.hh" #include "store-api.hh" +#include "command.hh" namespace nix { @@ -59,6 +60,9 @@ MixEvalArgs::MixEvalArgs() fetchers::Attrs extraAttrs; if (to.subdir != "") extraAttrs["dir"] = to.subdir; fetchers::overrideRegistry(from.input, to.input, extraAttrs); + }}, + .completer = {[&](size_t, std::string_view prefix) { + completeFlakeRef(openStore(), prefix); }} }); diff --git a/src/libexpr/common-eval-args.hh b/src/libcmd/common-eval-args.hh similarity index 100% rename from src/libexpr/common-eval-args.hh rename to src/libcmd/common-eval-args.hh diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 4e7262432..e3210d18d 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -1,5 +1,6 @@ #include "globals.hh" #include "installables.hh" +#include "util.hh" #include "command.hh" #include "attr-path.hh" #include "common-eval-args.hh" @@ -100,6 +101,14 @@ MixFlakeOptions::MixFlakeOptions() lockFlags.inputOverrides.insert_or_assign( flake::parseInputPath(inputPath), parseFlakeRef(flakeRef, absPath("."), true)); + }}, + .completer = {[&](size_t n, std::string_view prefix) { + if (n == 0) { + if (auto flakeRef = getFlakeRefForCompletion()) + completeFlakeInputPath(getEvalState(), *flakeRef, prefix); + } else if (n == 1) { + completeFlakeRef(getEvalState()->store, prefix); + } }} }); @@ -194,6 +203,8 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes() void SourceExprCommand::completeInstallable(std::string_view prefix) { if (file) { + completionType = ctAttrs; + evalSettings.pureEval = false; auto state = getEvalState(); Expr *e = state->parseExprFromFile( @@ -222,13 +233,14 @@ void SourceExprCommand::completeInstallable(std::string_view prefix) Value v2; state->autoCallFunction(*autoArgs, v1, v2); - completionType = ctAttrs; - if (v2.type() == nAttrs) { for (auto & i : *v2.attrs) { - std::string name = i.name; + std::string name = state->symbols[i.name]; if (name.find(searchWord) == 0) { - completions->add(i.name); + if (prefix_ == "") + completions->add(name); + else + completions->add(prefix_ + "." + name); } } } @@ -256,10 +268,11 @@ void completeFlakeRefWithFragment( if (hash == std::string::npos) { completeFlakeRef(evalState->store, prefix); } else { + completionType = ctAttrs; + auto fragment = prefix.substr(hash + 1); auto flakeRefS = std::string(prefix.substr(0, hash)); - // FIXME: do tilde expansion. - auto flakeRef = parseFlakeRef(flakeRefS, absPath(".")); + auto flakeRef = parseFlakeRef(expandTilde(flakeRefS), absPath(".")); auto evalCache = openEvalCache(*evalState, std::make_shared(lockFlake(*evalState, flakeRef, lockFlags))); @@ -271,8 +284,6 @@ void completeFlakeRefWithFragment( flake. */ attrPathPrefixes.push_back(""); - completionType = ctAttrs; - for (auto & attrPathPrefixS : attrPathPrefixes) { auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS); auto attrPathS = attrPathPrefixS + std::string(fragment); @@ -280,7 +291,7 @@ void completeFlakeRefWithFragment( std::string lastAttr; if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) { - lastAttr = attrPath.back(); + lastAttr = evalState->symbols[attrPath.back()]; attrPath.pop_back(); } @@ -288,11 +299,11 @@ void completeFlakeRefWithFragment( if (!attr) continue; for (auto & attr2 : (*attr)->getAttrs()) { - if (hasPrefix(attr2, lastAttr)) { + if (hasPrefix(evalState->symbols[attr2], lastAttr)) { auto attrPath2 = (*attr)->getAttrPath(attr2); /* Strip the attrpath prefix. */ attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size()); - completions->add(flakeRefS + "#" + concatStringsSep(".", attrPath2)); + completions->add(flakeRefS + "#" + concatStringsSep(".", evalState->symbols.resolve(attrPath2))); } } } @@ -346,16 +357,16 @@ DerivedPath Installable::toDerivedPath() return std::move(buildables[0]); } -std::vector, std::string>> +std::vector> Installable::getCursors(EvalState & state) { auto evalCache = std::make_shared(std::nullopt, state, [&]() { return toValue(state).first; }); - return {{evalCache->getRoot(), ""}}; + return {evalCache->getRoot()}; } -std::pair, std::string> +ref Installable::getCursor(EvalState & state) { auto cursors = getCursors(state); @@ -462,7 +473,7 @@ struct InstallableAttrPath : InstallableValue std::string what() const override { return attrPath; } - std::pair toValue(EvalState & state) override + std::pair toValue(EvalState & state) override { auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v); state.forceValue(*vRes, pos); @@ -578,43 +589,21 @@ InstallableFlake::InstallableFlake( std::tuple InstallableFlake::toDerivation() { - auto lockedFlake = getLockedFlake(); + auto attr = getCursor(*state); - auto cache = openEvalCache(*state, lockedFlake); - auto root = cache->getRoot(); + auto attrPath = attr->getAttrPathStr(); - Suggestions suggestions; + if (!attr->isDerivation()) + throw Error("flake output attribute '%s' is not a derivation", attrPath); - for (auto & attrPath : getActualAttrPaths()) { - debug("trying flake output attribute '%s'", attrPath); + auto drvPath = attr->forceDerivation(); - auto attrOrSuggestions = root->findAlongAttrPath( - parseAttrPath(*state, attrPath), - true - ); + auto drvInfo = DerivationInfo { + std::move(drvPath), + attr->getAttr(state->sOutputName)->getString() + }; - if (!attrOrSuggestions) { - suggestions += attrOrSuggestions.getSuggestions(); - continue; - } - - auto attr = *attrOrSuggestions; - - if (!attr->isDerivation()) - throw Error("flake output attribute '%s' is not a derivation", attrPath); - - auto drvPath = attr->forceDerivation(); - - auto drvInfo = DerivationInfo { - std::move(drvPath), - attr->getAttr(state->sOutputName)->getString() - }; - - return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)}; - } - - throw Error(suggestions, "flake '%s' does not provide attribute %s", - flakeRef, showAttrPaths(getActualAttrPaths())); + return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)}; } std::vector InstallableFlake::toDerivations() @@ -624,35 +613,12 @@ std::vector InstallableFlake::toDerivations() return res; } -std::pair InstallableFlake::toValue(EvalState & state) +std::pair InstallableFlake::toValue(EvalState & state) { - auto lockedFlake = getLockedFlake(); - - auto vOutputs = getFlakeOutputs(state, *lockedFlake); - - auto emptyArgs = state.allocBindings(0); - - Suggestions suggestions; - - for (auto & attrPath : getActualAttrPaths()) { - try { - auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs); - state.forceValue(*v, pos); - return {v, pos}; - } catch (AttrPathNotFound & e) { - suggestions += e.info().suggestions; - } - } - - throw Error( - suggestions, - "flake '%s' does not provide attribute %s", - flakeRef, - showAttrPaths(getActualAttrPaths()) - ); + return {&getCursor(state)->forceValue(), noPos}; } -std::vector, std::string>> +std::vector> InstallableFlake::getCursors(EvalState & state) { auto evalCache = openEvalCache(state, @@ -660,21 +626,55 @@ InstallableFlake::getCursors(EvalState & state) auto root = evalCache->getRoot(); - std::vector, std::string>> res; + std::vector> res; for (auto & attrPath : getActualAttrPaths()) { auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath)); - if (attr) res.push_back({*attr, attrPath}); + if (attr) res.push_back(ref(*attr)); } return res; } +ref InstallableFlake::getCursor(EvalState & state) +{ + auto lockedFlake = getLockedFlake(); + + auto cache = openEvalCache(state, lockedFlake); + auto root = cache->getRoot(); + + Suggestions suggestions; + + auto attrPaths = getActualAttrPaths(); + + for (auto & attrPath : attrPaths) { + debug("trying flake output attribute '%s'", attrPath); + + auto attrOrSuggestions = root->findAlongAttrPath( + parseAttrPath(state, attrPath), + true + ); + + if (!attrOrSuggestions) { + suggestions += attrOrSuggestions.getSuggestions(); + continue; + } + + return *attrOrSuggestions; + } + + throw Error( + suggestions, + "flake '%s' does not provide attribute %s", + flakeRef, + showAttrPaths(attrPaths)); +} + std::shared_ptr InstallableFlake::getLockedFlake() const { - flake::LockFlags lockFlagsApplyConfig = lockFlags; - lockFlagsApplyConfig.applyNixConfig = true; if (!_lockedFlake) { + flake::LockFlags lockFlagsApplyConfig = lockFlags; + lockFlagsApplyConfig.applyNixConfig = true; _lockedFlake = std::make_shared(lockFlake(*state, flakeRef, lockFlagsApplyConfig)); } return _lockedFlake; @@ -831,8 +831,8 @@ std::vector, BuiltPath>> Installable::bui auto realisation = store->queryRealisation(outputId); if (!realisation) throw Error( - "cannot operate on an output of unbuilt " - "content-addressed derivation '%s'", + "cannot operate on an output of the " + "unbuilt derivation '%s'", outputId.to_string()); outputs.insert_or_assign(output, realisation->outPath); } else { @@ -980,10 +980,10 @@ std::optional InstallablesCommand::getFlakeRefForCompletion() { if (_installables.empty()) { if (useDefaultInstallables()) - return parseFlakeRef(".", absPath(".")); + return parseFlakeRefWithFragment(".", absPath(".")).first; return {}; } - return parseFlakeRef(_installables.front(), absPath(".")); + return parseFlakeRefWithFragment(_installables.front(), absPath(".")).first; } InstallableCommand::InstallableCommand(bool supportReadOnlyMode) diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh index f4bf0d406..de8b08525 100644 --- a/src/libcmd/installables.hh +++ b/src/libcmd/installables.hh @@ -68,7 +68,7 @@ struct Installable UnresolvedApp toApp(EvalState & state); - virtual std::pair toValue(EvalState & state) + virtual std::pair toValue(EvalState & state) { throw Error("argument '%s' cannot be evaluated", what()); } @@ -80,10 +80,10 @@ struct Installable return {}; } - virtual std::vector, std::string>> + virtual std::vector> getCursors(EvalState & state); - std::pair, std::string> + virtual ref getCursor(EvalState & state); virtual FlakeRef nixpkgsFlakeRef() const @@ -178,11 +178,17 @@ struct InstallableFlake : InstallableValue std::vector toDerivations() override; - std::pair toValue(EvalState & state) override; + std::pair toValue(EvalState & state) override; - std::vector, std::string>> + /* Get a cursor to every attrpath in getActualAttrPaths() that + exists. */ + std::vector> getCursors(EvalState & state) override; + /* Get a cursor to the first attrpath in getActualAttrPaths() that + exists, or throw an exception with suggestions if none exists. */ + ref getCursor(EvalState & state) override; + std::shared_ptr getLockedFlake() const; FlakeRef nixpkgsFlakeRef() const override; diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc index 5c25183cc..d9ba7e7a4 100644 --- a/src/libcmd/repl.cc +++ b/src/libcmd/repl.cc @@ -33,6 +33,7 @@ extern "C" { #include "command.hh" #include "finally.hh" #include "markdown.hh" +#include "local-fs-store.hh" #if HAVE_BOEHMGC #define GC_INCLUDE_NEW @@ -75,7 +76,7 @@ struct NixRepl void loadFiles(); void reloadFiles(); void addAttrsToScope(Value & attrs); - void addVarToScope(const Symbol & name, Value & v); + void addVarToScope(const Symbol name, Value & v); Expr * parseString(std::string s); void evalString(std::string s, Value & v); void loadDebugTraceEnv(DebugTrace &dt); @@ -124,7 +125,7 @@ std::string runNix(Path program, const Strings & args, }); if (!statusOk(res.first)) - throw ExecError(res.first, fmt("program '%1%' %2%", program, statusToString(res.first))); + throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first)); return res.second; } @@ -389,9 +390,9 @@ StringSet NixRepl::completePrefix(const std::string & prefix) state->forceAttrs(v, noPos); for (auto & i : *v.attrs) { - std::string name = i.name; + std::string_view name = state->symbols[i.name]; if (name.substr(0, cur2.size()) != cur2) continue; - completions.insert(prev + expr + "." + name); + completions.insert(concatStrings(prev, expr, ".", name)); } } catch (ParseError & e) { @@ -480,7 +481,8 @@ bool NixRepl::processLine(std::string line) << " Evaluate and print expression\n" << " = Bind expression to variable\n" << " :a Add attributes from resulting set to scope\n" - << " :b Build derivation\n" + << " :b Build a derivation\n" + << " :bl Build a derivation, creating GC roots in the working directory\n" << " :e Open package or function in $EDITOR\n" << " :i Build derivation, then install result into current profile\n" << " :l Load Nix expression and add it to scope\n" @@ -586,21 +588,23 @@ bool NixRepl::processLine(std::string line) Value v; evalString(arg, v); - Pos pos; - - if (v.type() == nPath || v.type() == nString) { - PathSet context; - auto filename = state->coerceToString(noPos, v, context); - pos.file = state->symbols.create(*filename); - } else if (v.isLambda()) { - pos = v.lambda.fun->pos; - } else { - // assume it's a derivation - pos = findPackageFilename(*state, v, arg); - } + const auto [file, line] = [&] () -> std::pair { + if (v.type() == nPath || v.type() == nString) { + PathSet context; + auto filename = state->coerceToString(noPos, v, context).toOwned(); + state->symbols.create(filename); + return {filename, 0}; + } else if (v.isLambda()) { + auto pos = state->positions[v.lambda.fun->pos]; + return {pos.file, pos.line}; + } else { + // assume it's a derivation + return findPackageFilename(*state, v, arg); + } + }(); // Open in EDITOR - auto args = editorFor(pos); + auto args = editorFor(file, line); auto editor = args.front(); args.pop_front(); @@ -623,24 +627,32 @@ bool NixRepl::processLine(std::string line) Value v, f, result; evalString(arg, v); evalString("drv: (import {}).runCommand \"shell\" { buildInputs = [ drv ]; } \"\"", f); - state->callFunction(f, v, result, Pos()); + state->callFunction(f, v, result, PosIdx()); StorePath drvPath = getDerivationPath(result); runNix("nix-shell", {state->store->printStorePath(drvPath)}); } - else if (command == ":b" || command == ":i" || command == ":s" || command == ":log") { + else if (command == ":b" || command == ":bl" || command == ":i" || command == ":s" || command == ":log") { Value v; evalString(arg, v); StorePath drvPath = getDerivationPath(v); Path drvPathRaw = state->store->printStorePath(drvPath); - if (command == ":b") { + if (command == ":b" || command == ":bl") { state->store->buildPaths({DerivedPath::Built{drvPath}}); auto drv = state->store->readDerivation(drvPath); logger->cout("\nThis derivation produced the following outputs:"); - for (auto & [outputName, outputPath] : state->store->queryDerivationOutputMap(drvPath)) - logger->cout(" %s -> %s", outputName, state->store->printStorePath(outputPath)); + for (auto & [outputName, outputPath] : state->store->queryDerivationOutputMap(drvPath)) { + auto localStore = state->store.dynamic_pointer_cast(); + if (localStore && command == ":bl") { + std::string symlink = "repl-result-" + outputName; + localStore->addPermRoot(outputPath, absPath(symlink)); + logger->cout(" ./%s -> %s", symlink, state->store->printStorePath(outputPath)); + } else { + logger->cout(" %s -> %s", outputName, state->store->printStorePath(outputPath)); + } + } } else if (command == ":i") { runNix("nix-env", {"-i", drvPathRaw}); } else if (command == ":log") { @@ -791,7 +803,7 @@ void NixRepl::initEnv() varNames.clear(); for (auto & i : state->staticBaseEnv->vars) - varNames.insert(i.first); + varNames.emplace(state->symbols[i.first]); } @@ -827,7 +839,7 @@ void NixRepl::addAttrsToScope(Value & attrs) for (auto & i : *attrs.attrs) { staticEnv->vars.emplace_back(i.name, displ); env->values[displ++] = i.value; - varNames.insert((std::string) i.name); + varNames.emplace(state->symbols[i.name]); } staticEnv->sort(); staticEnv->deduplicate(); @@ -835,7 +847,7 @@ void NixRepl::addAttrsToScope(Value & attrs) } -void NixRepl::addVarToScope(const Symbol & name, Value & v) +void NixRepl::addVarToScope(const Symbol name, Value & v) { if (displ >= envSize) throw Error("environment full; cannot add more variables"); @@ -844,7 +856,7 @@ void NixRepl::addVarToScope(const Symbol & name, Value & v) staticEnv->vars.emplace_back(name, displ); staticEnv->sort(); env->values[displ++] = &v; - varNames.insert((std::string) name); + varNames.emplace(state->symbols[name]); } @@ -925,7 +937,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m Bindings::iterator i = v.attrs->find(state->sDrvPath); PathSet context; if (i != v.attrs->end()) - str << state->store->printStorePath(state->coerceToStorePath(*i->pos, *i->value, context)); + str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context)); else str << "???"; str << "»"; @@ -937,7 +949,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m typedef std::map Sorted; Sorted sorted; for (auto & i : *v.attrs) - sorted[i.name] = i.value; + sorted.emplace(state->symbols[i.name], i.value); for (auto & i : sorted) { if (isVarName(i.first)) @@ -987,7 +999,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m case nFunction: if (v.isLambda()) { std::ostringstream s; - s << v.lambda.fun->pos; + s << state->positions[v.lambda.fun->pos]; str << ANSI_BLUE "«lambda @ " << filterANSIEscapes(s.str()) << "»" ANSI_NORMAL; } else if (v.isPrimOp()) { str << ANSI_MAGENTA "«primop»" ANSI_NORMAL; diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index 32deecfae..94ab60f9a 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -41,13 +41,13 @@ std::vector parseAttrPath(EvalState & state, std::string_view s) } -std::pair findAlongAttrPath(EvalState & state, const std::string & attrPath, +std::pair findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & autoArgs, Value & vIn) { Strings tokens = parseAttrPath(attrPath); Value * v = &vIn; - Pos pos = noPos; + PosIdx pos = noPos; for (auto & attr : tokens) { @@ -77,13 +77,13 @@ std::pair findAlongAttrPath(EvalState & state, const std::string & if (a == v->attrs->end()) { std::set attrNames; for (auto & attr : *v->attrs) - attrNames.insert(attr.name); + attrNames.insert(state.symbols[attr.name]); auto suggestions = Suggestions::bestMatches(attrNames, attr); throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath); } v = &*a->value; - pos = *a->pos; + pos = a->pos; } else { @@ -106,7 +106,7 @@ std::pair findAlongAttrPath(EvalState & state, const std::string & } -Pos findPackageFilename(EvalState & state, Value & v, std::string what) +std::pair findPackageFilename(EvalState & state, Value & v, std::string what) { Value * v2; try { @@ -132,9 +132,7 @@ Pos findPackageFilename(EvalState & state, Value & v, std::string what) throw ParseError("cannot parse line number '%s'", pos); } - Symbol file = state.symbols.create(filename); - - return { foFile, file, lineno, 0 }; + return { std::move(filename), lineno }; } diff --git a/src/libexpr/attr-path.hh b/src/libexpr/attr-path.hh index ff1135a06..117e0051b 100644 --- a/src/libexpr/attr-path.hh +++ b/src/libexpr/attr-path.hh @@ -10,14 +10,14 @@ namespace nix { MakeError(AttrPathNotFound, Error); MakeError(NoPositionInfo, Error); -std::pair findAlongAttrPath( +std::pair findAlongAttrPath( EvalState & state, const std::string & attrPath, Bindings & autoArgs, Value & vIn); /* Heuristic to find the filename and lineno or a nix value. */ -Pos findPackageFilename(EvalState & state, Value & v, std::string what); +std::pair findPackageFilename(EvalState & state, Value & v, std::string what); std::vector parseAttrPath(EvalState & state, std::string_view s); diff --git a/src/libexpr/attr-set.cc b/src/libexpr/attr-set.cc index 52ac47e9b..877116f1f 100644 --- a/src/libexpr/attr-set.cc +++ b/src/libexpr/attr-set.cc @@ -26,7 +26,7 @@ Bindings * EvalState::allocBindings(size_t capacity) /* Create a new attribute named 'name' on an existing attribute set stored in 'vAttrs' and return the newly allocated Value which is associated with this attribute. */ -Value * EvalState::allocAttr(Value & vAttrs, const Symbol & name) +Value * EvalState::allocAttr(Value & vAttrs, Symbol name) { Value * v = allocValue(); vAttrs.attrs->push_back(Attr(name, v)); @@ -40,7 +40,7 @@ Value * EvalState::allocAttr(Value & vAttrs, std::string_view name) } -Value & BindingsBuilder::alloc(const Symbol & name, ptr pos) +Value & BindingsBuilder::alloc(Symbol name, PosIdx pos) { auto value = state.allocValue(); bindings->push_back(Attr(name, value, pos)); @@ -48,7 +48,7 @@ Value & BindingsBuilder::alloc(const Symbol & name, ptr pos) } -Value & BindingsBuilder::alloc(std::string_view name, ptr pos) +Value & BindingsBuilder::alloc(std::string_view name, PosIdx pos) { return alloc(state.symbols.create(name), pos); } diff --git a/src/libexpr/attr-set.hh b/src/libexpr/attr-set.hh index cad9743ea..dcc73b506 100644 --- a/src/libexpr/attr-set.hh +++ b/src/libexpr/attr-set.hh @@ -15,18 +15,27 @@ struct Value; /* Map one attribute name to its value. */ struct Attr { + /* the placement of `name` and `pos` in this struct is important. + both of them are uint32 wrappers, they are next to each other + to make sure that Attr has no padding on 64 bit machines. that + way we keep Attr size at two words with no wasted space. */ Symbol name; + PosIdx pos; Value * value; - ptr pos; - Attr(Symbol name, Value * value, ptr pos = ptr(&noPos)) - : name(name), value(value), pos(pos) { }; - Attr() : pos(&noPos) { }; + Attr(Symbol name, Value * value, PosIdx pos = noPos) + : name(name), pos(pos), value(value) { }; + Attr() { }; bool operator < (const Attr & a) const { return name < a.name; } }; +static_assert(sizeof(Attr) == 2 * sizeof(uint32_t) + sizeof(Value *), + "performance of the evaluator is highly sensitive to the size of Attr. " + "avoid introducing any padding into Attr if at all possible, and do not " + "introduce new fields that need not be present for almost every instance."); + /* Bindings contains all the attributes of an attribute set. It is defined by its size and its capacity, the capacity being the number of Attr elements allocated after this structure, while the size corresponds to @@ -35,13 +44,13 @@ class Bindings { public: typedef uint32_t size_t; - ptr pos; + PosIdx pos; private: size_t size_, capacity_; Attr attrs[0]; - Bindings(size_t capacity) : pos(&noPos), size_(0), capacity_(capacity) { } + Bindings(size_t capacity) : size_(0), capacity_(capacity) { } Bindings(const Bindings & bindings) = delete; public: @@ -57,7 +66,7 @@ public: attrs[size_++] = attr; } - iterator find(const Symbol & name) + iterator find(Symbol name) { Attr key(name, 0); iterator i = std::lower_bound(begin(), end(), key); @@ -65,7 +74,7 @@ public: return end(); } - Attr * get(const Symbol & name) + Attr * get(Symbol name) { Attr key(name, 0); iterator i = std::lower_bound(begin(), end(), key); @@ -73,18 +82,6 @@ public: return nullptr; } - Attr & need(const Symbol & name, const Pos & pos = noPos) - { - auto a = get(name); - if (!a) - throw Error({ - .msg = hintfmt("attribute '%s' missing", name), - .errPos = pos - }); - - return *a; - } - iterator begin() { return &attrs[0]; } iterator end() { return &attrs[size_]; } @@ -98,14 +95,15 @@ public: size_t capacity() { return capacity_; } /* Returns the attributes in lexicographically sorted order. */ - std::vector lexicographicOrder() const + std::vector lexicographicOrder(const SymbolTable & symbols) const { std::vector res; res.reserve(size_); for (size_t n = 0; n < size_; n++) res.emplace_back(&attrs[n]); - std::sort(res.begin(), res.end(), [](const Attr * a, const Attr * b) { - return (const std::string &) a->name < (const std::string &) b->name; + std::sort(res.begin(), res.end(), [&](const Attr * a, const Attr * b) { + std::string_view sa = symbols[a->name], sb = symbols[b->name]; + return sa < sb; }); return res; } @@ -130,7 +128,7 @@ public: : bindings(bindings), state(state) { } - void insert(Symbol name, Value * value, ptr pos = ptr(&noPos)) + void insert(Symbol name, Value * value, PosIdx pos = noPos) { insert(Attr(name, value, pos)); } @@ -145,9 +143,9 @@ public: bindings->push_back(attr); } - Value & alloc(const Symbol & name, ptr pos = ptr(&noPos)); + Value & alloc(Symbol name, PosIdx pos = noPos); - Value & alloc(std::string_view name, ptr pos = ptr(&noPos)); + Value & alloc(std::string_view name, PosIdx pos = noPos); Bindings * finish() { diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 0608d8378..236e07d14 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -35,9 +35,15 @@ struct AttrDb std::unique_ptr> _state; - AttrDb(const Store & cfg, const Hash & fingerprint) + SymbolTable & symbols; + + AttrDb( + const Store & cfg, + const Hash & fingerprint, + SymbolTable & symbols) : cfg(cfg) , _state(std::make_unique>()) + , symbols(symbols) { auto state(_state->lock()); @@ -100,7 +106,7 @@ struct AttrDb state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::FullAttrs) (0, false).exec(); @@ -110,7 +116,7 @@ struct AttrDb for (auto & attr : attrs) state->insertAttribute.use() (rowId) - (attr) + (symbols[attr]) (AttrType::Placeholder) (0, false).exec(); @@ -135,14 +141,14 @@ struct AttrDb } state->insertAttributeWithContext.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::String) (s) (ctx).exec(); } else { state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::String) (s).exec(); } @@ -161,7 +167,7 @@ struct AttrDb state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::Bool) (b ? 1 : 0).exec(); @@ -177,7 +183,7 @@ struct AttrDb state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::Placeholder) (0, false).exec(); @@ -193,7 +199,7 @@ struct AttrDb state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::Missing) (0, false).exec(); @@ -209,7 +215,7 @@ struct AttrDb state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::Misc) (0, false).exec(); @@ -225,7 +231,7 @@ struct AttrDb state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::Failed) (0, false).exec(); @@ -233,13 +239,11 @@ struct AttrDb }); } - std::optional> getAttr( - AttrKey key, - SymbolTable & symbols) + std::optional> getAttr(AttrKey key) { auto state(_state->lock()); - auto queryAttribute(state->queryAttribute.use()(key.first)(key.second)); + auto queryAttribute(state->queryAttribute.use()(key.first)(symbols[key.second])); if (!queryAttribute.next()) return {}; auto rowId = (AttrType) queryAttribute.getInt(0); @@ -253,7 +257,7 @@ struct AttrDb std::vector attrs; auto queryAttributes(state->queryAttributes.use()(rowId)); while (queryAttributes.next()) - attrs.push_back(symbols.create(queryAttributes.getStr(0))); + attrs.emplace_back(symbols.create(queryAttributes.getStr(0))); return {{rowId, attrs}}; } case AttrType::String: { @@ -277,10 +281,13 @@ struct AttrDb } }; -static std::shared_ptr makeAttrDb(const Store & cfg, const Hash & fingerprint) +static std::shared_ptr makeAttrDb( + const Store & cfg, + const Hash & fingerprint, + SymbolTable & symbols) { try { - return std::make_shared(cfg, fingerprint); + return std::make_shared(cfg, fingerprint, symbols); } catch (SQLiteError &) { ignoreException(); return nullptr; @@ -291,7 +298,7 @@ EvalCache::EvalCache( std::optional> useCache, EvalState & state, RootLoader rootLoader) - : db(useCache ? makeAttrDb(*state.store, *useCache) : nullptr) + : db(useCache ? makeAttrDb(*state.store, *useCache, state.symbols) : nullptr) , state(state) , rootLoader(rootLoader) { @@ -306,9 +313,9 @@ Value * EvalCache::getRootValue() return *value; } -std::shared_ptr EvalCache::getRoot() +ref EvalCache::getRoot() { - return std::make_shared(ref(shared_from_this()), std::nullopt); + return make_ref(ref(shared_from_this()), std::nullopt); } AttrCursor::AttrCursor( @@ -327,8 +334,7 @@ AttrKey AttrCursor::getKey() if (!parent) return {0, root->state.sEpsilon}; if (!parent->first->cachedValue) { - parent->first->cachedValue = root->db->getAttr( - parent->first->getKey(), root->state.symbols); + parent->first->cachedValue = root->db->getAttr(parent->first->getKey()); assert(parent->first->cachedValue); } return {parent->first->cachedValue->first, parent->second}; @@ -369,12 +375,12 @@ std::vector AttrCursor::getAttrPath(Symbol name) const std::string AttrCursor::getAttrPathStr() const { - return concatStringsSep(".", getAttrPath()); + return concatStringsSep(".", root->state.symbols.resolve(getAttrPath())); } std::string AttrCursor::getAttrPathStr(Symbol name) const { - return concatStringsSep(".", getAttrPath(name)); + return concatStringsSep(".", root->state.symbols.resolve(getAttrPath(name))); } Value & AttrCursor::forceValue() @@ -414,25 +420,25 @@ Suggestions AttrCursor::getSuggestionsForAttr(Symbol name) auto attrNames = getAttrs(); std::set strAttrNames; for (auto & name : attrNames) - strAttrNames.insert(std::string(name)); + strAttrNames.insert(root->state.symbols[name]); - return Suggestions::bestMatches(strAttrNames, name); + return Suggestions::bestMatches(strAttrNames, root->state.symbols[name]); } std::shared_ptr AttrCursor::maybeGetAttr(Symbol name, bool forceErrors) { if (root->db) { if (!cachedValue) - cachedValue = root->db->getAttr(getKey(), root->state.symbols); + cachedValue = root->db->getAttr(getKey()); if (cachedValue) { if (auto attrs = std::get_if>(&cachedValue->second)) { for (auto & attr : *attrs) if (attr == name) - return std::make_shared(root, std::make_pair(shared_from_this(), name)); + return std::make_shared(root, std::make_pair(shared_from_this(), attr)); return nullptr; } else if (std::get_if(&cachedValue->second)) { - auto attr = root->db->getAttr({cachedValue->first, name}, root->state.symbols); + auto attr = root->db->getAttr({cachedValue->first, name}); if (attr) { if (std::get_if(&attr->second)) return nullptr; @@ -522,7 +528,7 @@ std::string AttrCursor::getString() { if (root->db) { if (!cachedValue) - cachedValue = root->db->getAttr(getKey(), root->state.symbols); + cachedValue = root->db->getAttr(getKey()); if (cachedValue && !std::get_if(&cachedValue->second)) { if (auto s = std::get_if(&cachedValue->second)) { debug("using cached string attribute '%s'", getAttrPathStr()); @@ -552,7 +558,7 @@ string_t AttrCursor::getStringWithContext() { if (root->db) { if (!cachedValue) - cachedValue = root->db->getAttr(getKey(), root->state.symbols); + cachedValue = root->db->getAttr(getKey()); if (cachedValue && !std::get_if(&cachedValue->second)) { if (auto s = std::get_if(&cachedValue->second)) { bool valid = true; @@ -594,7 +600,7 @@ bool AttrCursor::getBool() { if (root->db) { if (!cachedValue) - cachedValue = root->db->getAttr(getKey(), root->state.symbols); + cachedValue = root->db->getAttr(getKey()); if (cachedValue && !std::get_if(&cachedValue->second)) { if (auto b = std::get_if(&cachedValue->second)) { debug("using cached Boolean attribute '%s'", getAttrPathStr()); @@ -624,7 +630,7 @@ std::vector AttrCursor::getAttrs() { if (root->db) { if (!cachedValue) - cachedValue = root->db->getAttr(getKey(), root->state.symbols); + cachedValue = root->db->getAttr(getKey()); if (cachedValue && !std::get_if(&cachedValue->second)) { if (auto attrs = std::get_if>(&cachedValue->second)) { debug("using cached attrset attribute '%s'", getAttrPathStr()); @@ -650,8 +656,9 @@ std::vector AttrCursor::getAttrs() std::vector attrs; for (auto & attr : *getValue().attrs) attrs.push_back(attr.name); - std::sort(attrs.begin(), attrs.end(), [](const Symbol & a, const Symbol & b) { - return (const std::string &) a < (const std::string &) b; + std::sort(attrs.begin(), attrs.end(), [&](Symbol a, Symbol b) { + std::string_view sa = root->state.symbols[a], sb = root->state.symbols[b]; + return sa < sb; }); if (root->db) diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh index c9a9bf471..b0709ebc2 100644 --- a/src/libexpr/eval-cache.hh +++ b/src/libexpr/eval-cache.hh @@ -33,7 +33,7 @@ public: EvalState & state, RootLoader rootLoader); - std::shared_ptr getRoot(); + ref getRoot(); }; enum AttrType { @@ -104,6 +104,8 @@ public: ref getAttr(std::string_view name); + /* Get an attribute along a chain of attrsets. Note that this does + not auto-call functors or functions. */ OrSuggestions> findAlongAttrPath(const std::vector & attrPath, bool force = false); std::string getString(); diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index 9b0073822..4e0826101 100644 --- a/src/libexpr/eval-inline.hh +++ b/src/libexpr/eval-inline.hh @@ -2,42 +2,8 @@ #include "eval.hh" -#define LocalNoInline(f) static f __attribute__((noinline)); f -#define LocalNoInlineNoReturn(f) static f __attribute__((noinline, noreturn)); f - namespace nix { -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, EvalState &evalState)) -{ - auto error = EvalError({ - .msg = hintfmt(s), - .errPos = pos - }); - - if (debuggerHook && !evalState.debugTraces.empty()) { - DebugTrace &last = evalState.debugTraces.front(); - debuggerHook(&error, last.env, last.expr); - } - - throw error; -} - -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v, EvalState &evalState)) -{ - auto error = TypeError({ - .msg = hintfmt(s, showType(v)), - .errPos = pos - }); - - if (debuggerHook && !evalState.debugTraces.empty()) { - DebugTrace &last = evalState.debugTraces.front(); - debuggerHook(&error, last.env, last.expr); - } - - throw error; -} - - /* Note: Various places expect the allocated memory to be zeroed. */ [[gnu::always_inline]] inline void * allocBytes(size_t n) @@ -113,7 +79,7 @@ Env & EvalState::allocEnv(size_t size) [[gnu::always_inline]] -void EvalState::forceValue(Value & v, const Pos & pos) +void EvalState::forceValue(Value & v, const PosIdx pos) { forceValue(v, [&]() { return pos; }); } @@ -142,7 +108,7 @@ void EvalState::forceValue(Value & v, Callable getPos) [[gnu::always_inline]] -inline void EvalState::forceAttrs(Value & v, const Pos & pos) +inline void EvalState::forceAttrs(Value & v, const PosIdx pos) { forceAttrs(v, [&]() { return pos; }); } @@ -159,7 +125,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos) [[gnu::always_inline]] -inline void EvalState::forceList(Value & v, const Pos & pos) +inline void EvalState::forceList(Value & v, const PosIdx pos) { forceValue(v, pos); if (!v.isList()) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index d0e147733..896242e0c 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -96,7 +96,8 @@ RootValue allocRootValue(Value * v) } -void Value::print(std::ostream & str, std::set * seen) const +void Value::print(const SymbolTable & symbols, std::ostream & str, + std::set * seen) const { checkInterrupt(); @@ -129,9 +130,9 @@ void Value::print(std::ostream & str, std::set * seen) const str << "«repeated»"; else { str << "{ "; - for (auto & i : attrs->lexicographicOrder()) { - str << i->name << " = "; - i->value->print(str, seen); + for (auto & i : attrs->lexicographicOrder(symbols)) { + str << symbols[i->name] << " = "; + i->value->print(symbols, str, seen); str << "; "; } str << "}"; @@ -146,7 +147,7 @@ void Value::print(std::ostream & str, std::set * seen) const else { str << "[ "; for (auto v2 : listItems()) { - v2->print(str, seen); + v2->print(symbols, str, seen); str << " "; } str << "]"; @@ -177,17 +178,18 @@ void Value::print(std::ostream & str, std::set * seen) const } -void Value::print(std::ostream & str, bool showRepeated) const +void Value::print(const SymbolTable & symbols, std::ostream & str, bool showRepeated) const { std::set seen; - print(str, showRepeated ? nullptr : &seen); + print(symbols, str, showRepeated ? nullptr : &seen); } -std::ostream & operator << (std::ostream & str, const Value & v) +std::string printValue(const EvalState & state, const Value & v) { - v.print(str, false); - return str; + std::ostringstream out; + v.print(state.symbols, out); + return out.str(); } @@ -236,10 +238,10 @@ std::string showType(const Value & v) } } -Pos Value::determinePos(const Pos & pos) const +PosIdx Value::determinePos(const PosIdx pos) const { switch (internalType) { - case tAttrs: return *attrs->pos; + case tAttrs: return attrs->pos; case tLambda: return lambda.fun->pos; case tApp: return app.left->determinePos(pos); default: return pos; @@ -308,7 +310,7 @@ static BoehmGCStackAllocator boehmGCStackAllocator; static Symbol getName(const AttrName & name, EvalState & state, Env & env) { - if (name.symbol.set()) { + if (name.symbol) { return name.symbol; } else { Value nameValue; @@ -641,20 +643,20 @@ Value * EvalState::addPrimOp(const std::string & name, size_t arity, PrimOpFun primOp) { auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name; - Symbol sym = symbols.create(name2); + auto sym = symbols.create(name2); /* Hack to make constants lazy: turn them into a application of the primop to a dummy value. */ if (arity == 0) { auto vPrimOp = allocValue(); - vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = sym }); + vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = name2 }); Value v; v.mkApp(vPrimOp, vPrimOp); return addConstant(name, v); } Value * v = allocValue(); - v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym }); + v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = name2 }); staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl); baseEnv.values[baseEnvDispl++] = v; baseEnv.values[0]->attrs->push_back(Attr(sym, v)); @@ -669,21 +671,21 @@ Value * EvalState::addPrimOp(PrimOp && primOp) if (primOp.arity == 0) { primOp.arity = 1; auto vPrimOp = allocValue(); - vPrimOp->mkPrimOp(new PrimOp(std::move(primOp))); + vPrimOp->mkPrimOp(new PrimOp(primOp)); Value v; v.mkApp(vPrimOp, vPrimOp); return addConstant(primOp.name, v); } - Symbol envName = primOp.name; + auto envName = symbols.create(primOp.name); if (hasPrefix(primOp.name, "__")) - primOp.name = symbols.create(std::string(primOp.name, 2)); + primOp.name = primOp.name.substr(2); Value * v = allocValue(); - v->mkPrimOp(new PrimOp(std::move(primOp))); + v->mkPrimOp(new PrimOp(primOp)); staticBaseEnv->vars.emplace_back(envName, baseEnvDispl); baseEnv.values[baseEnvDispl++] = v; - baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v)); + baseEnv.values[0]->attrs->push_back(Attr(symbols.create(primOp.name), v)); return v; } @@ -700,7 +702,7 @@ std::optional EvalState::getDoc(Value & v) auto v2 = &v; if (v2->primOp->doc) return Doc { - .pos = noPos, + .pos = {}, .name = v2->primOp->name, .arity = v2->primOp->arity, .args = v2->primOp->args, @@ -819,12 +821,42 @@ valmap * mapStaticEnvBindings(const StaticEnv &se, const Env &env) evaluator. So here are some helper functions for throwing exceptions. */ -LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2, EvalState &evalState)) +void EvalState::throwEvalError(const PosIdx pos, const char * s) const +{ + auto error = EvalError({ + .msg = hintfmt(s), + .errPos = positions[pos] + }); + + if (debuggerHook && !debugTraces.empty()) { + DebugTrace &last = debugTraces.front(); + debuggerHook(&error, last.env, last.expr); + } + + throw error; +} + +void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v) const +{ + auto error = TypeError({ + .msg = hintfmt(s, showType(v)), + .errPos = positions[pos] + }); + + if (debuggerHook && !debugTraces.empty()) { + DebugTrace &last = debugTraces.front(); + debuggerHook(&error, last.env, last.expr); + } + + throw error; +} + +void EvalState::throwEvalError(const char * s, const std::string & s2) const { auto error = EvalError(s, s2); - if (debuggerHook && !evalState.debugTraces.empty()) { - DebugTrace &last = evalState.debugTraces.front(); + if (debuggerHook && !debugTraces.empty()) { + DebugTrace &last = debugTraces.front(); debuggerHook(&error, last.env, last.expr); } @@ -840,12 +872,13 @@ void EvalState::debugLastTrace(Error & e) { } } -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, const std::string & s2, Env & env, Expr &expr)) +void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, + const std::string & s2, Env & env, Expr &expr) const { auto error = EvalError({ .msg = hintfmt(s, s2), - .errPos = pos, - .suggestions = suggestions + .errPos = positions[pos], + .suggestions = suggestions, }); if (debuggerHook) @@ -854,19 +887,19 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & s throw error; } -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2, EvalState &evalState)) +void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2) const { auto error = EvalError({ .msg = hintfmt(s, s2), - .errPos = pos + .errPos = positions[pos] }); - evalState.debugLastTrace(error); + debugLastTrace(error); throw error; } -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, Env & env, Expr &expr)) +void EvalState::throwEvalError(const char * s, const std::string & s2, const std::string & s3, Env & env, Expr &expr) const { auto error = EvalError({ .msg = hintfmt(s), @@ -879,7 +912,8 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, Env & throw error; } -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2, Env & env, Expr &expr)) +void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2, + const std::string & s3, Env & env, Expr &expr) const { auto error = EvalError({ .msg = hintfmt(s, s2), @@ -892,11 +926,12 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const throw error; } +/* LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2, const std::string & s3, EvalState &evalState)) { auto error = EvalError({ .msg = hintfmt(s, s2, s3), - .errPos = pos + .errPos = positions[pos] }); evalState.debugLastTrace(error); @@ -915,13 +950,14 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2 throw error; } +*/ -LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2, Env & env, Expr &expr)) +void EvalState::throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2, Env & env, Expr &expr) const { // p1 is where the error occurred; p2 is a position mentioned in the message. auto error = EvalError({ - .msg = hintfmt(s, sym, p2), - .errPos = p1 + .msg = hintfmt(s, symbols[sym], positions[p2]), + .errPos = positions[p1] }); if (debuggerHook) @@ -930,11 +966,11 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const throw error; } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, EvalState &evalState)) +void EvalState::throwTypeError(const PosIdx pos, const char * s) const { auto error = TypeError({ .msg = hintfmt(s), - .errPos = pos + .errPos = positions[pos] }); evalState.debugLastTrace(error); @@ -942,12 +978,12 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, EvalS throw error; } - -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v, Env & env, Expr &expr)) +void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, + const Symbol s2, Env & env, Expr &expr) const { auto error = TypeError({ - .msg = hintfmt(s, v), - .errPos = pos + .msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]), + .errPos = positions[pos] }); if (debuggerHook) @@ -956,11 +992,13 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const throw error; } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2, Env & env, Expr &expr)) +void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, + const ExprLambda & fun, const Symbol s2, Env & env, Expr &expr) const { - auto error = TypeError({ - .msg = hintfmt(s, fun.showNamePos(), s2), - .errPos = pos, + auto error = TypeError(ErrorInfo { + .msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]), + .errPos = positions[pos], + .suggestions = suggestions, }); if (debuggerHook) @@ -969,7 +1007,7 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const throw error; } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol & s2, Env & env, Expr &expr)) +void EvalState::throwTypeError(const char * s, const Value & v, Env & env, Expr &expr) const { auto error = TypeError({ .msg = hintfmt(s, fun.showNamePos(), s2), @@ -983,11 +1021,11 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const Suggestions & s throw error; } -LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const std::string & s1, Env & env, Expr &expr)) +void EvalState::throwAssertionError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr) const { auto error = AssertionError({ .msg = hintfmt(s, s1), - .errPos = pos + .errPos = positions[pos] }); if (debuggerHook) @@ -996,11 +1034,11 @@ LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, throw error; } -LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const std::string & s1, Env & env, const Expr &expr)) +void EvalState::throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr) const { auto error = UndefinedVarError({ .msg = hintfmt(s, s1), - .errPos = pos + .errPos = positions[pos] }); if (debuggerHook) @@ -1009,11 +1047,11 @@ LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * throw error; } -LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char * s, const std::string & s1, Env & env, Expr &expr)) +void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr) const { auto error = MissingArgumentError({ .msg = hintfmt(s, s1), - .errPos = pos + .errPos = positions[pos] }); if (debuggerHook) @@ -1022,14 +1060,14 @@ LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char throw error; } -LocalNoInline(void addErrorTrace(Error & e, const char * s, const std::string & s2)) +void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const { e.addTrace(std::nullopt, s, s2); } -LocalNoInline(void addErrorTrace(Error & e, const Pos & pos, const char * s, const std::string & s2)) +void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const { - e.addTrace(pos, s, s2); + e.addTrace(positions[pos], s, s2); } LocalNoInline(std::unique_ptr @@ -1108,12 +1146,11 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) } Bindings::iterator j = env->values[0]->attrs->find(var.name); if (j != env->values[0]->attrs->end()) { - if (countCalls) attrSelects[*j->pos]++; + if (countCalls) attrSelects[j->pos]++; return j->value; } - if (!env->prevWith) { - throwUndefinedVarError(var.pos, "undefined variable '%1%'", var.name, *env, var); - } + if (!env->prevWith) + throwUndefinedVarError(var.pos, "undefined variable '%1%'", symbols[var.name], *env, var); for (size_t l = env->prevWith; l; --l, env = env->up) ; } } @@ -1142,13 +1179,14 @@ void EvalState::mkThunk_(Value & v, Expr * expr) } -void EvalState::mkPos(Value & v, ptr pos) +void EvalState::mkPos(Value & v, PosIdx p) { - if (pos->file.set()) { + auto pos = positions[p]; + if (!pos.file.empty()) { auto attrs = buildBindings(3); - attrs.alloc(sFile).mkString(pos->file); - attrs.alloc(sLine).mkInt(pos->line); - attrs.alloc(sColumn).mkInt(pos->column); + attrs.alloc(sFile).mkString(pos.file); + attrs.alloc(sLine).mkInt(pos.line); + attrs.alloc(sColumn).mkInt(pos.column); v.mkAttrs(attrs); } else v.mkNull(); @@ -1291,7 +1329,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e) } -inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos) +inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos) { Value v; e->eval(*this, env, v); @@ -1365,7 +1403,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) } else vAttr = i.second.e->maybeThunk(state, i.second.inherited ? env : env2); env2.values[displ++] = vAttr; - v.attrs->push_back(Attr(i.first, vAttr, ptr(&i.second.pos))); + v.attrs->push_back(Attr(i.first, vAttr, i.second.pos)); } /* If the rec contains an attribute called `__overrides', then @@ -1397,7 +1435,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) else for (auto & i : attrs) - v.attrs->push_back(Attr(i.first, i.second.e->maybeThunk(state, env), ptr(&i.second.pos))); + v.attrs->push_back(Attr(i.first, i.second.e->maybeThunk(state, env), i.second.pos)); /* Dynamic attrs apply *after* rec and __overrides. */ for (auto & i : dynamicAttrs) { @@ -1407,18 +1445,18 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) if (nameVal.type() == nNull) continue; state.forceStringNoCtx(nameVal); - Symbol nameSym = state.symbols.create(nameVal.string.s); + auto nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) - throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, *j->pos, env, *this); + state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, j->pos, env, *this); i.valueExpr->setName(nameSym); /* Keep sorted order so find can catch duplicates */ - v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), ptr(&i.pos))); + v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), i.pos)); v.attrs->sort(); // FIXME: inefficient } - v.attrs->pos = ptr(&pos); + v.attrs->pos = pos; } @@ -1463,10 +1501,12 @@ static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & a for (auto & i : attrPath) { if (!first) out << '.'; else first = false; try { - out << getName(i, state, env); + out << state.symbols[getName(i, state, env)]; } catch (Error & e) { - assert(!i.symbol.set()); - out << "\"${" << *i.expr << "}\""; + assert(!i.symbol); + out << "\"${"; + i.expr->show(state.symbols, out); + out << "}\""; } } return out.str(); @@ -1476,7 +1516,7 @@ static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & a void ExprSelect::eval(EvalState & state, Env & env, Value & v) { Value vTmp; - ptr pos2(&noPos); + PosIdx pos2; Value * vAttrs = &vTmp; e->eval(state, env, vTmp); @@ -1496,7 +1536,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) for (auto & i : attrPath) { state.nrLookups++; Bindings::iterator j; - Symbol name = getName(i, state, env); + auto name = getName(i, state, env); if (def) { state.forceValue(*vAttrs, pos); if (vAttrs->type() != nAttrs || @@ -1510,23 +1550,24 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { std::set allAttrNames; for (auto & attr : *vAttrs->attrs) - allAttrNames.insert(attr.name); - throwEvalError( + allAttrNames.insert(state.symbols[attr.name]); + state.throwEvalError( pos, - Suggestions::bestMatches(allAttrNames, name), - "attribute '%1%' missing", name, env, *this); + Suggestions::bestMatches(allAttrNames, state.symbols[name]), + "attribute '%1%' missing", state.symbols[name], env, *this); } } vAttrs = j->value; pos2 = j->pos; - if (state.countCalls) state.attrSelects[*pos2]++; + if (state.countCalls) state.attrSelects[pos2]++; } - state.forceValue(*vAttrs, (*pos2 != noPos ? *pos2 : this->pos ) ); + state.forceValue(*vAttrs, (pos2 ? pos2 : this->pos ) ); } catch (Error & e) { - if (*pos2 != noPos && pos2->file != state.sDerivationNix) - addErrorTrace(e, *pos2, "while evaluating the attribute '%1%'", + auto pos2r = state.positions[pos2]; + if (pos2 && pos2r.file != state.derivationNixPath) + state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'", showAttrPath(state, env, attrPath)); throw; } @@ -1545,7 +1586,7 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v) for (auto & i : attrPath) { state.forceValue(*vAttrs, noPos); Bindings::iterator j; - Symbol name = getName(i, state, env); + auto name = getName(i, state, env); if (vAttrs->type() != nAttrs || (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { @@ -1566,9 +1607,11 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v) } -void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos) +void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos) { - auto trace = evalSettings.traceFunctionCalls ? std::make_unique(pos) : nullptr; + auto trace = evalSettings.traceFunctionCalls + ? std::make_unique(positions[pos]) + : nullptr; forceValue(fun, pos); @@ -1593,7 +1636,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & ExprLambda & lambda(*vCur.lambda.fun); auto size = - (lambda.arg.empty() ? 0 : 1) + + (!lambda.arg ? 0 : 1) + (lambda.hasFormals() ? lambda.formals->formals.size() : 0); Env & env2(allocEnv(size)); env2.up = vCur.lambda.env; @@ -1605,7 +1648,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & else { forceAttrs(*args[0], pos); - if (!lambda.arg.empty()) + if (lambda.arg) env2.values[displ++] = args[0]; /* For each formal argument, get the actual argument. If @@ -1633,10 +1676,10 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & if (!lambda.formals->has(i.name)) { std::set formalNames; for (auto & formal : lambda.formals->formals) - formalNames.insert(formal.name); + formalNames.insert(symbols[formal.name]); throwTypeError( pos, - Suggestions::bestMatches(formalNames, i.name), + Suggestions::bestMatches(formalNames, symbols[i.name]), "%1% called with unexpected argument '%2%'", lambda, i.name, *fun.lambda.env, lambda); } @@ -1662,8 +1705,8 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & } catch (Error & e) { if (loggerSettings.showTrace.get()) { addErrorTrace(e, lambda.pos, "while evaluating %s", - (lambda.name.set() - ? "'" + (const std::string &) lambda.name + "'" + (lambda.name + ? concatStrings("'", symbols[lambda.name], "'") : "anonymous lambda")); addErrorTrace(e, pos, "from call site%s", ""); } @@ -1812,8 +1855,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) Nix attempted to evaluate a function as a top level expression; in this case it must have its arguments supplied either by default values, or passed explicitly with '--arg' or '--argstr'. See -https://nixos.org/manual/nix/stable/#ss-functions.)", - i.name, +https://nixos.org/manual/nix/stable/#ss-functions.)", symbols[i.name],, *fun.lambda.env, *fun.lambda.fun); } } @@ -1845,8 +1887,8 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v) { if (!state.evalBool(env, cond, pos)) { std::ostringstream out; - cond->show(out); - throwAssertionError(pos, "assertion '%1%' failed", out.str(), env, *this); + cond->show(state.symbols, out); + state.throwAssertionError(pos, "assertion '%1%' failed", out.str(), env, *this); } body->eval(state, env, v); } @@ -1939,7 +1981,7 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v) } -void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos) +void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos) { nrListConcats++; @@ -2022,16 +2064,15 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) firstType = nFloat; nf = n; nf += vTmp.fpoint; - } else { - throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp), env, *this); - } + } else + state.throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp), env, *this); } else if (firstType == nFloat) { if (vTmp.type() == nInt) { nf += vTmp.integer; } else if (vTmp.type() == nFloat) { nf += vTmp.fpoint; } else - throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp), env, *this); + state.throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp), env, *this); } else { if (s.empty()) s.reserve(es->size()); /* skip canonization of first path, which would only be not @@ -2051,7 +2092,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) v.mkFloat(nf); else if (firstType == nPath) { if (!context.empty()) - throwEvalError(pos, "a string that refers to a store path cannot be appended to a path", env, *this); + state.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path", env, *this);); v.mkPath(canonPath(str())); } else v.mkStringMove(c_str(), context); @@ -2060,7 +2101,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) void ExprPos::eval(EvalState & state, Env & env, Value & v) { - state.mkPos(v, ptr(&pos)); + state.mkPos(v, pos); } @@ -2090,7 +2131,7 @@ void EvalState::forceValueDeep(Value & v) recurse(*i.value); } catch (Error & e) { - addErrorTrace(e, *i.pos, "while evaluating the attribute '%1%'", i.name); + addErrorTrace(e, i.pos, "while evaluating the attribute '%1%'", symbols[i.name]); throw; } } @@ -2105,7 +2146,7 @@ void EvalState::forceValueDeep(Value & v) } -NixInt EvalState::forceInt(Value & v, const Pos & pos) +NixInt EvalState::forceInt(Value & v, const PosIdx pos) { forceValue(v, pos); if (v.type() != nInt) @@ -2115,7 +2156,7 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos) } -NixFloat EvalState::forceFloat(Value & v, const Pos & pos) +NixFloat EvalState::forceFloat(Value & v, const PosIdx pos) { forceValue(v, pos); if (v.type() == nInt) @@ -2126,7 +2167,7 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos) } -bool EvalState::forceBool(Value & v, const Pos & pos) +bool EvalState::forceBool(Value & v, const PosIdx pos) { forceValue(v, pos); if (v.type() != nBool) @@ -2141,7 +2182,7 @@ bool EvalState::isFunctor(Value & fun) } -void EvalState::forceFunction(Value & v, const Pos & pos) +void EvalState::forceFunction(Value & v, const PosIdx pos) { forceValue(v, pos); if (v.type() != nFunction && !isFunctor(v)) @@ -2149,7 +2190,7 @@ void EvalState::forceFunction(Value & v, const Pos & pos) } -std::string_view EvalState::forceString(Value & v, const Pos & pos) +std::string_view EvalState::forceString(Value & v, const PosIdx pos) { forceValue(v, pos); if (v.type() != nString) { @@ -2199,7 +2240,7 @@ NixStringContext Value::getContext(const Store & store) } -std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos & pos) +std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos) { auto s = forceString(v, pos); copyContext(v, context); @@ -2207,7 +2248,7 @@ std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos } -std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos) +std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos) { auto s = forceString(v, pos); if (v.string.context) { @@ -2227,13 +2268,13 @@ bool EvalState::isDerivation(Value & v) if (v.type() != nAttrs) return false; Bindings::iterator i = v.attrs->find(sType); if (i == v.attrs->end()) return false; - forceValue(*i->value, *i->pos); + forceValue(*i->value, i->pos); if (i->value->type() != nString) return false; return strcmp(i->value->string.s, "derivation") == 0; } -std::optional EvalState::tryAttrsToString(const Pos & pos, Value & v, +std::optional EvalState::tryAttrsToString(const PosIdx pos, Value & v, PathSet & context, bool coerceMore, bool copyToStore) { auto i = v.attrs->find(sToString); @@ -2246,7 +2287,7 @@ std::optional EvalState::tryAttrsToString(const Pos & pos, Value & return {}; } -BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, +BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context, bool coerceMore, bool copyToStore, bool canonicalizePath) { forceValue(v, pos); @@ -2276,7 +2317,7 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & } if (v.type() == nExternal) - return v.external->coerceToString(pos, context, coerceMore, copyToStore); + return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore); if (coerceMore) { /* Note that `false' is represented as an empty string for @@ -2328,7 +2369,7 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path) } -Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context) +Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context) { auto path = coerceToString(pos, v, context, false, false).toOwned(); if (path == "" || path[0] != '/') @@ -2337,14 +2378,14 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context) } -StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context) +StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context) { auto path = coerceToString(pos, v, context, false, false).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) return *storePath; throw EvalError({ .msg = hintfmt("path '%1%' is not in the Nix store", path), - .errPos = pos + .errPos = positions[pos] }); } @@ -2514,14 +2555,14 @@ void EvalState::printStats() auto list = topObj.list("functions"); for (auto & i : functionCalls) { auto obj = list.object(); - if (i.first->name.set()) + if (i.first->name) obj.attr("name", (const std::string &) i.first->name); else obj.attr("name", nullptr); - if (i.first->pos) { - obj.attr("file", (const std::string &) i.first->pos.file); - obj.attr("line", i.first->pos.line); - obj.attr("column", i.first->pos.column); + if (auto pos = positions[i.first->pos]) { + obj.attr("file", (const std::string &) pos.file); + obj.attr("line", pos.line); + obj.attr("column", pos.column); } obj.attr("count", i.second); } @@ -2530,10 +2571,10 @@ void EvalState::printStats() auto list = topObj.list("attributes"); for (auto & i : attrSelects) { auto obj = list.object(); - if (i.first) { - obj.attr("file", (const std::string &) i.first.file); - obj.attr("line", i.first.line); - obj.attr("column", i.first.column); + if (auto pos = positions[i.first]) { + obj.attr("file", (const std::string &) pos.file); + obj.attr("line", pos.line); + obj.attr("column", pos.column); } obj.attr("count", i.second); } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 11f5707a4..76bd63ca6 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -23,7 +23,7 @@ class StorePath; enum RepairFlag : bool; -typedef void (* PrimOpFun) (EvalState & state, const Pos & pos, Value * * args, Value & v); +typedef void (* PrimOpFun) (EvalState & state, const PosIdx pos, Value * * args, Value & v); void printEnvBindings(const Expr &expr, const Env &env); void printEnvBindings(const StaticEnv &se, const Env &env, int lvl = 0); @@ -32,7 +32,7 @@ struct PrimOp { PrimOpFun fun; size_t arity; - Symbol name; + std::string name; std::vector args; const char * doc = nullptr; }; @@ -57,7 +57,8 @@ void copyContext(const Value & v, PathSet & context); typedef std::map SrcToStore; -std::ostream & operator << (std::ostream & str, const Value & v); +std::ostream & printValue(const EvalState & state, std::ostream & str, const Value & v); +std::string printValue(const EvalState & state, const Value & v); typedef std::pair SearchPathElem; @@ -84,6 +85,9 @@ class EvalState { public: SymbolTable symbols; + PosTable positions; + + static inline std::string derivationNixPath = "//builtin/derivation.nix"; const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls, @@ -222,7 +226,7 @@ public: /* Look up a file in the search path. */ Path findFile(const std::string_view path); - Path findFile(SearchPath & searchPath, const std::string_view path, const Pos & pos = noPos); + Path findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos); /* If the specified search path element is a URI, download it. */ std::pair resolveSearchPathElem(const SearchPathElem & elem); @@ -234,14 +238,14 @@ public: /* Evaluation the expression, then verify that it has the expected type. */ inline bool evalBool(Env & env, Expr * e); - inline bool evalBool(Env & env, Expr * e, const Pos & pos); + inline bool evalBool(Env & env, Expr * e, const PosIdx pos); inline void evalAttrs(Env & env, Expr * e, Value & v); /* If `v' is a thunk, enter it and overwrite `v' with the result of the evaluation of the thunk. If `v' is a delayed function application, call the function and overwrite `v' with the result. Otherwise, this is a no-op. */ - inline void forceValue(Value & v, const Pos & pos); + inline void forceValue(Value & v, const PosIdx pos); template inline void forceValue(Value & v, Callable getPos); @@ -251,33 +255,72 @@ public: void forceValueDeep(Value & v); /* Force `v', and then verify that it has the expected type. */ - NixInt forceInt(Value & v, const Pos & pos); - NixFloat forceFloat(Value & v, const Pos & pos); - bool forceBool(Value & v, const Pos & pos); + NixInt forceInt(Value & v, const PosIdx pos); + NixFloat forceFloat(Value & v, const PosIdx pos); + bool forceBool(Value & v, const PosIdx pos); - void forceAttrs(Value & v, const Pos & pos); + void forceAttrs(Value & v, const PosIdx pos); template inline void forceAttrs(Value & v, Callable getPos); - inline void forceList(Value & v, const Pos & pos); - void forceFunction(Value & v, const Pos & pos); // either lambda or primop - std::string_view forceString(Value & v, const Pos & pos = noPos); - std::string_view forceString(Value & v, PathSet & context, const Pos & pos = noPos); - std::string_view forceStringNoCtx(Value & v, const Pos & pos = noPos); + inline void forceList(Value & v, const PosIdx pos); + void forceFunction(Value & v, const PosIdx pos); // either lambda or primop + std::string_view forceString(Value & v, const PosIdx pos = noPos); + std::string_view forceString(Value & v, PathSet & context, const PosIdx pos = noPos); + std::string_view forceStringNoCtx(Value & v, const PosIdx pos = noPos); + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const PosIdx pos, const char * s) const; + [[gnu::noinline, gnu::noreturn]] + void throwTypeError(const PosIdx pos, const char * s, const Value & v) const; + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const char * s, const std::string & s2) const; + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, + const std::string & s2) const; + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const PosIdx pos, const char * s, const std::string & s2) const; + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const char * s, const std::string & s2, const std::string & s3) const; + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3) const; + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2) const; + [[gnu::noinline, gnu::noreturn]] + void throwTypeError(const PosIdx pos, const char * s) const; + [[gnu::noinline, gnu::noreturn]] + void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2) const; + [[gnu::noinline, gnu::noreturn]] + void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, + const ExprLambda & fun, const Symbol s2) const; + [[gnu::noinline, gnu::noreturn]] + void throwTypeError(const char * s, const Value & v) const; + [[gnu::noinline, gnu::noreturn]] + void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1) const; + [[gnu::noinline, gnu::noreturn]] + void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1) const; + [[gnu::noinline, gnu::noreturn]] + void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1) const; + + [[gnu::noinline]] + void addErrorTrace(Error & e, const char * s, const std::string & s2) const; + [[gnu::noinline]] + void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const; + +public: /* Return true iff the value `v' denotes a derivation (i.e. a set with attribute `type = "derivation"'). */ bool isDerivation(Value & v); - std::optional tryAttrsToString(const Pos & pos, Value & v, + std::optional tryAttrsToString(const PosIdx pos, Value & v, PathSet & context, bool coerceMore = false, bool copyToStore = true); /* String coercion. Converts strings, paths and derivations to a string. If `coerceMore' is set, also converts nulls, integers, 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 Pos & pos, Value & v, PathSet & context, + BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context, bool coerceMore = false, bool copyToStore = true, bool canonicalizePath = true); @@ -286,10 +329,10 @@ public: /* Path coercion. Converts strings, paths and derivations to a path. The result is guaranteed to be a canonicalised, absolute path. Nothing is copied to the store. */ - Path coerceToPath(const Pos & pos, Value & v, PathSet & context); + Path coerceToPath(const PosIdx pos, Value & v, PathSet & context); /* Like coerceToPath, but the result must be a store path. */ - StorePath coerceToStorePath(const Pos & pos, Value & v, PathSet & context); + StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context); public: @@ -322,7 +365,7 @@ public: struct Doc { Pos pos; - std::optional name; + std::optional name; size_t arity; std::vector args; const char * doc; @@ -350,9 +393,9 @@ public: bool isFunctor(Value & fun); // FIXME: use std::span - void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos); + void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos); - void callFunction(Value & fun, Value & arg, Value & vRes, const Pos & pos) + void callFunction(Value & fun, Value & arg, Value & vRes, const PosIdx pos) { Value * args[] = {&arg}; callFunction(fun, 1, args, vRes, pos); @@ -366,7 +409,7 @@ public: inline Value * allocValue(); inline Env & allocEnv(size_t size); - Value * allocAttr(Value & vAttrs, const Symbol & name); + Value * allocAttr(Value & vAttrs, Symbol name); Value * allocAttr(Value & vAttrs, std::string_view name); Bindings * allocBindings(size_t capacity); @@ -378,9 +421,9 @@ public: void mkList(Value & v, size_t length); void mkThunk_(Value & v, Expr * expr); - void mkPos(Value & v, ptr pos); + void mkPos(Value & v, PosIdx pos); - void concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos); + void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos); /* Print statistics. */ void printStats(); @@ -408,7 +451,7 @@ private: bool countCalls; - typedef std::map PrimOpCalls; + typedef std::map PrimOpCalls; PrimOpCalls primOpCalls; typedef std::map FunctionCalls; @@ -416,7 +459,7 @@ private: void incrFunctionCall(ExprLambda * fun); - typedef std::map AttrSelects; + typedef std::map AttrSelects; AttrSelects attrSelects; friend struct ExprOpUpdate; @@ -427,9 +470,9 @@ private: friend struct ExprFloat; friend struct ExprPath; friend struct ExprSelect; - friend void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v); - friend void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v); - friend void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v); + friend void prim_getAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v); + friend void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v); + friend void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v); friend struct Value; }; diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 22257c6b3..cbf4f0a6f 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -72,7 +72,7 @@ static std::tuple fetchOrSubstituteTree( return {std::move(tree), resolvedRef, lockedRef}; } -static void forceTrivialValue(EvalState & state, Value & value, const Pos & pos) +static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos) { if (value.isThunk() && value.isTrivial()) state.forceValue(value, pos); @@ -80,20 +80,20 @@ static void forceTrivialValue(EvalState & state, Value & value, const Pos & pos) static void expectType(EvalState & state, ValueType type, - Value & value, const Pos & pos) + Value & value, const PosIdx pos) { forceTrivialValue(state, value, pos); if (value.type() != type) throw Error("expected %s but got %s at %s", - showType(type), showType(value.type()), pos); + showType(type), showType(value.type()), state.positions[pos]); } static std::map parseFlakeInputs( - EvalState & state, Value * value, const Pos & pos, + EvalState & state, Value * value, const PosIdx pos, const std::optional & baseDir, InputPath lockRootPath); static FlakeInput parseFlakeInput(EvalState & state, - const std::string & inputName, Value * value, const Pos & pos, + const std::string & inputName, Value * value, const PosIdx pos, const std::optional & baseDir, InputPath lockRootPath) { expectType(state, nAttrs, *value, pos); @@ -111,37 +111,39 @@ static FlakeInput parseFlakeInput(EvalState & state, for (nix::Attr attr : *(value->attrs)) { try { if (attr.name == sUrl) { - expectType(state, nString, *attr.value, *attr.pos); + expectType(state, nString, *attr.value, attr.pos); url = attr.value->string.s; attrs.emplace("url", *url); } else if (attr.name == sFlake) { - expectType(state, nBool, *attr.value, *attr.pos); + expectType(state, nBool, *attr.value, attr.pos); input.isFlake = attr.value->boolean; } else if (attr.name == sInputs) { - input.overrides = parseFlakeInputs(state, attr.value, *attr.pos, baseDir, lockRootPath); + input.overrides = parseFlakeInputs(state, attr.value, attr.pos, baseDir, lockRootPath); } else if (attr.name == sFollows) { - expectType(state, nString, *attr.value, *attr.pos); + expectType(state, nString, *attr.value, attr.pos); auto follows(parseInputPath(attr.value->string.s)); follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end()); input.follows = follows; } else { switch (attr.value->type()) { case nString: - attrs.emplace(attr.name, attr.value->string.s); + attrs.emplace(state.symbols[attr.name], attr.value->string.s); break; case nBool: - attrs.emplace(attr.name, Explicit { attr.value->boolean }); + attrs.emplace(state.symbols[attr.name], Explicit { attr.value->boolean }); break; case nInt: - attrs.emplace(attr.name, (long unsigned int)attr.value->integer); + attrs.emplace(state.symbols[attr.name], (long unsigned int)attr.value->integer); break; default: throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected", - attr.name, showType(*attr.value)); + state.symbols[attr.name], showType(*attr.value)); } } } catch (Error & e) { - e.addTrace(*attr.pos, hintfmt("in flake attribute '%s'", attr.name)); + e.addTrace( + state.positions[attr.pos], + hintfmt("in flake attribute '%s'", state.symbols[attr.name])); throw; } } @@ -150,13 +152,13 @@ static FlakeInput parseFlakeInput(EvalState & state, try { input.ref = FlakeRef::fromAttrs(attrs); } catch (Error & e) { - e.addTrace(pos, hintfmt("in flake input")); + e.addTrace(state.positions[pos], hintfmt("in flake input")); throw; } else { attrs.erase("url"); if (!attrs.empty()) - throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, pos); + throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, state.positions[pos]); if (url) input.ref = parseFlakeRef(*url, baseDir, true, input.isFlake); } @@ -168,7 +170,7 @@ static FlakeInput parseFlakeInput(EvalState & state, } static std::map parseFlakeInputs( - EvalState & state, Value * value, const Pos & pos, + EvalState & state, Value * value, const PosIdx pos, const std::optional & baseDir, InputPath lockRootPath) { std::map inputs; @@ -176,11 +178,11 @@ static std::map parseFlakeInputs( expectType(state, nAttrs, *value, pos); for (nix::Attr & inputAttr : *(*value).attrs) { - inputs.emplace(inputAttr.name, + inputs.emplace(state.symbols[inputAttr.name], parseFlakeInput(state, - inputAttr.name, + state.symbols[inputAttr.name], inputAttr.value, - *inputAttr.pos, + inputAttr.pos, baseDir, lockRootPath)); } @@ -218,28 +220,28 @@ static Flake getFlake( Value vInfo; state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack - expectType(state, nAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0)); + expectType(state, nAttrs, vInfo, state.positions.add({flakeFile, foFile}, 0, 0)); if (auto description = vInfo.attrs->get(state.sDescription)) { - expectType(state, nString, *description->value, *description->pos); + expectType(state, nString, *description->value, description->pos); flake.description = description->value->string.s; } auto sInputs = state.symbols.create("inputs"); if (auto inputs = vInfo.attrs->get(sInputs)) - flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos, flakeDir, lockRootPath); + flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir, lockRootPath); auto sOutputs = state.symbols.create("outputs"); if (auto outputs = vInfo.attrs->get(sOutputs)) { - expectType(state, nFunction, *outputs->value, *outputs->pos); + expectType(state, nFunction, *outputs->value, outputs->pos); if (outputs->value->isLambda() && outputs->value->lambda.fun->hasFormals()) { for (auto & formal : outputs->value->lambda.fun->formals->formals) { if (formal.name != state.sSelf) - flake.inputs.emplace(formal.name, FlakeInput { - .ref = parseFlakeRef(formal.name) + flake.inputs.emplace(state.symbols[formal.name], FlakeInput { + .ref = parseFlakeRef(state.symbols[formal.name]) }); } } @@ -250,35 +252,41 @@ static Flake getFlake( auto sNixConfig = state.symbols.create("nixConfig"); if (auto nixConfig = vInfo.attrs->get(sNixConfig)) { - expectType(state, nAttrs, *nixConfig->value, *nixConfig->pos); + expectType(state, nAttrs, *nixConfig->value, nixConfig->pos); for (auto & setting : *nixConfig->value->attrs) { - forceTrivialValue(state, *setting.value, *setting.pos); + forceTrivialValue(state, *setting.value, setting.pos); if (setting.value->type() == nString) - flake.config.settings.insert({setting.name, std::string(state.forceStringNoCtx(*setting.value, *setting.pos))}); + flake.config.settings.emplace( + state.symbols[setting.name], + std::string(state.forceStringNoCtx(*setting.value, setting.pos))); else if (setting.value->type() == nPath) { PathSet emptyContext = {}; flake.config.settings.emplace( - setting.name, - state.coerceToString(*setting.pos, *setting.value, emptyContext, false, true, true) .toOwned()); + state.symbols[setting.name], + state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true) .toOwned()); } else if (setting.value->type() == nInt) - flake.config.settings.insert({setting.name, state.forceInt(*setting.value, *setting.pos)}); + flake.config.settings.emplace( + state.symbols[setting.name], + state.forceInt(*setting.value, setting.pos)); else if (setting.value->type() == nBool) - flake.config.settings.insert({setting.name, Explicit { state.forceBool(*setting.value, *setting.pos) }}); + flake.config.settings.emplace( + state.symbols[setting.name], + Explicit { state.forceBool(*setting.value, setting.pos) }); else if (setting.value->type() == nList) { std::vector ss; for (auto elem : setting.value->listItems()) { if (elem->type() != nString) throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected", - setting.name, showType(*setting.value)); - ss.emplace_back(state.forceStringNoCtx(*elem, *setting.pos)); + state.symbols[setting.name], showType(*setting.value)); + ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos)); } - flake.config.settings.insert({setting.name, ss}); + flake.config.settings.emplace(state.symbols[setting.name], ss); } else throw TypeError("flake configuration setting '%s' is %s", - setting.name, showType(*setting.value)); + state.symbols[setting.name], showType(*setting.value)); } } @@ -288,7 +296,7 @@ static Flake getFlake( attr.name != sOutputs && attr.name != sNixConfig) throw Error("flake '%s' has an unsupported attribute '%s', at %s", - lockedRef, attr.name, *attr.pos); + lockedRef, state.symbols[attr.name], state.positions[attr.pos]); } return flake; @@ -704,12 +712,12 @@ void callFlake(EvalState & state, state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos); } -static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v) { std::string flakeRefS(state.forceStringNoCtx(*args[0], pos)); auto flakeRef = parseFlakeRef(flakeRefS, {}, true); if (evalSettings.pureEval && !flakeRef.input.isLocked()) - throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, pos); + throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]); callFlake(state, lockFlake(state, flakeRef, diff --git a/src/libexpr/function-trace.hh b/src/libexpr/function-trace.hh index 472f2045e..e9a2526bd 100644 --- a/src/libexpr/function-trace.hh +++ b/src/libexpr/function-trace.hh @@ -8,7 +8,7 @@ namespace nix { struct FunctionCallTrace { - const Pos & pos; + const Pos pos; FunctionCallTrace(const Pos & pos); ~FunctionCallTrace(); }; diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index bb7e77b61..11f2b279d 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -61,7 +61,7 @@ std::string DrvInfo::querySystem() const { if (system == "" && attrs) { auto i = attrs->find(state->sSystem); - system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos); + system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos); } return system; } @@ -75,7 +75,7 @@ std::optional DrvInfo::queryDrvPath() const if (i == attrs->end()) drvPath = {std::nullopt}; else - drvPath = {state->coerceToStorePath(*i->pos, *i->value, context)}; + drvPath = {state->coerceToStorePath(i->pos, *i->value, context)}; } return drvPath.value_or(std::nullopt); } @@ -95,7 +95,7 @@ StorePath DrvInfo::queryOutPath() const Bindings::iterator i = attrs->find(state->sOutPath); PathSet context; if (i != attrs->end()) - outPath = state->coerceToStorePath(*i->pos, *i->value, context); + outPath = state->coerceToStorePath(i->pos, *i->value, context); } if (!outPath) throw UnimplementedError("CA derivations are not yet supported"); @@ -109,23 +109,23 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall /* Get the ‘outputs’ list. */ Bindings::iterator i; if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) { - state->forceList(*i->value, *i->pos); + state->forceList(*i->value, i->pos); /* For each output... */ for (auto elem : i->value->listItems()) { - std::string output(state->forceStringNoCtx(*elem, *i->pos)); + std::string output(state->forceStringNoCtx(*elem, i->pos)); 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); + state->forceAttrs(*out->value, i->pos); /* And evaluate its ‘outPath’ attribute. */ Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); if (outPath == out->value->attrs->end()) continue; // FIXME: throw error? PathSet context; - outputs.emplace(output, state->coerceToStorePath(*outPath->pos, *outPath->value, context)); + outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context)); } else outputs.emplace(output, std::nullopt); } @@ -168,7 +168,7 @@ Bindings * DrvInfo::getMeta() if (!attrs) return 0; Bindings::iterator a = attrs->find(state->sMeta); if (a == attrs->end()) return 0; - state->forceAttrs(*a->value, *a->pos); + state->forceAttrs(*a->value, a->pos); meta = a->value->attrs; return meta; } @@ -179,7 +179,7 @@ StringSet DrvInfo::queryMetaNames() StringSet res; if (!getMeta()) return res; for (auto & i : *meta) - res.insert(i.name); + res.emplace(state->symbols[i.name]); return res; } @@ -269,7 +269,7 @@ void DrvInfo::setMeta(const std::string & name, Value * v) { getMeta(); auto attrs = state->buildBindings(1 + (meta ? meta->size() : 0)); - Symbol sym = state->symbols.create(name); + auto sym = state->symbols.create(name); if (meta) for (auto i : *meta) if (i.name != sym) @@ -356,11 +356,11 @@ static void getDerivations(EvalState & state, Value & vIn, 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()) { - debug("evaluating attribute '%1%'", i->name); - if (!std::regex_match(std::string(i->name), attrRegex)) + 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, i->name); + 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, done, ignoreAssertionFailures)) { @@ -369,7 +369,7 @@ static void getDerivations(EvalState & state, Value & vIn, `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)) + if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos)) getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); } } diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index d574121b0..4c28b976e 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -28,9 +28,9 @@ using namespace nix; namespace nix { -static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data) +static inline PosIdx makeCurPos(const YYLTYPE & loc, ParseData * data) { - return Pos(data->origin, data->file, loc.first_line, loc.first_column); + return data->state.positions.add(data->origin, loc.first_line, loc.first_column); } #define CUR_POS makeCurPos(*yylloc, data) @@ -155,7 +155,7 @@ or { return OR_KW; } } catch (const boost::bad_lexical_cast &) { throw ParseError({ .msg = hintfmt("invalid integer '%1%'", yytext), - .errPos = CUR_POS, + .errPos = data->state.positions[CUR_POS], }); } return INT; @@ -165,7 +165,7 @@ or { return OR_KW; } if (errno != 0) throw ParseError({ .msg = hintfmt("invalid float '%1%'", yytext), - .errPos = CUR_POS, + .errPos = data->state.positions[CUR_POS], }); return FLOAT; } @@ -294,7 +294,7 @@ or { return OR_KW; } <> { throw ParseError({ .msg = hintfmt("path has a trailing slash"), - .errPos = CUR_POS, + .errPos = data->state.positions[CUR_POS], }); } diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 51b05de60..efaeba7c6 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -1,5 +1,7 @@ #include "nixexpr.hh" #include "derivations.hh" +#include "eval.hh" +#include "symbol-table.hh" #include "util.hh" #include @@ -12,12 +14,6 @@ std::function deb /* Displaying abstract syntax trees. */ -std::ostream & operator << (std::ostream & str, const Expr & e) -{ - e.show(str); - return str; -} - static void showString(std::ostream & str, std::string_view s) { str << '"'; @@ -30,8 +26,10 @@ static void showString(std::ostream & str, std::string_view s) str << '"'; } -static void showId(std::ostream & str, std::string_view s) +std::ostream & operator <<(std::ostream & str, const SymbolStr & symbol) { + std::string_view s = symbol; + if (s.empty()) str << "\"\""; else if (s == "if") // FIXME: handle other keywords @@ -40,7 +38,7 @@ static void showId(std::ostream & str, std::string_view s) char c = s[0]; if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) { showString(str, s); - return; + return str; } for (auto c : s) if (!((c >= 'a' && c <= 'z') || @@ -48,89 +46,104 @@ static void showId(std::ostream & str, std::string_view s) (c >= '0' && c <= '9') || c == '_' || c == '\'' || c == '-')) { showString(str, s); - return; + return str; } str << s; } -} - -std::ostream & operator << (std::ostream & str, const Symbol & sym) -{ - showId(str, *sym.s); return str; } -void Expr::show(std::ostream & str) const +void Expr::show(const SymbolTable & symbols, std::ostream & str) const { abort(); } -void ExprInt::show(std::ostream & str) const +void ExprInt::show(const SymbolTable & symbols, std::ostream & str) const { str << n; } -void ExprFloat::show(std::ostream & str) const +void ExprFloat::show(const SymbolTable & symbols, std::ostream & str) const { str << nf; } -void ExprString::show(std::ostream & str) const +void ExprString::show(const SymbolTable & symbols, std::ostream & str) const { showString(str, s); } -void ExprPath::show(std::ostream & str) const +void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const { str << s; } -void ExprVar::show(std::ostream & str) const +void ExprVar::show(const SymbolTable & symbols, std::ostream & str) const { - str << name; + str << symbols[name]; } -void ExprSelect::show(std::ostream & str) const +void ExprSelect::show(const SymbolTable & symbols, std::ostream & str) const { - str << "(" << *e << ")." << showAttrPath(attrPath); - if (def) str << " or (" << *def << ")"; + str << "("; + e->show(symbols, str); + str << ")." << showAttrPath(symbols, attrPath); + if (def) { + str << " or ("; + def->show(symbols, str); + str << ")"; + } } -void ExprOpHasAttr::show(std::ostream & str) const +void ExprOpHasAttr::show(const SymbolTable & symbols, std::ostream & str) const { - str << "((" << *e << ") ? " << showAttrPath(attrPath) << ")"; + str << "(("; + e->show(symbols, str); + str << ") ? " << showAttrPath(symbols, attrPath) << ")"; } -void ExprAttrs::show(std::ostream & str) const +void ExprAttrs::show(const SymbolTable & symbols, std::ostream & str) const { if (recursive) str << "rec "; str << "{ "; typedef const decltype(attrs)::value_type * Attr; std::vector sorted; for (auto & i : attrs) sorted.push_back(&i); - std::sort(sorted.begin(), sorted.end(), [](Attr a, Attr b) { - return (const std::string &) a->first < (const std::string &) b->first; - }); + std::sort(sorted.begin(), sorted.end(), [&](Attr a, Attr b) { + std::string_view sa = symbols[a->first], sb = symbols[b->first]; + return sa < sb; + }); for (auto & i : sorted) { if (i->second.inherited) - str << "inherit " << i->first << " " << "; "; - else - str << i->first << " = " << *i->second.e << "; "; + str << "inherit " << symbols[i->first] << " " << "; "; + else { + str << symbols[i->first] << " = "; + i->second.e->show(symbols, str); + str << "; "; + } + } + for (auto & i : dynamicAttrs) { + str << "\"${"; + i.nameExpr->show(symbols, str); + str << "}\" = "; + i.valueExpr->show(symbols, str); + str << "; "; } - for (auto & i : dynamicAttrs) - str << "\"${" << *i.nameExpr << "}\" = " << *i.valueExpr << "; "; str << "}"; } -void ExprList::show(std::ostream & str) const +void ExprList::show(const SymbolTable & symbols, std::ostream & str) const { str << "[ "; - for (auto & i : elems) - str << "(" << *i << ") "; + for (auto & i : elems) { + str << "("; + i->show(symbols, str); + str << ") "; + } str << "]"; } -void ExprLambda::show(std::ostream & str) const +void ExprLambda::show(const SymbolTable & symbols, std::ostream & str) const { str << "("; if (hasFormals()) { @@ -138,74 +151,100 @@ void ExprLambda::show(std::ostream & str) const bool first = true; for (auto & i : formals->formals) { if (first) first = false; else str << ", "; - str << i.name; - if (i.def) str << " ? " << *i.def; + str << symbols[i.name]; + if (i.def) { + str << " ? "; + i.def->show(symbols, str); + } } if (formals->ellipsis) { if (!first) str << ", "; str << "..."; } str << " }"; - if (!arg.empty()) str << " @ "; + if (arg) str << " @ "; } - if (!arg.empty()) str << arg; - str << ": " << *body << ")"; + if (arg) str << symbols[arg]; + str << ": "; + body->show(symbols, str); + str << ")"; } -void ExprCall::show(std::ostream & str) const +void ExprCall::show(const SymbolTable & symbols, std::ostream & str) const { - str << '(' << *fun; + str << '('; + fun->show(symbols, str); for (auto e : args) { str << ' '; - str << *e; + e->show(symbols, str); } str << ')'; } -void ExprLet::show(std::ostream & str) const +void ExprLet::show(const SymbolTable & symbols, std::ostream & str) const { str << "(let "; for (auto & i : attrs->attrs) if (i.second.inherited) { - str << "inherit " << i.first << "; "; + str << "inherit " << symbols[i.first] << "; "; } - else - str << i.first << " = " << *i.second.e << "; "; - str << "in " << *body << ")"; + else { + str << symbols[i.first] << " = "; + i.second.e->show(symbols, str); + str << "; "; + } + str << "in "; + body->show(symbols, str); + str << ")"; } -void ExprWith::show(std::ostream & str) const +void ExprWith::show(const SymbolTable & symbols, std::ostream & str) const { - str << "(with " << *attrs << "; " << *body << ")"; + str << "(with "; + attrs->show(symbols, str); + str << "; "; + body->show(symbols, str); + str << ")"; } -void ExprIf::show(std::ostream & str) const +void ExprIf::show(const SymbolTable & symbols, std::ostream & str) const { - str << "(if " << *cond << " then " << *then << " else " << *else_ << ")"; + str << "(if "; + cond->show(symbols, str); + str << " then "; + then->show(symbols, str); + str << " else "; + else_->show(symbols, str); + str << ")"; } -void ExprAssert::show(std::ostream & str) const +void ExprAssert::show(const SymbolTable & symbols, std::ostream & str) const { - str << "assert " << *cond << "; " << *body; + str << "assert "; + cond->show(symbols, str); + str << "; "; + body->show(symbols, str); } -void ExprOpNot::show(std::ostream & str) const +void ExprOpNot::show(const SymbolTable & symbols, std::ostream & str) const { - str << "(! " << *e << ")"; + str << "(! "; + e->show(symbols, str); + str << ")"; } -void ExprConcatStrings::show(std::ostream & str) const +void ExprConcatStrings::show(const SymbolTable & symbols, std::ostream & str) const { bool first = true; str << "("; for (auto & i : *es) { if (first) first = false; else str << " + "; - str << *i.second; + i.second->show(symbols, str); } str << ")"; } -void ExprPos::show(std::ostream & str) const +void ExprPos::show(const SymbolTable & symbols, std::ostream & str) const { str << "__curPos"; } @@ -236,56 +275,57 @@ std::ostream & operator << (std::ostream & str, const Pos & pos) } -std::string showAttrPath(const AttrPath & attrPath) +std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath) { std::ostringstream out; bool first = true; for (auto & i : attrPath) { if (!first) out << '.'; else first = false; - if (i.symbol.set()) - out << i.symbol; - else - out << "\"${" << *i.expr << "}\""; + if (i.symbol) + out << symbols[i.symbol]; + else { + out << "\"${"; + i.expr->show(symbols, out); + out << "}\""; + } } return out.str(); } -Pos noPos; - /* Computing levels/displacements for variables. */ -void Expr::bindVars(const std::shared_ptr &env) +void Expr::bindVars(const EvalState & es, const std::shared_ptr &env) { abort(); } -void ExprInt::bindVars(const std::shared_ptr &env) +void ExprInt::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; } -void ExprFloat::bindVars(const std::shared_ptr &env) +void ExprFloat::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; } -void ExprString::bindVars(const std::shared_ptr &env) +void ExprString::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; } -void ExprPath::bindVars(const std::shared_ptr &env) +void ExprPath::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; } -void ExprVar::bindVars(const std::shared_ptr &env) +void ExprVar::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; @@ -315,38 +355,38 @@ void ExprVar::bindVars(const std::shared_ptr &env) if (withLevel == -1) { throw UndefinedVarError({ - .msg = hintfmt("undefined variable '%1%'", name), - .errPos = pos + .msg = hintfmt("undefined variable '%1%'", es.symbols[name]), + .errPos = es.positions[pos] }); } fromWith = true; this->level = withLevel; } -void ExprSelect::bindVars(const std::shared_ptr &env) +void ExprSelect::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; - e->bindVars(env); - if (def) def->bindVars(env); + e->bindVars(es, env); + if (def) def->bindVars(es, env); for (auto & i : attrPath) - if (!i.symbol.set()) - i.expr->bindVars(env); + if (!i.symbol) + i.expr->bindVars(es, env); } -void ExprOpHasAttr::bindVars(const std::shared_ptr &env) +void ExprOpHasAttr::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; - e->bindVars(env); + e->bindVars(es, env); for (auto & i : attrPath) - if (!i.symbol.set()) - i.expr->bindVars(env); + if (!i.symbol) + i.expr->bindVars(es, env); } -void ExprAttrs::bindVars(const std::shared_ptr &env) +void ExprAttrs::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; @@ -361,34 +401,34 @@ void ExprAttrs::bindVars(const std::shared_ptr &env) // No need to sort newEnv since attrs is in sorted order. for (auto & i : attrs) - i.second.e->bindVars(i.second.inherited ? env : newEnv); + i.second.e->bindVars(es, i.second.inherited ? env : newEnv); for (auto & i : dynamicAttrs) { - i.nameExpr->bindVars(newEnv); - i.valueExpr->bindVars(newEnv); + i.nameExpr->bindVars(es, newEnv); + i.valueExpr->bindVars(es, newEnv); } } else { for (auto & i : attrs) - i.second.e->bindVars(env); + i.second.e->bindVars(es, env); for (auto & i : dynamicAttrs) { - i.nameExpr->bindVars(env); - i.valueExpr->bindVars(env); + i.nameExpr->bindVars(es, env); + i.valueExpr->bindVars(es, env); } } } -void ExprList::bindVars(const std::shared_ptr &env) +void ExprList::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; for (auto & i : elems) - i->bindVars(env); + i->bindVars(es, env); } -void ExprLambda::bindVars(const std::shared_ptr &env) +void ExprLambda::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; @@ -397,11 +437,11 @@ void ExprLambda::bindVars(const std::shared_ptr &env) new StaticEnv( false, env.get(), (hasFormals() ? formals->formals.size() : 0) + - (arg.empty() ? 0 : 1))); + (!arg ? 0 : 1)); Displacement displ = 0; - if (!arg.empty()) newEnv->vars.emplace_back(arg, displ++); + if (arg) newEnv.vars.emplace_back(arg, displ++); if (hasFormals()) { for (auto & i : formals->formals) @@ -410,23 +450,23 @@ void ExprLambda::bindVars(const std::shared_ptr &env) newEnv->sort(); for (auto & i : formals->formals) - if (i.def) i.def->bindVars(newEnv); + if (i.def) i.def->bindVars(es, newEnv); } - body->bindVars(newEnv); + body->bindVars(es, newEnv); } -void ExprCall::bindVars(const std::shared_ptr &env) +void ExprCall::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; - fun->bindVars(env); + fun->bindVars(es, env); for (auto e : args) - e->bindVars(env); + e->bindVars(es, env); } -void ExprLet::bindVars(const std::shared_ptr &env) +void ExprLet::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; @@ -440,12 +480,12 @@ void ExprLet::bindVars(const std::shared_ptr &env) // No need to sort newEnv since attrs->attrs is in sorted order. for (auto & i : attrs->attrs) - i.second.e->bindVars(i.second.inherited ? env : newEnv); + i.second.e->bindVars(es, i.second.inherited ? env : newEnv); - body->bindVars(newEnv); + body->bindVars(es, newEnv); } -void ExprWith::bindVars(const std::shared_ptr &env) +void ExprWith::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; @@ -462,48 +502,51 @@ void ExprWith::bindVars(const std::shared_ptr &env) break; } - attrs->bindVars(env); - auto newEnv = std::shared_ptr(new StaticEnv(true, env.get())); - body->bindVars(newEnv); + if (debuggerHook) + staticenv = env; + + attrs->bindVars(es, env); + StaticEnv newEnv(true, &env); + body->bindVars(es, newEnv); } -void ExprIf::bindVars(const std::shared_ptr &env) +void ExprIf::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; - cond->bindVars(env); - then->bindVars(env); - else_->bindVars(env); + cond->bindVars(es, env); + then->bindVars(es, env); + else_->bindVars(es, env); } -void ExprAssert::bindVars(const std::shared_ptr &env) +void ExprAssert::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; - cond->bindVars(env); - body->bindVars(env); + cond->bindVars(es, env); + body->bindVars(es, env); } -void ExprOpNot::bindVars(const std::shared_ptr &env) +void ExprOpNot::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; - e->bindVars(env); + e->bindVars(es, env); } -void ExprConcatStrings::bindVars(const std::shared_ptr &env) +void ExprConcatStrings::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; - for (auto & i : *es) - i.second->bindVars(env); + for (auto & i : *this->es) + i.second->bindVars(es, env); } -void ExprPos::bindVars(const std::shared_ptr &env) +void ExprPos::bindVars(const EvalState & es, const std::shared_ptr &env) { if (debuggerHook) staticenv = env; @@ -513,21 +556,24 @@ void ExprPos::bindVars(const std::shared_ptr &env) /* Storing function names. */ -void Expr::setName(Symbol & name) +void Expr::setName(Symbol name) { } -void ExprLambda::setName(Symbol & name) +void ExprLambda::setName(Symbol name) { this->name = name; body->setName(name); } -std::string ExprLambda::showNamePos() const +std::string ExprLambda::showNamePos(const EvalState & state) const { - return fmt("%1% at %2%", name.set() ? "'" + (std::string) name + "'" : "anonymous function", pos); + std::string id(name + ? concatStrings("'", state.symbols[name], "'") + : "anonymous function"); + return fmt("%1% at %2%", id, state.positions[pos]); } @@ -537,8 +583,7 @@ std::string ExprLambda::showNamePos() const size_t SymbolTable::totalSize() const { size_t n = 0; - for (auto & i : store) - n += i.size(); + dump([&] (const std::string & s) { n += s.size(); }); return n; } diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index db210e07b..d275a51e9 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -1,8 +1,12 @@ #pragma once +#include +#include + #include "value.hh" #include "symbol-table.hh" #include "error.hh" +#include "chunked-vector.hh" namespace nix { @@ -24,32 +28,92 @@ extern std::function 0; } +}; + +class PosIdx { + friend class PosTable; + +private: + uint32_t id; + + explicit PosIdx(uint32_t id): id(id) {} + +public: + PosIdx() : id(0) {} + + explicit operator bool() const { return id > 0; } + + bool operator<(const PosIdx other) const { return id < other.id; } +}; + +class PosTable +{ +public: + class Origin { + friend PosTable; + private: + // must always be invalid by default, add() replaces this with the actual value. + // subsequent add() calls use this index as a token to quickly check whether the + // current origins.back() can be reused or not. + mutable uint32_t idx = std::numeric_limits::max(); + + explicit Origin(uint32_t idx): idx(idx), file{}, origin{} {} + + public: + const std::string file; + const FileOrigin origin; + + Origin(std::string file, FileOrigin origin): file(std::move(file)), origin(origin) {} + }; + + struct Offset { + uint32_t line, column; + }; + +private: + std::vector origins; + ChunkedVector offsets; + +public: + PosTable(): offsets(1024) { - return line != 0; + origins.reserve(1024); } - bool operator < (const Pos & p2) const + PosIdx add(const Origin & origin, uint32_t line, uint32_t column) { - if (!line) return p2.line; - if (!p2.line) return false; - int d = ((const std::string &) file).compare((const std::string &) p2.file); - if (d < 0) return true; - if (d > 0) return false; - if (line < p2.line) return true; - if (line > p2.line) return false; - return column < p2.column; + const auto idx = offsets.add({line, column}).second; + if (origins.empty() || origins.back().idx != origin.idx) { + origin.idx = idx; + origins.push_back(origin); + } + return PosIdx(idx + 1); + } + + Pos operator[](PosIdx p) const + { + if (p.id == 0 || p.id > offsets.size()) + return {}; + const auto idx = p.id - 1; + /* we want the last key <= idx, so we'll take prev(first key > idx). + this is guaranteed to never rewind origin.begin because the first + key is always 0. */ + const auto pastOrigin = std::upper_bound( + origins.begin(), origins.end(), Origin(idx), + [] (const auto & a, const auto & b) { return a.idx < b.idx; }); + const auto origin = *std::prev(pastOrigin); + const auto offset = offsets[idx]; + return {origin.file, origin.origin, offset.line, offset.column}; } }; -extern Pos noPos; +inline PosIdx noPos = {}; std::ostream & operator << (std::ostream & str, const Pos & pos); @@ -65,13 +129,13 @@ struct AttrName { Symbol symbol; Expr * expr; - AttrName(const Symbol & s) : symbol(s) {}; + AttrName(Symbol s) : symbol(s) {}; AttrName(Expr * e) : expr(e) {}; }; typedef std::vector AttrPath; -std::string showAttrPath(const AttrPath & attrPath); +std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath); /* Abstract syntax of Nix expressions. */ @@ -79,22 +143,19 @@ std::string showAttrPath(const AttrPath & attrPath); struct Expr { virtual ~Expr() { }; - virtual void show(std::ostream & str) const; - virtual void bindVars(const std::shared_ptr & env); + virtual void show(const SymbolTable & symbols, std::ostream & str) const; + virtual void bindVars(const EvalState & es, const std::shared_ptr & env); virtual void eval(EvalState & state, Env & env, Value & v); virtual Value * maybeThunk(EvalState & state, Env & env); - virtual void setName(Symbol & name); - + virtual void setName(Symbol name); std::shared_ptr staticenv; - virtual const Pos* getPos() const = 0; + virtual const PosIdx getPos() const = 0; }; -std::ostream & operator << (std::ostream & str, const Expr & e); - #define COMMON_METHODS \ - void show(std::ostream & str) const; \ + void show(const SymbolTable & symbols, std::ostream & str) const; \ void eval(EvalState & state, Env & env, Value & v); \ - void bindVars(const std::shared_ptr & env); + void bindVars(const EvalState & es, const std::shared_ptr & env); struct ExprInt : Expr { @@ -102,7 +163,7 @@ struct ExprInt : Expr Value v; ExprInt(NixInt n) : n(n) { v.mkInt(n); }; Value * maybeThunk(EvalState & state, Env & env); - const Pos* getPos() const { return 0; } + const PosIdx getPos() const { return noPos; } COMMON_METHODS }; @@ -112,7 +173,7 @@ struct ExprFloat : Expr Value v; ExprFloat(NixFloat nf) : nf(nf) { v.mkFloat(nf); }; Value * maybeThunk(EvalState & state, Env & env); - const Pos* getPos() const { return 0; } + const PosIdx getPos() const { return noPos; } COMMON_METHODS }; @@ -122,7 +183,7 @@ struct ExprString : Expr Value v; ExprString(std::string s) : s(std::move(s)) { v.mkString(this->s.data()); }; Value * maybeThunk(EvalState & state, Env & env); - const Pos* getPos() const { return 0; } + const PosIdx getPos() const { return noPos; } COMMON_METHODS }; @@ -132,7 +193,7 @@ struct ExprPath : Expr Value v; ExprPath(std::string s) : s(std::move(s)) { v.mkPath(this->s.c_str()); }; Value * maybeThunk(EvalState & state, Env & env); - const Pos* getPos() const { return 0; } + const PosIdx getPos() const { return noPos; } COMMON_METHODS }; @@ -141,7 +202,7 @@ typedef uint32_t Displacement; struct ExprVar : Expr { - Pos pos; + PosIdx pos; Symbol name; /* Whether the variable comes from an environment (e.g. a rec, let @@ -157,21 +218,21 @@ struct ExprVar : Expr Level level; Displacement displ; - ExprVar(const Symbol & name) : name(name) { }; - ExprVar(const Pos & pos, const Symbol & name) : pos(pos), name(name) { }; + ExprVar(Symbol name) : name(name) { }; + ExprVar(const PosIdx & pos, Symbol name) : pos(pos), name(name) { }; Value * maybeThunk(EvalState & state, Env & env); - const Pos* getPos() const { return &pos; } + const PosIdx getPos() const { return pos; } COMMON_METHODS }; struct ExprSelect : Expr { - Pos pos; + PosIdx pos; Expr * e, * def; AttrPath attrPath; - ExprSelect(const Pos & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { }; - ExprSelect(const Pos & pos, Expr * e, const Symbol & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; - const Pos* getPos() const { return &pos; } + ExprSelect(const PosIdx & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { }; + ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; + const PosIdx getPos() const { return pos; } COMMON_METHODS }; @@ -180,20 +241,20 @@ struct ExprOpHasAttr : Expr Expr * e; AttrPath attrPath; ExprOpHasAttr(Expr * e, const AttrPath & attrPath) : e(e), attrPath(attrPath) { }; - const Pos* getPos() const { return e->getPos(); } + const PosIdx getPos() const { return e->getPos(); } COMMON_METHODS }; struct ExprAttrs : Expr { bool recursive; - Pos pos; + PosIdx pos; struct AttrDef { bool inherited; Expr * e; - Pos pos; + PosIdx pos; Displacement displ; // displacement - AttrDef(Expr * e, const Pos & pos, bool inherited=false) + AttrDef(Expr * e, const PosIdx & pos, bool inherited=false) : inherited(inherited), e(e), pos(pos) { }; AttrDef() { }; }; @@ -201,15 +262,15 @@ struct ExprAttrs : Expr AttrDefs attrs; struct DynamicAttrDef { Expr * nameExpr, * valueExpr; - Pos pos; - DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const Pos & pos) + PosIdx pos; + DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const PosIdx & pos) : nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { }; }; typedef std::vector DynamicAttrDefs; DynamicAttrDefs dynamicAttrs; - ExprAttrs(const Pos &pos) : recursive(false), pos(pos) { }; - ExprAttrs() : recursive(false), pos(noPos) { }; - const Pos* getPos() const { return &pos; } + ExprAttrs(const PosIdx &pos) : recursive(false), pos(pos) { }; + ExprAttrs() : recursive(false) { }; + const PosIdx getPos() const { return pos; } COMMON_METHODS }; @@ -217,16 +278,15 @@ struct ExprList : Expr { std::vector elems; ExprList() { }; - const Pos* getPos() const { return 0; } + const PosIdx getPos() const { return pos; } COMMON_METHODS }; struct Formal { - Pos pos; + PosIdx pos; Symbol name; Expr * def; - Formal(const Pos & pos, const Symbol & name, Expr * def) : pos(pos), name(name), def(def) { }; }; struct Formals @@ -235,18 +295,20 @@ struct Formals Formals_ formals; bool ellipsis; - bool has(Symbol arg) const { + bool has(Symbol arg) const + { auto it = std::lower_bound(formals.begin(), formals.end(), arg, [] (const Formal & f, const Symbol & sym) { return f.name < sym; }); return it != formals.end() && it->name == arg; } - std::vector lexicographicOrder() const + std::vector lexicographicOrder(const SymbolTable & symbols) const { std::vector result(formals.begin(), formals.end()); std::sort(result.begin(), result.end(), - [] (const Formal & a, const Formal & b) { - return std::string_view(a.name) < std::string_view(b.name); + [&] (const Formal & a, const Formal & b) { + std::string_view sa = symbols[a.name], sb = symbols[b.name]; + return sa < sb; }); return result; } @@ -254,19 +316,23 @@ struct Formals struct ExprLambda : Expr { - Pos pos; + PosIdx pos; Symbol name; Symbol arg; Formals * formals; Expr * body; - ExprLambda(const Pos & pos, const Symbol & arg, Formals * formals, Expr * body) + ExprLambda(PosIdx pos, Symbol arg, Formals * formals, Expr * body) : pos(pos), arg(arg), formals(formals), body(body) { }; - void setName(Symbol & name); - std::string showNamePos() const; + ExprLambda(PosIdx pos, Formals * formals, Expr * body) + : pos(pos), formals(formals), body(body) + { + } + void setName(Symbol name); + std::string showNamePos(const EvalState & state) const; inline bool hasFormals() const { return formals != nullptr; } - const Pos* getPos() const { return &pos; } + const PosIdx getPos() const { return pos; } COMMON_METHODS }; @@ -274,11 +340,11 @@ struct ExprCall : Expr { Expr * fun; std::vector args; - Pos pos; - ExprCall(const Pos & pos, Expr * fun, std::vector && args) + PosIdx pos; + ExprCall(const PosIdx & pos, Expr * fun, std::vector && args) : fun(fun), args(args), pos(pos) { } - const Pos* getPos() const { return &pos; } + const PosIdx getPos() const { return pos; } COMMON_METHODS }; @@ -287,35 +353,35 @@ struct ExprLet : Expr ExprAttrs * attrs; Expr * body; ExprLet(ExprAttrs * attrs, Expr * body) : attrs(attrs), body(body) { }; - const Pos* getPos() const { return 0; } + const PosIdx getPos() const { return noPos; } COMMON_METHODS }; struct ExprWith : Expr { - Pos pos; + PosIdx pos; Expr * attrs, * body; size_t prevWith; - ExprWith(const Pos & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { }; - const Pos* getPos() const { return &pos; } + ExprWith(const PosIdx & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { }; + const PosIdx getPos() const { return pos; } COMMON_METHODS }; struct ExprIf : Expr { - Pos pos; + PosIdx pos; Expr * cond, * then, * else_; - ExprIf(const Pos & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { }; - const Pos* getPos() const { return &pos; } + ExprIf(const PosIdx & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { }; + const PosIdx getPos() const { return pos; } COMMON_METHODS }; struct ExprAssert : Expr { - Pos pos; + PosIdx pos; Expr * cond, * body; - ExprAssert(const Pos & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { }; - const Pos* getPos() const { return &pos; } + ExprAssert(const PosIdx & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { }; + const PosIdx getPos() const { return pos; } COMMON_METHODS }; @@ -330,20 +396,20 @@ struct ExprOpNot : Expr #define MakeBinOp(name, s) \ struct name : Expr \ { \ - Pos pos; \ + PosIdx pos; \ Expr * e1, * e2; \ name(Expr * e1, Expr * e2) : e1(e1), e2(e2) { }; \ - name(const Pos & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \ - void show(std::ostream & str) const \ + name(const PosIdx & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \ + void show(const SymbolTable & symbols, std::ostream & str) const \ { \ - str << "(" << *e1 << " " s " " << *e2 << ")"; \ + str << "("; e1->show(symbols, str); str << " " s " "; e2->show(symbols, str); str << ")"; \ } \ - void bindVars(const std::shared_ptr & env) \ + void bindVars(const EvalState & es, const std::shared_ptr & env) \ { \ - e1->bindVars(env); e2->bindVars(env); \ + e1->bindVars(es, env); e2->bindVars(es, env); \ } \ void eval(EvalState & state, Env & env, Value & v); \ - const Pos* getPos() const { return &pos; } \ + const PosIdx getPos() const { return pos; } \ }; MakeBinOp(ExprOpEq, "==") @@ -356,20 +422,20 @@ MakeBinOp(ExprOpConcatLists, "++") struct ExprConcatStrings : Expr { - Pos pos; + PosIdx pos; bool forceString; - std::vector > * es; - ExprConcatStrings(const Pos & pos, bool forceString, std::vector > * es) + std::vector > * es; + ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector > * es) : pos(pos), forceString(forceString), es(es) { }; - const Pos* getPos() const { return &pos; } + const PosIdx getPos() const { return pos; } COMMON_METHODS }; struct ExprPos : Expr { - Pos pos; - ExprPos(const Pos & pos) : pos(pos) { }; - const Pos* getPos() const { return &pos; } + PosIdx pos; + ExprPos(const PosIdx & pos) : pos(pos) { }; + const PosIdx getPos() const { return pos; } COMMON_METHODS }; @@ -407,7 +473,7 @@ struct StaticEnv vars.erase(it, end); } - Vars::const_iterator find(const Symbol & name) const + Vars::const_iterator find(Symbol name) const { Vars::value_type key(name, 0); auto i = std::lower_bound(vars.begin(), vars.end(), key); diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 1a5832e6b..8edafdd57 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -32,12 +32,12 @@ namespace nix { SymbolTable & symbols; Expr * result; Path basePath; - Symbol file; - FileOrigin origin; + PosTable::Origin origin; std::optional error; - ParseData(EvalState & state) + ParseData(EvalState & state, PosTable::Origin origin) : state(state) , symbols(state.symbols) + , origin(std::move(origin)) { }; }; @@ -77,26 +77,26 @@ using namespace nix; namespace nix { -static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prevPos) +static void dupAttr(const EvalState & state, const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos) { throw ParseError({ .msg = hintfmt("attribute '%1%' already defined at %2%", - showAttrPath(attrPath), prevPos), - .errPos = pos + showAttrPath(state.symbols, attrPath), state.positions[prevPos]), + .errPos = state.positions[pos] }); } -static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos) +static void dupAttr(const EvalState & state, Symbol attr, const PosIdx pos, const PosIdx prevPos) { throw ParseError({ - .msg = hintfmt("attribute '%1%' already defined at %2%", attr, prevPos), - .errPos = pos + .msg = hintfmt("attribute '%1%' already defined at %2%", state.symbols[attr], state.positions[prevPos]), + .errPos = state.positions[pos] }); } static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, - Expr * e, const Pos & pos) + Expr * e, const PosIdx pos, const nix::EvalState & state) { AttrPath::iterator i; // All attrpaths have at least one attr @@ -104,15 +104,15 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, // Checking attrPath validity. // =========================== for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) { - if (i->symbol.set()) { + if (i->symbol) { ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol); if (j != attrs->attrs.end()) { if (!j->second.inherited) { ExprAttrs * attrs2 = dynamic_cast(j->second.e); - if (!attrs2) dupAttr(attrPath, pos, j->second.pos); + if (!attrs2) dupAttr(state, attrPath, pos, j->second.pos); attrs = attrs2; } else - dupAttr(attrPath, pos, j->second.pos); + dupAttr(state, attrPath, pos, j->second.pos); } else { ExprAttrs * nested = new ExprAttrs; attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos); @@ -126,7 +126,7 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, } // Expr insertion. // ========================== - if (i->symbol.set()) { + if (i->symbol) { ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol); if (j != attrs->attrs.end()) { // This attr path is already defined. However, if both @@ -139,11 +139,11 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, for (auto & ad : ae->attrs) { auto j2 = jAttrs->attrs.find(ad.first); if (j2 != jAttrs->attrs.end()) // Attr already defined in iAttrs, error. - dupAttr(ad.first, j2->second.pos, ad.second.pos); + dupAttr(state, ad.first, j2->second.pos, ad.second.pos); jAttrs->attrs.emplace(ad.first, ad.second); } } else { - dupAttr(attrPath, pos, j->second.pos); + dupAttr(state, attrPath, pos, j->second.pos); } } else { // This attr path is not defined. Let's create it. @@ -157,14 +157,14 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, static Formals * toFormals(ParseData & data, ParserFormals * formals, - Pos pos = noPos, Symbol arg = {}) + PosIdx pos = noPos, Symbol arg = {}) { std::sort(formals->formals.begin(), formals->formals.end(), [] (const auto & a, const auto & b) { return std::tie(a.name, a.pos) < std::tie(b.name, b.pos); }); - std::optional> duplicate; + std::optional> duplicate; for (size_t i = 0; i + 1 < formals->formals.size(); i++) { if (formals->formals[i].name != formals->formals[i + 1].name) continue; @@ -173,18 +173,18 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals, } if (duplicate) throw ParseError({ - .msg = hintfmt("duplicate formal function argument '%1%'", duplicate->first), - .errPos = duplicate->second + .msg = hintfmt("duplicate formal function argument '%1%'", data.symbols[duplicate->first]), + .errPos = data.state.positions[duplicate->second] }); Formals result; result.ellipsis = formals->ellipsis; result.formals = std::move(formals->formals); - if (arg.set() && result.has(arg)) + if (arg && result.has(arg)) throw ParseError({ - .msg = hintfmt("duplicate formal function argument '%1%'", arg), - .errPos = pos + .msg = hintfmt("duplicate formal function argument '%1%'", data.symbols[arg]), + .errPos = data.state.positions[pos] }); delete formals; @@ -192,8 +192,8 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals, } -static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, - std::vector > > & es) +static Expr * stripIndentation(const PosIdx pos, SymbolTable & symbols, + std::vector > > & es) { if (es.empty()) return new ExprString(""); @@ -233,7 +233,7 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, } /* Strip spaces from each line. */ - std::vector > * es2 = new std::vector >; + auto * es2 = new std::vector >; atStartOfLine = true; size_t curDropped = 0; size_t n = es.size(); @@ -284,9 +284,9 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, } -static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data) +static inline PosIdx makeCurPos(const YYLTYPE & loc, ParseData * data) { - return Pos(data->origin, data->file, loc.first_line, loc.first_column); + return data->state.positions.add(data->origin, loc.first_line, loc.first_column); } #define CUR_POS makeCurPos(*yylocp, data) @@ -299,7 +299,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err { data->error = { .msg = hintfmt(error), - .errPos = makeCurPos(*loc, data) + .errPos = data->state.positions[makeCurPos(*loc, data)] }; } @@ -320,8 +320,8 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err StringToken uri; StringToken str; std::vector * attrNames; - std::vector > * string_parts; - std::vector > > * ind_string_parts; + std::vector > * string_parts; + std::vector > > * ind_string_parts; } %type start expr expr_function expr_if expr_op @@ -369,15 +369,15 @@ expr_function : ID ':' expr_function { $$ = new ExprLambda(CUR_POS, data->symbols.create($1), 0, $3); } | '{' formals '}' ':' expr_function - { $$ = new ExprLambda(CUR_POS, data->symbols.create(""), toFormals(*data, $2), $5); } + { $$ = new ExprLambda(CUR_POS, toFormals(*data, $2), $5); } | '{' formals '}' '@' ID ':' expr_function { - Symbol arg = data->symbols.create($5); + auto arg = data->symbols.create($5); $$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $2, CUR_POS, arg), $7); } | ID '@' '{' formals '}' ':' expr_function { - Symbol arg = data->symbols.create($1); + auto arg = data->symbols.create($1); $$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $4, CUR_POS, arg), $7); } | ASSERT expr ';' expr_function @@ -388,7 +388,7 @@ expr_function { if (!$2->dynamicAttrs.empty()) throw ParseError({ .msg = hintfmt("dynamic attributes not allowed in let"), - .errPos = CUR_POS + .errPos = data->state.positions[CUR_POS] }); $$ = new ExprLet($2, $4); } @@ -415,7 +415,7 @@ expr_op | expr_op UPDATE expr_op { $$ = new ExprOpUpdate(CUR_POS, $1, $3); } | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); } | expr_op '+' expr_op - { $$ = new ExprConcatStrings(CUR_POS, false, new std::vector >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); } + { $$ = new ExprConcatStrings(CUR_POS, false, new std::vector >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); } | expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); } | expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); } | expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); } @@ -477,7 +477,7 @@ expr_simple if (noURLLiterals) throw ParseError({ .msg = hintfmt("URL literals are disabled"), - .errPos = CUR_POS + .errPos = data->state.positions[CUR_POS] }); $$ = new ExprString(std::string($1)); } @@ -503,9 +503,9 @@ string_parts_interpolated : string_parts_interpolated STR { $$ = $1; $1->emplace_back(makeCurPos(@2, data), new ExprString(std::string($2))); } | string_parts_interpolated DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $3); } - | DOLLAR_CURLY expr '}' { $$ = new std::vector >; $$->emplace_back(makeCurPos(@1, data), $2); } + | DOLLAR_CURLY expr '}' { $$ = new std::vector >; $$->emplace_back(makeCurPos(@1, data), $2); } | STR DOLLAR_CURLY expr '}' { - $$ = new std::vector >; + $$ = new std::vector >; $$->emplace_back(makeCurPos(@1, data), new ExprString(std::string($1))); $$->emplace_back(makeCurPos(@2, data), $3); } @@ -528,17 +528,17 @@ path_start ind_string_parts : ind_string_parts IND_STR { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $2); } | ind_string_parts DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $3); } - | { $$ = new std::vector > >; } + | { $$ = new std::vector > >; } ; binds - : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data)); } + : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data), data->state); } | binds INHERIT attrs ';' { $$ = $1; for (auto & i : *$3) { if ($$->attrs.find(i.symbol) != $$->attrs.end()) - dupAttr(i.symbol, makeCurPos(@3, data), $$->attrs[i.symbol].pos); - Pos pos = makeCurPos(@3, data); + dupAttr(data->state, i.symbol, makeCurPos(@3, data), $$->attrs[i.symbol].pos); + auto pos = makeCurPos(@3, data); $$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprVar(CUR_POS, i.symbol), pos, true)); } } @@ -547,7 +547,7 @@ binds /* !!! Should ensure sharing of the expression in $4. */ for (auto & i : *$6) { if ($$->attrs.find(i.symbol) != $$->attrs.end()) - dupAttr(i.symbol, makeCurPos(@6, data), $$->attrs[i.symbol].pos); + dupAttr(data->state, i.symbol, makeCurPos(@6, data), $$->attrs[i.symbol].pos); $$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data))); } } @@ -565,7 +565,7 @@ attrs } else throw ParseError({ .msg = hintfmt("dynamic attributes not allowed in inherit"), - .errPos = makeCurPos(@2, data) + .errPos = data->state.positions[makeCurPos(@2, data)] }); } | { $$ = new AttrPath; } @@ -621,8 +621,8 @@ formals ; formal - : ID { $$ = new Formal(CUR_POS, data->symbols.create($1), 0); } - | ID '?' expr { $$ = new Formal(CUR_POS, data->symbols.create($1), $3); } + : ID { $$ = new Formal{CUR_POS, data->symbols.create($1), 0}; } + | ID '?' expr { $$ = new Formal{CUR_POS, data->symbols.create($1), $3}; } ; %% @@ -646,19 +646,19 @@ Expr * EvalState::parse(char * text, size_t length, FileOrigin origin, const PathView path, const PathView basePath, std::shared_ptr & staticEnv) { yyscan_t scanner; - ParseData data(*this); - data.origin = origin; + std::string file; switch (origin) { case foFile: - data.file = data.symbols.create(path); + file = path; break; case foStdin: case foString: - data.file = data.symbols.create(text); + file = text; break; default: assert(false); } + ParseData data(*this, {file, origin}); data.basePath = basePath; yylex_init(&scanner); @@ -668,7 +668,7 @@ Expr * EvalState::parse(char * text, size_t length, FileOrigin origin, if (res) throw ParseError(data.error.value()); - data.result->bindVars(staticEnv); + data.result->bindVars(*this, staticEnv); return data.result; } @@ -760,7 +760,7 @@ Path EvalState::findFile(const std::string_view path) } -Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, const Pos & pos) +Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos) { for (auto & i : searchPath) { std::string suffix; @@ -787,7 +787,7 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c ? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)" : "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)", path), - .errPos = pos + .errPos = positions[pos] }); debugLastTrace(e); throw e; diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index b1d2f5f40..212ce3de9 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -108,7 +108,7 @@ struct RealisePathFlags { bool checkForPureEval = true; }; -static Path realisePath(EvalState & state, const Pos & pos, Value & v, const RealisePathFlags flags = {}) +static Path realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {}) { PathSet context; @@ -117,7 +117,7 @@ static Path realisePath(EvalState & state, const Pos & pos, Value & v, const Rea try { return state.coerceToPath(pos, v, context); } catch (Error & e) { - e.addTrace(pos, "while realising the context of a path"); + e.addTrace(state.positions[pos], "while realising the context of a path"); throw; } }(); @@ -131,7 +131,7 @@ static Path realisePath(EvalState & state, const Pos & pos, Value & v, const Rea ? state.checkSourcePath(realPath) : realPath; } catch (Error & e) { - e.addTrace(pos, "while realising the context of path '%s'", path); + e.addTrace(state.positions[pos], "while realising the context of path '%s'", path); throw; } } @@ -169,7 +169,7 @@ static void mkOutputString( /* Load and evaluate an expression from path specified by the argument. */ -static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vScope, Value & v) +static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v) { auto path = realisePath(state, pos, vPath); @@ -249,7 +249,7 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS static RegisterPrimOp primop_scopedImport(RegisterPrimOp::Info { .name = "scopedImport", .arity = 2, - .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) + .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v) { import(state, pos, *args[1], args[0], v); } @@ -311,7 +311,7 @@ static RegisterPrimOp primop_import({ (The function argument doesn’t have to be called `x` in `foo.nix`; any name would work.) )", - .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) + .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v) { import(state, pos, *args[0], nullptr, v); } @@ -322,7 +322,7 @@ static RegisterPrimOp primop_import({ extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v); /* Load a ValueInitializer from a DSO and return whatever it initializes */ -void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v) +void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto path = realisePath(state, pos, *args[0]); @@ -362,7 +362,7 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value /* Execute a program and parse its output */ -void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) +void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); auto elems = args[0]->listElems(); @@ -370,7 +370,7 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) if (count == 0) { auto e = EvalError({ .msg = hintfmt("at least one argument to 'exec' required"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -387,7 +387,7 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) auto ee = EvalError({ .msg = hintfmt("cannot execute '%1%', since path '%2%' is not valid", program, e.path), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(ee); throw ee; @@ -396,22 +396,23 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) auto output = runProgram(program, true, commandArgs); Expr * parsed; try { - parsed = state.parseExprFromString(std::move(output), pos.file); + auto base = state.positions[pos]; + parsed = state.parseExprFromString(std::move(output), base.file); } catch (Error & e) { - e.addTrace(pos, "While parsing the output from '%1%'", program); + e.addTrace(state.positions[pos], "While parsing the output from '%1%'", program); throw; } try { state.eval(parsed, v); } catch (Error & e) { - e.addTrace(pos, "While evaluating the output from '%1%'", program); + e.addTrace(state.positions[pos], "While evaluating the output from '%1%'", program); throw; } } /* Return a string representing the type of the expression. */ -static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_typeOf(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); std::string t; @@ -430,7 +431,7 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu case nFloat: t = "float"; break; case nThunk: abort(); } - v.mkString(state.symbols.create(t)); + v.mkString(t); } static RegisterPrimOp primop_typeOf({ @@ -445,7 +446,7 @@ static RegisterPrimOp primop_typeOf({ }); /* Determine whether the argument is the null value. */ -static void prim_isNull(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isNull(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nNull); @@ -465,7 +466,7 @@ static RegisterPrimOp primop_isNull({ }); /* Determine whether the argument is a function. */ -static void prim_isFunction(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isFunction(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nFunction); @@ -481,7 +482,7 @@ static RegisterPrimOp primop_isFunction({ }); /* Determine whether the argument is an integer. */ -static void prim_isInt(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isInt(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nInt); @@ -497,7 +498,7 @@ static RegisterPrimOp primop_isInt({ }); /* Determine whether the argument is a float. */ -static void prim_isFloat(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isFloat(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nFloat); @@ -513,7 +514,7 @@ static RegisterPrimOp primop_isFloat({ }); /* Determine whether the argument is a string. */ -static void prim_isString(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isString(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nString); @@ -529,7 +530,7 @@ static RegisterPrimOp primop_isString({ }); /* Determine whether the argument is a Boolean. */ -static void prim_isBool(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isBool(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nBool); @@ -545,7 +546,7 @@ static RegisterPrimOp primop_isBool({ }); /* Determine whether the argument is a path. */ -static void prim_isPath(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isPath(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nPath); @@ -621,33 +622,33 @@ static Bindings::iterator getAttr( std::string_view funcName, Symbol attrSym, Bindings * attrSet, - const Pos & pos) + const PosIdx pos) { Bindings::iterator value = attrSet->find(attrSym); if (value == attrSet->end()) { hintformat errorMsg = hintfmt( "attribute '%s' missing for call to '%s'", - attrSym, + state.symbols[attrSym], funcName ); - Pos aPos = *attrSet->pos; - if (aPos == noPos) { + auto aPos = attrSet->pos; + if (!aPos) { auto e = TypeError({ .msg = errorMsg, - .errPos = pos, + .errPos = state.positions[pos], }); state.debugLastTrace(e); throw e; } else { auto e = TypeError({ .msg = errorMsg, - .errPos = aPos, + .errPos = state.positions[aPos], }); // Adding another trace for the function name to make it clear // which call received wrong arguments. - e.addTrace(pos, hintfmt("while invoking '%s'", funcName)); + e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", funcName)); state.debugLastTrace(e); throw e; } @@ -656,7 +657,7 @@ static Bindings::iterator getAttr( return value; } -static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); @@ -706,7 +707,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar { auto e = EvalError({ .msg = hintfmt("attribute 'key' required"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -806,7 +807,7 @@ static RegisterPrimOp primop_abort({ .doc = R"( Abort Nix expression evaluation and print the error message *s*. )", - .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) + .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto s = state.coerceToString(pos, *args[0], context).toOwned(); @@ -828,7 +829,7 @@ static RegisterPrimOp primop_throw({ derivations, a derivation that throws an error is silently skipped (which is not the case for `abort`). )", - .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) + .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto s = state.coerceToString(pos, *args[0], context).toOwned(); @@ -838,7 +839,7 @@ static RegisterPrimOp primop_throw({ } }); -static void prim_addErrorContext(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) { try { state.forceValue(*args[1], pos); @@ -856,7 +857,7 @@ static RegisterPrimOp primop_addErrorContext(RegisterPrimOp::Info { .fun = prim_addErrorContext, }); -static void prim_ceil(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_ceil(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto value = state.forceFloat(*args[0], args[0]->determinePos(pos)); v.mkInt(ceil(value)); @@ -875,7 +876,7 @@ static RegisterPrimOp primop_ceil({ .fun = prim_ceil, }); -static void prim_floor(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_floor(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto value = state.forceFloat(*args[0], args[0]->determinePos(pos)); v.mkInt(floor(value)); @@ -896,7 +897,7 @@ static RegisterPrimOp primop_floor({ /* Try evaluating the argument. Success => {success=true; value=something;}, * else => {success=false; value=false;} */ -static void prim_tryEval(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto attrs = state.buildBindings(2); auto saveDebuggerHook = debuggerHook; @@ -935,7 +936,7 @@ static RegisterPrimOp primop_tryEval({ }); /* Return an environment variable. Use with care. */ -static void prim_getEnv(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_getEnv(EvalState & state, const PosIdx pos, Value * * args, Value & v) { std::string name(state.forceStringNoCtx(*args[0], pos)); v.mkString(evalSettings.restrictEval || evalSettings.pureEval ? "" : getEnv(name).value_or("")); @@ -959,7 +960,7 @@ static RegisterPrimOp primop_getEnv({ }); /* Evaluate the first argument, then return the second argument. */ -static void prim_seq(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_seq(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); @@ -978,7 +979,7 @@ static RegisterPrimOp primop_seq({ /* Evaluate the first argument deeply (i.e. recursing into lists and attrsets), then return the second argument. */ -static void prim_deepSeq(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_deepSeq(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValueDeep(*args[0]); state.forceValue(*args[1], pos); @@ -998,13 +999,13 @@ static RegisterPrimOp primop_deepSeq({ /* Evaluate the first expression and print it on standard error. Then return the second expression. Useful for debugging. */ -static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_trace(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); if (args[0]->type() == nString) printError("trace: %1%", args[0]->string.s); else - printError("trace: %1%", *args[0]); + printError("trace: %1%", printValue(state, *args[0])); state.forceValue(*args[1], pos); v = *args[1]; } @@ -1033,7 +1034,7 @@ static RegisterPrimOp primop_trace({ derivation; `drvPath' containing the path of the Nix expression; and `type' set to `derivation' to indicate that this is a derivation. */ -static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); @@ -1047,11 +1048,11 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * ); std::string drvName; - Pos & posDrvName(*attr->pos); + const auto posDrvName = attr->pos; try { drvName = state.forceStringNoCtx(*attr->value, pos); } catch (Error & e) { - e.addTrace(posDrvName, "while evaluating the derivation attribute 'name'"); + e.addTrace(state.positions[posDrvName], "while evaluating the derivation attribute 'name'"); throw; } @@ -1083,9 +1084,9 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * StringSet outputs; outputs.insert("out"); - for (auto & i : args[0]->attrs->lexicographicOrder()) { + for (auto & i : args[0]->attrs->lexicographicOrder(state.symbols)) { if (i->name == state.sIgnoreNulls) continue; - const std::string & key = i->name; + const std::string & key = state.symbols[i->name]; vomit("processing attribute '%1%'", key); auto handleHashMode = [&](const std::string_view s) { @@ -1095,7 +1096,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * { auto e = EvalError({ .msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); state.debugLastTrace(e); throw e; @@ -1109,7 +1110,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * { auto e = EvalError({ .msg = hintfmt("duplicate derivation output '%1%'", j), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); state.debugLastTrace(e); throw e; @@ -1123,7 +1124,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * { auto e = EvalError({ .msg = hintfmt("invalid derivation output name 'drv'" ), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); state.debugLastTrace(e); throw e; @@ -1134,7 +1135,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * { auto e = EvalError({ .msg = hintfmt("derivation cannot have an empty set of outputs"), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); state.debugLastTrace(e); throw e; @@ -1201,7 +1202,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * } } else { - auto s = state.coerceToString(*i->pos, *i->value, context, true).toOwned(); + auto s = state.coerceToString(i->pos, *i->value, context, 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); @@ -1215,7 +1216,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * } } catch (Error & e) { - e.addTrace(posDrvName, + e.addTrace(state.positions[posDrvName], "while evaluating the attribute '%1%' of the derivation '%2%'", key, drvName); throw; @@ -1266,7 +1267,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * { auto e = EvalError({ .msg = hintfmt("required attribute 'builder' missing"), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); state.debugLastTrace(e); throw e; @@ -1276,7 +1277,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * { auto e = EvalError({ .msg = hintfmt("required attribute 'system' missing"), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); state.debugLastTrace(e); throw e; @@ -1287,7 +1288,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * { auto e = EvalError({ .msg = hintfmt("derivation names are not allowed to end in '%s'", drvExtension), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); state.debugLastTrace(e); throw e; @@ -1302,7 +1303,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * { auto e = Error({ .msg = hintfmt("multiple outputs are not supported in fixed-output derivations"), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); state.debugLastTrace(e); throw e; @@ -1326,7 +1327,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * if (contentAddressed && isImpure) throw EvalError({ .msg = hintfmt("derivation cannot be both content-addressed and impure"), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); auto ht = parseHashTypeOpt(outputHashAlgo).value_or(htSHA256); @@ -1418,7 +1419,7 @@ static RegisterPrimOp primop_derivationStrict(RegisterPrimOp::Info { time, any occurrence of this string in an derivation attribute will be replaced with the concrete path in the Nix store of the output ‘out’. */ -static void prim_placeholder(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_placeholder(EvalState & state, const PosIdx pos, Value * * args, Value & v) { v.mkString(hashPlaceholder(state.forceStringNoCtx(*args[0], pos))); } @@ -1441,7 +1442,7 @@ static RegisterPrimOp primop_placeholder({ /* Convert the argument to a path. !!! obsolete? */ -static void prim_toPath(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; Path path = state.coerceToPath(pos, *args[0], context); @@ -1466,13 +1467,13 @@ static RegisterPrimOp primop_toPath({ /nix/store/newhash-oldhash-oldname. In the past, `toPath' had special case behaviour for store paths, but that created weird corner cases. */ -static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v) { if (evalSettings.pureEval) { auto e = EvalError({ .msg = hintfmt("'%s' is not allowed in pure evaluation mode", "builtins.storePath"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -1488,7 +1489,7 @@ static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, V { auto e = EvalError({ .msg = hintfmt("path '%1%' is not in the Nix store", path), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -1518,7 +1519,7 @@ static RegisterPrimOp primop_storePath({ .fun = prim_storePath, }); -static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args, Value & v) { /* We don’t check the path right now, because we don’t want to throw if the path isn’t allowed, but just return false (and we @@ -1550,7 +1551,7 @@ static RegisterPrimOp primop_pathExists({ /* Return the base name of the given string, i.e., everything following the last slash. */ -static void prim_baseNameOf(EvalState & state, const Pos & pos, Value * * args, Value & v) +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)), context); @@ -1570,7 +1571,7 @@ static RegisterPrimOp primop_baseNameOf({ /* Return the directory of the given path, i.e., everything before the last slash. Return either a path or a string depending on the type of the argument. */ -static void prim_dirOf(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto path = state.coerceToString(pos, *args[0], context, false, false); @@ -1590,7 +1591,7 @@ static RegisterPrimOp primop_dirOf({ }); /* Return the contents of a file as a string. */ -static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto path = realisePath(state, pos, *args[0]); auto s = readFile(path); @@ -1622,7 +1623,7 @@ static RegisterPrimOp primop_readFile({ /* Find a file in the Nix search path. Used to implement paths, which are desugared to 'findFile __nixPath "x"'. */ -static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); @@ -1653,7 +1654,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va } catch (InvalidPathError & e) { auto ee = EvalError({ .msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(ee); throw ee; @@ -1675,7 +1676,7 @@ static RegisterPrimOp primop_findFile(RegisterPrimOp::Info { }); /* Return the cryptographic hash of a file in base-16. */ -static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto type = state.forceStringNoCtx(*args[0], pos); std::optional ht = parseHashType(type); @@ -1683,7 +1684,7 @@ static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Va { auto e = Error({ .msg = hintfmt("unknown hash type '%1%'", type), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -1706,7 +1707,7 @@ static RegisterPrimOp primop_hashFile({ }); /* Read a directory (without . or ..) */ -static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto path = realisePath(state, pos, *args[0]); @@ -1755,7 +1756,7 @@ static RegisterPrimOp primop_readDir({ /* Convert the argument (which can be any Nix expression) to an XML representation returned in a string. Not all Nix expressions can be sensibly or completely represented (e.g., functions). */ -static void prim_toXML(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_toXML(EvalState & state, const PosIdx pos, Value * * args, Value & v) { std::ostringstream out; PathSet context; @@ -1863,7 +1864,7 @@ static RegisterPrimOp primop_toXML({ /* Convert the argument (which can be any Nix expression) to a JSON string. Not all Nix expressions can be sensibly or completely represented (e.g., functions). */ -static void prim_toJSON(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_toJSON(EvalState & state, const PosIdx pos, Value * * args, Value & v) { std::ostringstream out; PathSet context; @@ -1886,13 +1887,13 @@ static RegisterPrimOp primop_toJSON({ }); /* Parse a JSON string to a value. */ -static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fromJSON(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto s = state.forceStringNoCtx(*args[0], pos); try { parseJSON(state, s, v); } catch (JSONParseError &e) { - e.addTrace(pos, "while decoding a JSON string"); + e.addTrace(state.positions[pos], "while decoding a JSON string"); throw; } } @@ -1914,7 +1915,7 @@ static RegisterPrimOp primop_fromJSON({ /* Store a string in the Nix store as a source file that can be used as an input by derivations. */ -static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; std::string name(state.forceStringNoCtx(*args[0], pos)); @@ -1930,7 +1931,7 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu "in 'toFile': the file named '%1%' must not contain a reference " "to a derivation but contains (%2%)", name, path), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -1938,15 +1939,16 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu refs.insert(state.store->parseStorePath(path)); } - auto storePath = state.store->printStorePath(settings.readOnlyMode + auto storePath = settings.readOnlyMode ? state.store->computeStorePathForText(name, contents, refs) - : state.store->addTextToStore(name, contents, refs, state.repair)); + : state.store->addTextToStore(name, contents, refs, state.repair); /* Note: we don't need to add `context' to the context of the result, since `storePath' itself has references to the paths used in args[1]. */ - v.mkString(storePath, {storePath}); + /* Add the output of this to the allowed paths. */ + state.allowAndSetStorePathString(storePath, v); } static RegisterPrimOp primop_toFile({ @@ -2029,7 +2031,7 @@ static RegisterPrimOp primop_toFile({ static void addPath( EvalState & state, - const Pos & pos, + const PosIdx pos, const std::string & name, Path path, Value * filterFun, @@ -2100,13 +2102,13 @@ static void addPath( } else state.allowAndSetStorePathString(*expectedStorePath, v); } catch (Error & e) { - e.addTrace(pos, "while adding path '%s'", path); + e.addTrace(state.positions[pos], "while adding path '%s'", path); throw; } } -static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; Path path = state.coerceToPath(pos, *args[1], context); @@ -2118,7 +2120,7 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args .msg = hintfmt( "first argument in call to 'filterSource' is not a function but %1%", showType(*args[0])), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -2182,7 +2184,7 @@ static RegisterPrimOp primop_filterSource({ .fun = prim_filterSource, }); -static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); Path path; @@ -2193,23 +2195,23 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value PathSet context; for (auto & attr : *args[0]->attrs) { - auto & n(attr.name); + auto n = state.symbols[attr.name]; if (n == "path") - path = state.coerceToPath(*attr.pos, *attr.value, context); + path = state.coerceToPath(attr.pos, *attr.value, context); else if (attr.name == state.sName) - name = state.forceStringNoCtx(*attr.value, *attr.pos); + name = state.forceStringNoCtx(*attr.value, attr.pos); else if (n == "filter") { state.forceValue(*attr.value, pos); filterFun = attr.value; } else if (n == "recursive") - method = FileIngestionMethod { state.forceBool(*attr.value, *attr.pos) }; + method = FileIngestionMethod { state.forceBool(*attr.value, attr.pos) }; else if (n == "sha256") - expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256); + expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256); else { auto e = EvalError({ - .msg = hintfmt("unsupported argument '%1%' to 'addPath'", attr.name), - .errPos = *attr.pos + .msg = hintfmt("unsupported argument '%1%' to 'addPath'", state.symbols[attr.name]), + .errPos = state.positions[attr.pos] }); state.debugLastTrace(e); throw e; @@ -2219,7 +2221,7 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value { auto e = EvalError({ .msg = hintfmt("'path' required"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -2273,7 +2275,7 @@ static RegisterPrimOp primop_path({ /* Return the names of the attributes in a set as a sorted list of strings. */ -static void prim_attrNames(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_attrNames(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); @@ -2281,7 +2283,7 @@ static void prim_attrNames(EvalState & state, const Pos & pos, Value * * args, V size_t n = 0; for (auto & i : *args[0]->attrs) - (v.listElems()[n++] = state.allocValue())->mkString(i.name); + (v.listElems()[n++] = state.allocValue())->mkString(state.symbols[i.name]); std::sort(v.listElems(), v.listElems() + n, [](Value * v1, Value * v2) { return strcmp(v1->string.s, v2->string.s) < 0; }); @@ -2300,7 +2302,7 @@ static RegisterPrimOp primop_attrNames({ /* Return the values of the attributes in a set as a list, in the same order as attrNames. */ -static void prim_attrValues(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_attrValues(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); @@ -2311,8 +2313,9 @@ static void prim_attrValues(EvalState & state, const Pos & pos, Value * * args, v.listElems()[n++] = (Value *) &i; std::sort(v.listElems(), v.listElems() + n, - [](Value * v1, Value * v2) { - std::string_view s1 = ((Attr *) v1)->name, s2 = ((Attr *) v2)->name; + [&](Value * v1, Value * v2) { + std::string_view s1 = state.symbols[((Attr *) v1)->name], + s2 = state.symbols[((Attr *) v2)->name]; return s1 < s2; }); @@ -2331,7 +2334,7 @@ static RegisterPrimOp primop_attrValues({ }); /* Dynamic version of the `.' operator. */ -void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) +void prim_getAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto attr = state.forceStringNoCtx(*args[0], pos); state.forceAttrs(*args[1], pos); @@ -2343,7 +2346,7 @@ void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) pos ); // !!! add to stack trace? - if (state.countCalls && *i->pos != noPos) state.attrSelects[*i->pos]++; + if (state.countCalls && i->pos) state.attrSelects[i->pos]++; state.forceValue(*i->value, pos); v = *i->value; } @@ -2361,7 +2364,7 @@ static RegisterPrimOp primop_getAttr({ }); /* Return position information of the specified attribute. */ -static void prim_unsafeGetAttrPos(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_unsafeGetAttrPos(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto attr = state.forceStringNoCtx(*args[0], pos); state.forceAttrs(*args[1], pos); @@ -2379,7 +2382,7 @@ static RegisterPrimOp primop_unsafeGetAttrPos(RegisterPrimOp::Info { }); /* Dynamic version of the `?' operator. */ -static void prim_hasAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_hasAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto attr = state.forceStringNoCtx(*args[0], pos); state.forceAttrs(*args[1], pos); @@ -2398,7 +2401,7 @@ static RegisterPrimOp primop_hasAttr({ }); /* Determine whether the argument is a set. */ -static void prim_isAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nAttrs); @@ -2413,7 +2416,7 @@ static RegisterPrimOp primop_isAttrs({ .fun = prim_isAttrs, }); -static void prim_removeAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_removeAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); state.forceList(*args[1], pos); @@ -2461,7 +2464,7 @@ static RegisterPrimOp primop_removeAttrs({ "nameN"; value = valueN;}] is transformed to {name1 = value1; ... nameN = valueN;}. In case of duplicate occurrences of the same name, the first takes precedence. */ -static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); @@ -2480,9 +2483,9 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, pos ); - auto name = state.forceStringNoCtx(*j->value, *j->pos); + auto name = state.forceStringNoCtx(*j->value, j->pos); - Symbol sym = state.symbols.create(name); + auto sym = state.symbols.create(name); if (seen.insert(sym).second) { Bindings::iterator j2 = getAttr( state, @@ -2523,7 +2526,7 @@ static RegisterPrimOp primop_listToAttrs({ .fun = prim_listToAttrs, }); -static void prim_intersectAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_intersectAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); state.forceAttrs(*args[1], pos); @@ -2549,9 +2552,9 @@ static RegisterPrimOp primop_intersectAttrs({ .fun = prim_intersectAttrs, }); -static void prim_catAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_catAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { - Symbol attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos)); + auto attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos)); state.forceList(*args[1], pos); Value * res[args[1]->listSize()]; @@ -2586,7 +2589,7 @@ static RegisterPrimOp primop_catAttrs({ .fun = prim_catAttrs, }); -static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); if (args[0]->isPrimOpApp() || args[0]->isPrimOp()) { @@ -2597,7 +2600,7 @@ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args { auto e = TypeError({ .msg = hintfmt("'functionArgs' requires a function"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -2611,7 +2614,7 @@ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args auto attrs = state.buildBindings(args[0]->lambda.fun->formals->formals.size()); for (auto & i : args[0]->lambda.fun->formals->formals) // !!! should optimise booleans (allocate only once) - attrs.alloc(i.name, ptr(&i.pos)).mkBool(i.def); + attrs.alloc(i.name, i.pos).mkBool(i.def); v.mkAttrs(attrs); } @@ -2633,7 +2636,7 @@ static RegisterPrimOp primop_functionArgs({ }); /* */ -static void prim_mapAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_mapAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[1], pos); @@ -2642,7 +2645,7 @@ static void prim_mapAttrs(EvalState & state, const Pos & pos, Value * * args, Va for (auto & i : *args[1]->attrs) { Value * vName = state.allocValue(); Value * vFun2 = state.allocValue(); - vName->mkString(i.name); + vName->mkString(state.symbols[i.name]); vFun2->mkApp(args[0], vName); attrs.alloc(i.name).mkApp(vFun2, i.value); } @@ -2665,7 +2668,7 @@ static RegisterPrimOp primop_mapAttrs({ .fun = prim_mapAttrs, }); -static void prim_zipAttrsWith(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * args, Value & v) { // we will first count how many values are present for each given key. // we then allocate a single attrset and pre-populate it with lists of @@ -2688,7 +2691,7 @@ static void prim_zipAttrsWith(EvalState & state, const Pos & pos, Value * * args for (auto & attr : *vElem->attrs) attrsSeen[attr.name].first++; } catch (TypeError & e) { - e.addTrace(pos, hintfmt("while invoking '%s'", "zipAttrsWith")); + e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "zipAttrsWith")); state.debugLastTrace(e); throw e; } @@ -2710,7 +2713,7 @@ static void prim_zipAttrsWith(EvalState & state, const Pos & pos, Value * * args for (auto & attr : *v.attrs) { auto name = state.allocValue(); - name->mkString(attr.name); + name->mkString(state.symbols[attr.name]); auto call1 = state.allocValue(); call1->mkApp(args[0], name); auto call2 = state.allocValue(); @@ -2758,7 +2761,7 @@ static RegisterPrimOp primop_zipAttrsWith({ /* Determine whether the argument is a list. */ -static void prim_isList(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isList(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nList); @@ -2773,14 +2776,14 @@ static RegisterPrimOp primop_isList({ .fun = prim_isList, }); -static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Value & v) +static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Value & v) { state.forceList(list, pos); if (n < 0 || (unsigned int) n >= list.listSize()) { auto e = Error({ - .msg = hintfmt("list index %1% is out of boundz", n), - .errPos = pos + .msg = hintfmt("list index %1% is out of bounds", n), + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -2790,7 +2793,7 @@ static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Valu } /* Return the n-1'th element of a list. */ -static void prim_elemAt(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_elemAt(EvalState & state, const PosIdx pos, Value * * args, Value & v) { elemAt(state, pos, *args[0], state.forceInt(*args[1], pos), v); } @@ -2806,7 +2809,7 @@ static RegisterPrimOp primop_elemAt({ }); /* Return the first element of a list. */ -static void prim_head(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_head(EvalState & state, const PosIdx pos, Value * * args, Value & v) { elemAt(state, pos, *args[0], 0, v); } @@ -2825,14 +2828,14 @@ static RegisterPrimOp primop_head({ /* Return a list consisting of everything but the first element of a list. Warning: this function takes O(n) time, so you probably don't want to use it! */ -static void prim_tail(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_tail(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); if (args[0]->listSize() == 0) { auto e = Error({ .msg = hintfmt("'tail' called on an empty list"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -2860,7 +2863,7 @@ static RegisterPrimOp primop_tail({ }); /* Apply a function to every element of a list. */ -static void prim_map(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_map(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[1], pos); @@ -2890,7 +2893,7 @@ static RegisterPrimOp primop_map({ /* Filter a list using a predicate; that is, return a list containing every element from the list for which the predicate function returns true. */ -static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_filter(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); @@ -2928,7 +2931,7 @@ static RegisterPrimOp primop_filter({ }); /* Return true if a list contains a given element. */ -static void prim_elem(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_elem(EvalState & state, const PosIdx pos, Value * * args, Value & v) { bool res = false; state.forceList(*args[1], pos); @@ -2951,7 +2954,7 @@ static RegisterPrimOp primop_elem({ }); /* Concatenate a list of lists. */ -static void prim_concatLists(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_concatLists(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); state.concatLists(v, args[0]->listSize(), args[0]->listElems(), pos); @@ -2967,7 +2970,7 @@ static RegisterPrimOp primop_concatLists({ }); /* Return the length of a list. This is an O(1) time operation. */ -static void prim_length(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_length(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); v.mkInt(args[0]->listSize()); @@ -2984,7 +2987,7 @@ static RegisterPrimOp primop_length({ /* Reduce a list by applying a binary operator, from left to right. The operator is applied strictly. */ -static void prim_foldlStrict(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_foldlStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[2], pos); @@ -3017,7 +3020,7 @@ static RegisterPrimOp primop_foldlStrict({ .fun = prim_foldlStrict, }); -static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * args, Value & v) +static void anyOrAll(bool any, EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); @@ -3036,7 +3039,7 @@ static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * arg } -static void prim_any(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_any(EvalState & state, const PosIdx pos, Value * * args, Value & v) { anyOrAll(true, state, pos, args, v); } @@ -3051,7 +3054,7 @@ static RegisterPrimOp primop_any({ .fun = prim_any, }); -static void prim_all(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_all(EvalState & state, const PosIdx pos, Value * * args, Value & v) { anyOrAll(false, state, pos, args, v); } @@ -3066,7 +3069,7 @@ static RegisterPrimOp primop_all({ .fun = prim_all, }); -static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto len = state.forceInt(*args[1], pos); @@ -3074,7 +3077,7 @@ static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Val { auto e = EvalError({ .msg = hintfmt("cannot create list of size %1%", len), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -3105,10 +3108,10 @@ static RegisterPrimOp primop_genList({ .fun = prim_genList, }); -static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Value & v); +static void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, Value & v); -static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); @@ -3159,7 +3162,7 @@ static RegisterPrimOp primop_sort({ .fun = prim_sort, }); -static void prim_partition(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_partition(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); @@ -3219,7 +3222,7 @@ static RegisterPrimOp primop_partition({ .fun = prim_partition, }); -static void prim_groupBy(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_groupBy(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); @@ -3230,7 +3233,7 @@ static void prim_groupBy(EvalState & state, const Pos & pos, Value * * args, Val Value res; state.callFunction(*args[0], *vElem, res, pos); auto name = state.forceStringNoCtx(res, pos); - Symbol sym = state.symbols.create(name); + auto sym = state.symbols.create(name); auto vector = attrs.try_emplace(sym, ValueVector()).first; vector->second.push_back(vElem); } @@ -3271,7 +3274,7 @@ static RegisterPrimOp primop_groupBy({ .fun = prim_groupBy, }); -static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); @@ -3286,7 +3289,7 @@ static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, V try { state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos))); } catch (TypeError &e) { - e.addTrace(pos, hintfmt("while invoking '%s'", "concatMap")); + e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "concatMap")); state.debugLastTrace(e); throw e; } @@ -3319,7 +3322,7 @@ static RegisterPrimOp primop_concatMap({ *************************************************************/ -static void prim_add(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_add(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); @@ -3338,7 +3341,7 @@ static RegisterPrimOp primop_add({ .fun = prim_add, }); -static void prim_sub(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_sub(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); @@ -3357,7 +3360,7 @@ static RegisterPrimOp primop_sub({ .fun = prim_sub, }); -static void prim_mul(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_mul(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); @@ -3376,7 +3379,7 @@ static RegisterPrimOp primop_mul({ .fun = prim_mul, }); -static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); @@ -3386,7 +3389,7 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & { auto e = EvalError({ .msg = hintfmt("division by zero"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -3402,7 +3405,7 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & { auto e = EvalError({ .msg = hintfmt("overflow in integer division"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -3421,7 +3424,7 @@ static RegisterPrimOp primop_div({ .fun = prim_div, }); -static void prim_bitAnd(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_bitAnd(EvalState & state, const PosIdx pos, Value * * args, Value & v) { v.mkInt(state.forceInt(*args[0], pos) & state.forceInt(*args[1], pos)); } @@ -3435,7 +3438,7 @@ static RegisterPrimOp primop_bitAnd({ .fun = prim_bitAnd, }); -static void prim_bitOr(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_bitOr(EvalState & state, const PosIdx pos, Value * * args, Value & v) { v.mkInt(state.forceInt(*args[0], pos) | state.forceInt(*args[1], pos)); } @@ -3449,7 +3452,7 @@ static RegisterPrimOp primop_bitOr({ .fun = prim_bitOr, }); -static void prim_bitXor(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_bitXor(EvalState & state, const PosIdx pos, Value * * args, Value & v) { v.mkInt(state.forceInt(*args[0], pos) ^ state.forceInt(*args[1], pos)); } @@ -3463,7 +3466,7 @@ static RegisterPrimOp primop_bitXor({ .fun = prim_bitXor, }); -static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); @@ -3491,7 +3494,7 @@ static RegisterPrimOp primop_lessThan({ /* Convert the argument to a string. Paths are *not* copied to the store, so `toString /foo/bar' yields `"/foo/bar"', not `"/nix/store/whatever..."'. */ -static void prim_toString(EvalState & state, const Pos & pos, Value * * args, Value & v) +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); @@ -3526,7 +3529,7 @@ static RegisterPrimOp primop_toString({ at character position `min(start, stringLength str)' inclusive and ending at `min(start + len, stringLength str)'. `start' must be non-negative. */ -static void prim_substring(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_substring(EvalState & state, const PosIdx pos, Value * * args, Value & v) { int start = state.forceInt(*args[0], pos); int len = state.forceInt(*args[1], pos); @@ -3537,7 +3540,7 @@ static void prim_substring(EvalState & state, const Pos & pos, Value * * args, V { auto e = EvalError({ .msg = hintfmt("negative start position in 'substring'"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -3566,7 +3569,7 @@ static RegisterPrimOp primop_substring({ .fun = prim_substring, }); -static void prim_stringLength(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_stringLength(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto s = state.coerceToString(pos, *args[0], context); @@ -3584,7 +3587,7 @@ static RegisterPrimOp primop_stringLength({ }); /* Return the cryptographic hash of a string in base-16. */ -static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto type = state.forceStringNoCtx(*args[0], pos); std::optional ht = parseHashType(type); @@ -3592,7 +3595,7 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, { auto e = Error({ .msg = hintfmt("unknown hash type '%1%'", type), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -3636,7 +3639,7 @@ std::shared_ptr makeRegexCache() return std::make_shared(); } -void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v) +void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto re = state.forceStringNoCtx(*args[0], pos); @@ -3668,14 +3671,14 @@ void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v) // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ auto e = EvalError({ .msg = hintfmt("memory limit exceeded by regular expression '%s'", re), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; } else { auto e = EvalError({ .msg = hintfmt("invalid regular expression '%s'", re), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -3721,7 +3724,7 @@ static RegisterPrimOp primop_match({ /* Split a string with a regular expression, and return a list of the non-matching parts interleaved by the lists of the matching groups. */ -void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v) +void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto re = state.forceStringNoCtx(*args[0], pos); @@ -3777,14 +3780,14 @@ void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v) // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ auto e = EvalError({ .msg = hintfmt("memory limit exceeded by regular expression '%s'", re), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; } else { auto e = EvalError({ .msg = hintfmt("invalid regular expression '%s'", re), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -3829,7 +3832,7 @@ static RegisterPrimOp primop_split({ .fun = prim_split, }); -static void prim_concatStringsSep(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_concatStringsSep(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; @@ -3859,7 +3862,7 @@ static RegisterPrimOp primop_concatStringsSep({ .fun = prim_concatStringsSep, }); -static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); state.forceList(*args[1], pos); @@ -3867,7 +3870,7 @@ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * ar { auto e = EvalError({ .msg = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -3943,7 +3946,7 @@ static RegisterPrimOp primop_replaceStrings({ *************************************************************/ -static void prim_parseDrvName(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_parseDrvName(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto name = state.forceStringNoCtx(*args[0], pos); DrvName parsed(name); @@ -3967,7 +3970,7 @@ static RegisterPrimOp primop_parseDrvName({ .fun = prim_parseDrvName, }); -static void prim_compareVersions(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_compareVersions(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto version1 = state.forceStringNoCtx(*args[0], pos); auto version2 = state.forceStringNoCtx(*args[1], pos); @@ -3987,7 +3990,7 @@ static RegisterPrimOp primop_compareVersions({ .fun = prim_compareVersions, }); -static void prim_splitVersion(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_splitVersion(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto version = state.forceStringNoCtx(*args[0], pos); auto iter = version.cbegin(); @@ -4108,7 +4111,7 @@ void EvalState::createBaseEnv() addPrimOp({ .fun = primOp.fun, .arity = std::max(primOp.args.size(), primOp.arity), - .name = symbols.create(primOp.name), + .name = primOp.name, .args = primOp.args, .doc = primOp.doc, }); @@ -4116,7 +4119,7 @@ void EvalState::createBaseEnv() /* Add a wrapper around the derivation primop that computes the `drvPath' and `outPath' attributes lazily. */ - sDerivationNix = symbols.create("//builtin/derivation.nix"); + sDerivationNix = symbols.create(derivationNixPath); auto vDerivation = allocValue(); addConstant("derivation", vDerivation); @@ -4133,7 +4136,7 @@ void EvalState::createBaseEnv() // the parser needs two NUL bytes as terminators; one of them // is implied by being a C string. "\0"; - eval(parse(code, sizeof(code), foFile, sDerivationNix, "/", staticBaseEnv), *vDerivation); + eval(parse(code, sizeof(code), foFile, derivationNixPath, "/", staticBaseEnv), *vDerivation); } diff --git a/src/libexpr/primops.hh b/src/libexpr/primops.hh index 905bd0366..1cfb4356b 100644 --- a/src/libexpr/primops.hh +++ b/src/libexpr/primops.hh @@ -38,9 +38,9 @@ struct RegisterPrimOp them. */ /* Load a ValueInitializer from a DSO and return whatever it initializes */ -void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v); +void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Value & v); /* Execute a program and parse its output */ -void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v); +void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v); } diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc index cc74c7f58..979136984 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -5,7 +5,7 @@ namespace nix { -static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto s = state.coerceToString(pos, *args[0], context); @@ -15,7 +15,7 @@ static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos, static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext); -static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; state.forceString(*args[0], context, pos); @@ -31,7 +31,7 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext); source-only deployment). This primop marks the string context so that builtins.derivation adds the path to drv.inputSrcs rather than drv.inputDrvs. */ -static void prim_unsafeDiscardOutputDependency(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto s = state.coerceToString(pos, *args[0], context); @@ -65,7 +65,7 @@ static RegisterPrimOp primop_unsafeDiscardOutputDependency("__unsafeDiscardOutpu Note that for a given path any combination of the above attributes may be present. */ -static void prim_getContext(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) { struct ContextInfo { bool path = false; @@ -134,7 +134,7 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext); See the commentary above unsafeGetContext for details of the context representation. */ -static void prim_appendContext(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto orig = state.forceString(*args[0], context, pos); @@ -144,45 +144,46 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg auto sPath = state.symbols.create("path"); auto sAllOutputs = state.symbols.create("allOutputs"); for (auto & i : *args[1]->attrs) { - if (!state.store->isStorePath(i.name)) + const auto & name = state.symbols[i.name]; + if (!state.store->isStorePath(name)) throw EvalError({ - .msg = hintfmt("Context key '%s' is not a store path", i.name), - .errPos = *i.pos + .msg = hintfmt("Context key '%s' is not a store path", name), + .errPos = state.positions[i.pos] }); if (!settings.readOnlyMode) - state.store->ensurePath(state.store->parseStorePath(i.name)); - state.forceAttrs(*i.value, *i.pos); + state.store->ensurePath(state.store->parseStorePath(name)); + state.forceAttrs(*i.value, i.pos); auto iter = i.value->attrs->find(sPath); if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos)) - context.insert(i.name); + if (state.forceBool(*iter->value, iter->pos)) + context.emplace(name); } iter = i.value->attrs->find(sAllOutputs); if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos)) { - if (!isDerivation(i.name)) { + if (state.forceBool(*iter->value, iter->pos)) { + if (!isDerivation(name)) { throw EvalError({ - .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name), - .errPos = *i.pos + .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", name), + .errPos = state.positions[i.pos] }); } - context.insert("=" + std::string(i.name)); + context.insert(concatStrings("=", name)); } } iter = i.value->attrs->find(state.sOutputs); if (iter != i.value->attrs->end()) { - state.forceList(*iter->value, *iter->pos); - if (iter->value->listSize() && !isDerivation(i.name)) { + state.forceList(*iter->value, iter->pos); + if (iter->value->listSize() && !isDerivation(name)) { throw EvalError({ - .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name), - .errPos = *i.pos + .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", name), + .errPos = state.positions[i.pos] }); } for (auto elem : iter->value->listItems()) { - auto name = state.forceStringNoCtx(*elem, *iter->pos); - context.insert(concatStrings("!", name, "!", i.name)); + auto outputName = state.forceStringNoCtx(*elem, iter->pos); + context.insert(concatStrings("!", outputName, "!", name)); } } } diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index 821eba698..662c9652e 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -5,7 +5,7 @@ namespace nix { -static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); @@ -15,40 +15,42 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args std::optional toPath; for (auto & attr : *args[0]->attrs) { - if (attr.name == "fromPath") { + const auto & attrName = state.symbols[attr.name]; + + if (attrName == "fromPath") { PathSet context; - fromPath = state.coerceToStorePath(*attr.pos, *attr.value, context); + fromPath = state.coerceToStorePath(attr.pos, *attr.value, context); } - else if (attr.name == "toPath") { - state.forceValue(*attr.value, *attr.pos); + else if (attrName == "toPath") { + state.forceValue(*attr.value, attr.pos); toCA = true; if (attr.value->type() != nString || attr.value->string.s != std::string("")) { PathSet context; - toPath = state.coerceToStorePath(*attr.pos, *attr.value, context); + toPath = state.coerceToStorePath(attr.pos, *attr.value, context); } } - else if (attr.name == "fromStore") - fromStoreUrl = state.forceStringNoCtx(*attr.value, *attr.pos); + else if (attrName == "fromStore") + fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos); else throw Error({ - .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attr.name), - .errPos = pos + .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName), + .errPos = state.positions[pos] }); } if (!fromPath) throw Error({ .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"), - .errPos = pos + .errPos = state.positions[pos] }); if (!fromStoreUrl) throw Error({ .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"), - .errPos = pos + .errPos = state.positions[pos] }); auto parsedURL = parseURL(*fromStoreUrl); @@ -58,13 +60,13 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args !(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file")) throw Error({ .msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"), - .errPos = pos + .errPos = state.positions[pos] }); if (!parsedURL.query.empty()) throw Error({ .msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl), - .errPos = pos + .errPos = state.positions[pos] }); auto fromStore = openStore(parsedURL.to_string()); @@ -80,7 +82,7 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args state.store->printStorePath(*fromPath), state.store->printStorePath(i->second), state.store->printStorePath(*toPath)), - .errPos = pos + .errPos = state.positions[pos] }); if (!toPath) throw Error({ @@ -89,7 +91,7 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args "please set this in the 'toPath' attribute passed to 'fetchClosure'", state.store->printStorePath(*fromPath), state.store->printStorePath(i->second)), - .errPos = pos + .errPos = state.positions[pos] }); } } else { @@ -105,7 +107,7 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args throw Error({ .msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't", state.store->printStorePath(*toPath)), - .errPos = pos + .errPos = state.positions[pos] }); } diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc index b7f715859..249c0934e 100644 --- a/src/libexpr/primops/fetchMercurial.cc +++ b/src/libexpr/primops/fetchMercurial.cc @@ -7,7 +7,7 @@ namespace nix { -static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * args, Value & v) { std::string url; std::optional rev; @@ -22,31 +22,31 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar state.forceAttrs(*args[0], pos); for (auto & attr : *args[0]->attrs) { - std::string_view n(attr.name); + std::string_view n(state.symbols[attr.name]); if (n == "url") - url = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned(); + url = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned(); else if (n == "rev") { // Ugly: unlike fetchGit, here the "rev" attribute can // be both a revision or a branch/tag name. - auto value = state.forceStringNoCtx(*attr.value, *attr.pos); + auto value = state.forceStringNoCtx(*attr.value, attr.pos); if (std::regex_match(value.begin(), value.end(), revRegex)) rev = Hash::parseAny(value, htSHA1); else ref = value; } else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos); + name = state.forceStringNoCtx(*attr.value, attr.pos); else throw EvalError({ - .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name), - .errPos = *attr.pos + .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]), + .errPos = state.positions[attr.pos] }); } if (url.empty()) throw EvalError({ .msg = hintfmt("'url' argument required"), - .errPos = pos + .errPos = state.positions[pos] }); } else diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index c7b73b83d..55fe7da24 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -90,7 +90,7 @@ struct FetchTreeParams { static void fetchTree( EvalState & state, - const Pos & pos, + const PosIdx pos, Value * * args, Value & v, std::optional type, @@ -111,18 +111,17 @@ static void fetchTree( { auto e = EvalError({ .msg = hintfmt("unexpected attribute 'type'"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; - } - type = state.forceStringNoCtx(*aType->value, *aType->pos); + type = state.forceStringNoCtx(*aType->value, aType->pos); } else if (!type) { auto e = EvalError({ .msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -132,24 +131,24 @@ static void fetchTree( for (auto & attr : *args[0]->attrs) { if (attr.name == state.sType) continue; - state.forceValue(*attr.value, *attr.pos); + 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(); - attrs.emplace(attr.name, - attr.name == "url" + 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" ? fixURIForGit(s, state) : fixURI(s, state) : s); } else if (attr.value->type() == nBool) - attrs.emplace(attr.name, Explicit{attr.value->boolean}); + attrs.emplace(state.symbols[attr.name], Explicit{attr.value->boolean}); else if (attr.value->type() == nInt) - attrs.emplace(attr.name, uint64_t(attr.value->integer)); + attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer)); else { auto e = TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected", - attr.name, showType(*attr.value)); + state.symbols[attr.name], showType(*attr.value)); state.debugLastTrace(e); throw e; } @@ -160,7 +159,7 @@ static void fetchTree( { auto e = EvalError({ .msg = hintfmt("attribute 'name' isn’t supported in call to 'fetchTree'"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -185,7 +184,7 @@ static void fetchTree( if (evalSettings.pureEval && !input.isLocked()) { - auto e = EvalError("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", pos); + auto e = EvalError("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]); state.debugLastTrace(e); throw e; } @@ -197,7 +196,7 @@ static void fetchTree( emitTreeAttrs(state, tree, input2, v, params.emptyRevFallback, false); } -static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v) { settings.requireExperimentalFeature(Xp::Flakes); fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false }); @@ -206,7 +205,7 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V // FIXME: document static RegisterPrimOp primop_fetchTree("fetchTree", 1, prim_fetchTree); -static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, +static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v, const std::string & who, bool unpack, std::string name) { std::optional url; @@ -219,18 +218,18 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, state.forceAttrs(*args[0], pos); for (auto & attr : *args[0]->attrs) { - std::string n(attr.name); + std::string_view n(state.symbols[attr.name]); if (n == "url") - url = state.forceStringNoCtx(*attr.value, *attr.pos); + url = state.forceStringNoCtx(*attr.value, attr.pos); else if (n == "sha256") - expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256); + expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256); else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos); + name = state.forceStringNoCtx(*attr.value, attr.pos); else { auto e = EvalError({ - .msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who), - .errPos = *attr.pos + .msg = hintfmt("unsupported argument '%s' to '%s'", n, who), + .errPos = state.positions[attr.pos] }); state.debugLastTrace(e); throw e; @@ -241,7 +240,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, { auto e = EvalError({ .msg = hintfmt("'url' argument required"), - .errPos = pos + .errPos = state.positions[pos] }); state.debugLastTrace(e); throw e; @@ -299,7 +298,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, state.allowAndSetStorePathString(storePath, v); } -static void prim_fetchurl(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fetchurl(EvalState & state, const PosIdx pos, Value * * args, Value & v) { fetch(state, pos, args, v, "fetchurl", false, ""); } @@ -315,7 +314,7 @@ static RegisterPrimOp primop_fetchurl({ .fun = prim_fetchurl, }); -static void prim_fetchTarball(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fetchTarball(EvalState & state, const PosIdx pos, Value * * args, Value & v) { fetch(state, pos, args, v, "fetchTarball", true, "source"); } @@ -366,7 +365,7 @@ static RegisterPrimOp primop_fetchTarball({ .fun = prim_fetchTarball, }); -static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fetchGit(EvalState & state, const PosIdx pos, Value * * args, Value & v) { fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true }); } diff --git a/src/libexpr/primops/fromTOML.cc b/src/libexpr/primops/fromTOML.cc index dd4280030..9753e2ac9 100644 --- a/src/libexpr/primops/fromTOML.cc +++ b/src/libexpr/primops/fromTOML.cc @@ -5,7 +5,7 @@ namespace nix { -static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & val) +static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val) { auto toml = state.forceStringNoCtx(*args[0], pos); @@ -73,7 +73,7 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va } catch (std::exception & e) { // TODO: toml::syntax_error throw EvalError({ .msg = hintfmt("while parsing a TOML string: %s", e.what()), - .errPos = pos + .errPos = state.positions[pos] }); } } diff --git a/src/libexpr/symbol-table.hh b/src/libexpr/symbol-table.hh index 48d20c29d..288c15602 100644 --- a/src/libexpr/symbol-table.hh +++ b/src/libexpr/symbol-table.hh @@ -5,44 +5,32 @@ #include #include "types.hh" +#include "chunked-vector.hh" namespace nix { /* Symbol table used by the parser and evaluator to represent and look up identifiers and attributes efficiently. SymbolTable::create() converts a string into a symbol. Symbols have the property that - they can be compared efficiently (using a pointer equality test), + they can be compared efficiently (using an equality test), because the symbol table stores only one copy of each string. */ -class Symbol +/* This class mainly exists to give us an operator<< for ostreams. We could also + return plain strings from SymbolTable, but then we'd have to wrap every + instance of a symbol that is fmt()ed, which is inconvenient and error-prone. */ +class SymbolStr { -private: - const std::string * s; // pointer into SymbolTable - Symbol(const std::string * s) : s(s) { }; friend class SymbolTable; +private: + const std::string * s; + + explicit SymbolStr(const std::string & symbol): s(&symbol) {} + public: - Symbol() : s(0) { }; - - bool operator == (const Symbol & s2) const - { - return s == s2.s; - } - - // FIXME: remove bool operator == (std::string_view s2) const { - return s->compare(s2) == 0; - } - - bool operator != (const Symbol & s2) const - { - return s != s2.s; - } - - bool operator < (const Symbol & s2) const - { - return s < s2.s; + return *s == s2; } operator const std::string & () const @@ -55,51 +43,78 @@ public: return *s; } - bool set() const - { - return s; - } + friend std::ostream & operator <<(std::ostream & os, const SymbolStr & symbol); +}; - bool empty() const - { - return s->empty(); - } +class Symbol +{ + friend class SymbolTable; - friend std::ostream & operator << (std::ostream & str, const Symbol & sym); +private: + uint32_t id; + + explicit Symbol(uint32_t id): id(id) {} + +public: + Symbol() : id(0) {} + + explicit operator bool() const { return id > 0; } + + bool operator<(const Symbol other) const { return id < other.id; } + bool operator==(const Symbol other) const { return id == other.id; } + bool operator!=(const Symbol other) const { return id != other.id; } }; class SymbolTable { private: - std::unordered_map symbols; - std::list store; + std::unordered_map> symbols; + ChunkedVector store{16}; public: + Symbol create(std::string_view s) { // Most symbols are looked up more than once, so we trade off insertion performance // for lookup performance. // TODO: could probably be done more efficiently with transparent Hash and Equals // on the original implementation using unordered_set + // FIXME: make this thread-safe. auto it = symbols.find(s); - if (it != symbols.end()) return it->second; + if (it != symbols.end()) return Symbol(it->second.second + 1); - auto & rawSym = store.emplace_back(s); - return symbols.emplace(rawSym, Symbol(&rawSym)).first->second; + const auto & [rawSym, idx] = store.add(std::string(s)); + symbols.emplace(rawSym, std::make_pair(&rawSym, idx)); + return Symbol(idx + 1); + } + + std::vector resolve(const std::vector & symbols) const + { + std::vector result; + result.reserve(symbols.size()); + for (auto sym : symbols) + result.push_back((*this)[sym]); + return result; + } + + SymbolStr operator[](Symbol s) const + { + if (s.id == 0 || s.id > store.size()) + abort(); + return SymbolStr(store[s.id - 1]); } size_t size() const { - return symbols.size(); + return store.size(); } size_t totalSize() const; template - void dump(T callback) + void dump(T callback) const { - for (auto & s : store) - callback(s); + store.forEach(callback); } }; diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc index 2f34feb1f..34f3a34c8 100644 --- a/src/libexpr/value-to-json.cc +++ b/src/libexpr/value-to-json.cc @@ -10,7 +10,7 @@ namespace nix { void printValueAsJSON(EvalState & state, bool strict, - Value & v, const Pos & pos, JSONPlaceholder & out, PathSet & context) + Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context) { checkInterrupt(); @@ -50,14 +50,14 @@ void printValueAsJSON(EvalState & state, bool strict, auto obj(out.object()); StringSet names; for (auto & j : *v.attrs) - names.insert(j.name); + names.emplace(state.symbols[j.name]); for (auto & j : names) { Attr & a(*v.attrs->find(state.symbols.create(j))); auto placeholder(obj.placeholder(j)); - printValueAsJSON(state, strict, *a.value, *a.pos, placeholder, context); + printValueAsJSON(state, strict, *a.value, a.pos, placeholder, context); } } else - printValueAsJSON(state, strict, *i->value, *i->pos, out, context); + printValueAsJSON(state, strict, *i->value, i->pos, out, context); break; } @@ -82,16 +82,16 @@ void printValueAsJSON(EvalState & state, bool strict, case nFunction: auto e = TypeError({ .msg = hintfmt("cannot convert %1% to JSON", showType(v)), - .errPos = v.determinePos(pos) + .errPos = state.positions[v.determinePos(pos)] }); - e.addTrace(pos, hintfmt("message for the trace")); + e.addTrace(state.positions[pos], hintfmt("message for the trace")); state.debugLastTrace(e); throw e; } } void printValueAsJSON(EvalState & state, bool strict, - Value & v, const Pos & pos, std::ostream & str, PathSet & context) + Value & v, const PosIdx pos, std::ostream & str, PathSet & context) { JSONPlaceholder out(str); printValueAsJSON(state, strict, v, pos, out, context); diff --git a/src/libexpr/value-to-json.hh b/src/libexpr/value-to-json.hh index c2f797b29..c020a817a 100644 --- a/src/libexpr/value-to-json.hh +++ b/src/libexpr/value-to-json.hh @@ -11,9 +11,9 @@ namespace nix { class JSONPlaceholder; void printValueAsJSON(EvalState & state, bool strict, - Value & v, const Pos & pos, JSONPlaceholder & out, PathSet & context); + Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context); void printValueAsJSON(EvalState & state, bool strict, - Value & v, const Pos & pos, std::ostream & str, PathSet & context); + Value & v, const PosIdx pos, std::ostream & str, PathSet & context); } diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc index afeaf5694..7c3bf9492 100644 --- a/src/libexpr/value-to-xml.cc +++ b/src/libexpr/value-to-xml.cc @@ -19,10 +19,10 @@ static XMLAttrs singletonAttrs(const std::string & name, const std::string & val static void printValueAsXML(EvalState & state, bool strict, bool location, Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen, - const Pos & pos); + const PosIdx pos); -static void posToXML(XMLAttrs & xmlAttrs, const Pos & pos) +static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos) { xmlAttrs["path"] = pos.file; xmlAttrs["line"] = (format("%1%") % pos.line).str(); @@ -36,25 +36,25 @@ static void showAttrs(EvalState & state, bool strict, bool location, StringSet names; for (auto & i : attrs) - names.insert(i.name); + names.emplace(state.symbols[i.name]); for (auto & i : names) { Attr & a(*attrs.find(state.symbols.create(i))); XMLAttrs xmlAttrs; xmlAttrs["name"] = i; - if (location && a.pos != ptr(&noPos)) posToXML(xmlAttrs, *a.pos); + if (location && a.pos) posToXML(state, xmlAttrs, state.positions[a.pos]); XMLOpenElement _(doc, "attr", xmlAttrs); printValueAsXML(state, strict, location, - *a.value, doc, context, drvsSeen, *a.pos); + *a.value, doc, context, drvsSeen, a.pos); } } static void printValueAsXML(EvalState & state, bool strict, bool location, Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen, - const Pos & pos) + const PosIdx pos) { checkInterrupt(); @@ -93,14 +93,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, Path drvPath; a = v.attrs->find(state.sDrvPath); if (a != v.attrs->end()) { - if (strict) state.forceValue(*a->value, *a->pos); + if (strict) state.forceValue(*a->value, a->pos); if (a->value->type() == nString) xmlAttrs["drvPath"] = drvPath = a->value->string.s; } a = v.attrs->find(state.sOutPath); if (a != v.attrs->end()) { - if (strict) state.forceValue(*a->value, *a->pos); + if (strict) state.forceValue(*a->value, a->pos); if (a->value->type() == nString) xmlAttrs["outPath"] = a->value->string.s; } @@ -134,18 +134,18 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, break; } XMLAttrs xmlAttrs; - if (location) posToXML(xmlAttrs, v.lambda.fun->pos); + if (location) posToXML(state, xmlAttrs, state.positions[v.lambda.fun->pos]); XMLOpenElement _(doc, "function", xmlAttrs); if (v.lambda.fun->hasFormals()) { XMLAttrs attrs; - if (!v.lambda.fun->arg.empty()) attrs["name"] = v.lambda.fun->arg; + if (v.lambda.fun->arg) attrs["name"] = state.symbols[v.lambda.fun->arg]; if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1"; XMLOpenElement _(doc, "attrspat", attrs); - for (auto & i : v.lambda.fun->formals->lexicographicOrder()) - doc.writeEmptyElement("attr", singletonAttrs("name", i.name)); + for (auto & i : v.lambda.fun->formals->lexicographicOrder(state.symbols)) + doc.writeEmptyElement("attr", singletonAttrs("name", state.symbols[i.name])); } else - doc.writeEmptyElement("varpat", singletonAttrs("name", v.lambda.fun->arg)); + doc.writeEmptyElement("varpat", singletonAttrs("name", state.symbols[v.lambda.fun->arg])); break; } @@ -166,14 +166,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, void ExternalValueBase::printValueAsXML(EvalState & state, bool strict, bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen, - const Pos & pos) const + const PosIdx pos) const { doc.writeEmptyElement("unevaluated"); } void printValueAsXML(EvalState & state, bool strict, bool location, - Value & v, std::ostream & out, PathSet & context, const Pos & pos) + Value & v, std::ostream & out, PathSet & context, const PosIdx pos) { XMLWriter doc(true, out); XMLOpenElement root(doc, "expr"); diff --git a/src/libexpr/value-to-xml.hh b/src/libexpr/value-to-xml.hh index cc778a2cb..506f32b6b 100644 --- a/src/libexpr/value-to-xml.hh +++ b/src/libexpr/value-to-xml.hh @@ -9,6 +9,6 @@ namespace nix { void printValueAsXML(EvalState & state, bool strict, bool location, - Value & v, std::ostream & out, PathSet & context, const Pos & pos); + Value & v, std::ostream & out, PathSet & context, const PosIdx pos); } diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index 3d07c3198..58a8a56a0 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -56,6 +56,7 @@ struct Expr; struct ExprLambda; struct PrimOp; class Symbol; +class PosIdx; struct Pos; class StorePath; class Store; @@ -103,7 +104,7 @@ class ExternalValueBase /* Print the value as XML. Defaults to unevaluated */ virtual void printValueAsXML(EvalState & state, bool strict, bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen, - const Pos & pos) const; + const PosIdx pos) const; virtual ~ExternalValueBase() { @@ -120,11 +121,11 @@ private: friend std::string showType(const Value & v); - void print(std::ostream & str, std::set * seen) const; + void print(const SymbolTable & symbols, std::ostream & str, std::set * seen) const; public: - void print(std::ostream & str, bool showRepeated = false) const; + void print(const SymbolTable & symbols, std::ostream & str, bool showRepeated = false) const; // Functions needed to distinguish the type // These should be removed eventually, by putting the functionality that's @@ -250,11 +251,6 @@ public: void mkStringMove(const char * s, const PathSet & context); - inline void mkString(const Symbol & s) - { - mkString(((const std::string &) s).c_str()); - } - inline void mkPath(const char * s) { clearValue(); @@ -368,7 +364,7 @@ public: return internalType == tList1 ? 1 : internalType == tList2 ? 2 : bigList.size; } - Pos determinePos(const Pos & pos) const; + PosIdx determinePos(const PosIdx pos) const; /* Check whether forcing this value requires a trivial amount of computation. In particular, function applications are diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 34b1342a0..af40990e5 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -21,9 +21,18 @@ namespace nix::fetchers { // old version of git, which will ignore unrecognized `-c` options. const std::string gitInitialBranch = "__nix_dummy_branch"; +static std::string getGitDir() +{ + auto gitDir = getEnv("GIT_DIR"); + if (!gitDir) { + return ".git"; + } + return *gitDir; +} + static std::string readHead(const Path & path) { - return chomp(runProgram("git", true, { "-C", path, "rev-parse", "--abbrev-ref", "HEAD" })); + return chomp(runProgram("git", true, { "-C", path, "--git-dir", ".git", "rev-parse", "--abbrev-ref", "HEAD" })); } static bool isNotDotGitDirectory(const Path & path) @@ -150,13 +159,14 @@ struct GitInputScheme : InputScheme { auto sourcePath = getSourcePath(input); assert(sourcePath); + auto gitDir = getGitDir(); runProgram("git", true, - { "-C", *sourcePath, "add", "--force", "--intent-to-add", "--", std::string(file) }); + { "-C", *sourcePath, "--git-dir", gitDir, "add", "--force", "--intent-to-add", "--", std::string(file) }); if (commitMsg) runProgram("git", true, - { "-C", *sourcePath, "commit", std::string(file), "-m", *commitMsg }); + { "-C", *sourcePath, "--git-dir", gitDir, "commit", std::string(file), "-m", *commitMsg }); } std::pair getActualUrl(const Input & input) const @@ -175,6 +185,7 @@ struct GitInputScheme : InputScheme std::pair fetch(ref store, const Input & _input) override { Input input(_input); + auto gitDir = getGitDir(); std::string name = input.getName(); @@ -237,7 +248,7 @@ struct GitInputScheme : InputScheme since that is the refrence we want to use later on. */ auto result = runProgram(RunOptions { .program = "git", - .args = { "-C", actualUrl, "--git-dir=.git", "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" }, + .args = { "-C", actualUrl, "--git-dir", gitDir, "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" }, .environment = env, .mergeStderrToStdout = true }); @@ -259,7 +270,7 @@ struct GitInputScheme : InputScheme if (hasHead) { // Using git diff is preferrable over lower-level operations here, // because its conceptually simpler and we only need the exit code anyways. - auto gitDiffOpts = Strings({ "-C", actualUrl, "diff", "HEAD", "--quiet"}); + auto gitDiffOpts = Strings({ "-C", actualUrl, "--git-dir", gitDir, "diff", "HEAD", "--quiet"}); if (!submodules) { // Changes in submodules should only make the tree dirty // when those submodules will be copied as well. @@ -284,7 +295,7 @@ struct GitInputScheme : InputScheme if (fetchSettings.warnDirty) warn("Git tree '%s' is dirty", actualUrl); - auto gitOpts = Strings({ "-C", actualUrl, "ls-files", "-z" }); + auto gitOpts = Strings({ "-C", actualUrl, "--git-dir", gitDir, "ls-files", "-z" }); if (submodules) gitOpts.emplace_back("--recurse-submodules"); @@ -314,7 +325,7 @@ struct GitInputScheme : InputScheme // modified dirty file? input.attrs.insert_or_assign( "lastModified", - hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0); + hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "--git-dir", gitDir, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0); return {std::move(storePath), input}; } @@ -335,7 +346,7 @@ struct GitInputScheme : InputScheme if (!input.getRev()) input.attrs.insert_or_assign("rev", - Hash::parseAny(chomp(runProgram("git", true, { "-C", actualUrl, "rev-parse", *input.getRef() })), htSHA1).gitRev()); + Hash::parseAny(chomp(runProgram("git", true, { "-C", actualUrl, "--git-dir", gitDir, "rev-parse", *input.getRef() })), htSHA1).gitRev()); repoDir = actualUrl; @@ -351,6 +362,7 @@ struct GitInputScheme : InputScheme Path cacheDir = getCacheDir() + "/nix/gitv3/" + hashString(htSHA256, actualUrl).to_string(Base32, false); repoDir = cacheDir; + gitDir = "."; createDirs(dirOf(cacheDir)); PathLocks cacheDirLock({cacheDir + ".lock"}); @@ -427,7 +439,7 @@ struct GitInputScheme : InputScheme // cache dir lock is removed at scope end; we will only use read-only operations on specific revisions in the remainder } - bool isShallow = chomp(runProgram("git", true, { "-C", repoDir, "rev-parse", "--is-shallow-repository" })) == "true"; + bool isShallow = chomp(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "rev-parse", "--is-shallow-repository" })) == "true"; if (isShallow && !shallow) throw Error("'%s' is a shallow Git repository, but a non-shallow repository is needed", actualUrl); diff --git a/src/libfetchers/mercurial.cc b/src/libfetchers/mercurial.cc index 1beb8b944..5c5671681 100644 --- a/src/libfetchers/mercurial.cc +++ b/src/libfetchers/mercurial.cc @@ -36,7 +36,7 @@ static std::string runHg(const Strings & args, const std::optional auto res = runProgram(std::move(opts)); if (!statusOk(res.first)) - throw ExecError(res.first, fmt("hg %1%", statusToString(res.first))); + throw ExecError(res.first, "hg %1%", statusToString(res.first)); return res.second; } @@ -273,7 +273,7 @@ struct MercurialInputScheme : InputScheme runHg({ "recover", "-R", cacheDir }); runHg({ "pull", "-R", cacheDir, "--", actualUrl }); } else { - throw ExecError(e.status, fmt("'hg pull' %s", statusToString(e.status))); + throw ExecError(e.status, "'hg pull' %s", statusToString(e.status)); } } } else { diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 562d1b414..31454e49d 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -60,37 +60,37 @@ void printMissing(ref store, const StorePathSet & willBuild, { if (!willBuild.empty()) { if (willBuild.size() == 1) - printMsg(lvl, fmt("this derivation will be built:")); + printMsg(lvl, "this derivation will be built:"); else - printMsg(lvl, fmt("these %d derivations will be built:", willBuild.size())); + printMsg(lvl, "these %d derivations will be built:", willBuild.size()); auto sorted = store->topoSortPaths(willBuild); reverse(sorted.begin(), sorted.end()); for (auto & i : sorted) - printMsg(lvl, fmt(" %s", store->printStorePath(i))); + printMsg(lvl, " %s", store->printStorePath(i)); } if (!willSubstitute.empty()) { const float downloadSizeMiB = downloadSize / (1024.f * 1024.f); const float narSizeMiB = narSize / (1024.f * 1024.f); if (willSubstitute.size() == 1) { - printMsg(lvl, fmt("this path will be fetched (%.2f MiB download, %.2f MiB unpacked):", + printMsg(lvl, "this path will be fetched (%.2f MiB download, %.2f MiB unpacked):", downloadSizeMiB, - narSizeMiB)); + narSizeMiB); } else { - printMsg(lvl, fmt("these %d paths will be fetched (%.2f MiB download, %.2f MiB unpacked):", + printMsg(lvl, "these %d paths will be fetched (%.2f MiB download, %.2f MiB unpacked):", willSubstitute.size(), downloadSizeMiB, - narSizeMiB)); + narSizeMiB); } for (auto & i : willSubstitute) - printMsg(lvl, fmt(" %s", store->printStorePath(i))); + printMsg(lvl, " %s", store->printStorePath(i)); } if (!unknown.empty()) { - printMsg(lvl, fmt("don't know how to build these paths%s:", - (settings.readOnlyMode ? " (may be caused by read-only store access)" : ""))); + printMsg(lvl, "don't know how to build these paths%s:", + (settings.readOnlyMode ? " (may be caused by read-only store access)" : "")); for (auto & i : unknown) - printMsg(lvl, fmt(" %s", store->printStorePath(i))); + printMsg(lvl, " %s", store->printStorePath(i)); } } diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 53f212c1d..d095a0f02 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -786,8 +786,7 @@ void runPostBuildHook( Store & store, Logger & logger, const StorePath & drvPath, - StorePathSet outputPaths -) + const StorePathSet & outputPaths) { auto hook = settings.postBuildHook; if (hook == "") @@ -906,7 +905,7 @@ void DerivationGoal::buildDone() auto builtOutputs = registerOutputs(); StorePathSet outputPaths; - for (auto & [_, output] : buildResult.builtOutputs) + for (auto & [_, output] : builtOutputs) outputPaths.insert(output.outPath); runPostBuildHook( worker.store, diff --git a/src/libstore/ca-specific-schema.sql b/src/libstore/ca-specific-schema.sql index 64cc97fde..4ca91f585 100644 --- a/src/libstore/ca-specific-schema.sql +++ b/src/libstore/ca-specific-schema.sql @@ -13,12 +13,27 @@ create table if not exists Realisations ( create index if not exists IndexRealisations on Realisations(drvPath, outputName); +-- We can end-up in a weird edge-case where a path depends on itself because +-- it’s an output of a CA derivation, that happens to be the same as one of its +-- dependencies. +-- In that case we have a dependency loop (path -> realisation1 -> realisation2 +-- -> path) that we need to break by removing the dependencies between the +-- realisations +create trigger if not exists DeleteSelfRefsViaRealisations before delete on ValidPaths + begin + delete from RealisationsRefs where realisationReference in ( + select id from Realisations where outputPath = old.id + ); + end; + create table if not exists RealisationsRefs ( referrer integer not null, realisationReference integer, foreign key (referrer) references Realisations(id) on delete cascade, foreign key (realisationReference) references Realisations(id) on delete restrict ); +-- used by deletion trigger +create index if not exists IndexRealisationsRefsRealisationReference on RealisationsRefs(realisationReference); -- used by QueryRealisationReferences create index if not exists IndexRealisationsRefs on RealisationsRefs(referrer); diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc index c46262299..529a41891 100644 --- a/src/libstore/filetransfer.cc +++ b/src/libstore/filetransfer.cc @@ -443,14 +443,13 @@ struct curlFileTransfer : public FileTransfer : httpStatus != 0 ? FileTransferError(err, std::move(response), - fmt("unable to %s '%s': HTTP error %d ('%s')", - request.verb(), request.uri, httpStatus, statusMsg) - + (code == CURLE_OK ? "" : fmt(" (curl error: %s)", curl_easy_strerror(code))) - ) + "unable to %s '%s': HTTP error %d%s", + request.verb(), request.uri, httpStatus, + code == CURLE_OK ? "" : fmt(" (curl error: %s)", curl_easy_strerror(code))) : FileTransferError(err, std::move(response), - fmt("unable to %s '%s': %s (%d)", - request.verb(), request.uri, curl_easy_strerror(code), code)); + "unable to %s '%s': %s (%d)", + request.verb(), request.uri, curl_easy_strerror(code), code); /* If this is a transient error, then maybe retry the download after a while. If we're writing to a @@ -704,7 +703,7 @@ struct curlFileTransfer : public FileTransfer auto s3Res = s3Helper.getObject(bucketName, key); FileTransferResult res; if (!s3Res.data) - throw FileTransferError(NotFound, {}, "S3 object '%s' does not exist", request.uri); + throw FileTransferError(NotFound, "S3 object '%s' does not exist", request.uri); res.data = std::move(*s3Res.data); callback(std::move(res)); #else diff --git a/src/libstore/filetransfer.hh b/src/libstore/filetransfer.hh index 1ad96bc10..40e7cf52c 100644 --- a/src/libstore/filetransfer.hh +++ b/src/libstore/filetransfer.hh @@ -31,7 +31,7 @@ struct FileTransferSettings : Config R"( The timeout (in seconds) for establishing connections in the binary cache substituter. It corresponds to `curl`’s - `--connect-timeout` option. + `--connect-timeout` option. A value of 0 means no limit. )"}; Setting stalledDownloadTimeout{ diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index d77fff963..5cc5c91cc 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -81,7 +81,7 @@ int getSchema(Path schemaPath) void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd) { - const int nixCASchemaVersion = 3; + const int nixCASchemaVersion = 4; int curCASchema = getSchema(schemaPath); if (curCASchema != nixCASchemaVersion) { if (curCASchema > nixCASchemaVersion) { @@ -143,6 +143,21 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd) )"); txn.commit(); } + if (curCASchema < 4) { + SQLiteTxn txn(db); + db.exec(R"( + create trigger if not exists DeleteSelfRefsViaRealisations before delete on ValidPaths + begin + delete from RealisationsRefs where realisationReference in ( + select id from Realisations where outputPath = old.id + ); + end; + -- used by deletion trigger + create index if not exists IndexRealisationsRefsRealisationReference on RealisationsRefs(realisationReference); + )"); + txn.commit(); + } + writeFile(schemaPath, fmt("%d", nixCASchemaVersion)); lockFile(lockFd.get(), ltRead, true); } @@ -482,18 +497,18 @@ void LocalStore::openDB(State & state, bool create) SQLiteStmt stmt; stmt.create(db, "pragma main.journal_mode;"); if (sqlite3_step(stmt) != SQLITE_ROW) - throwSQLiteError(db, "querying journal mode"); + SQLiteError::throw_(db, "querying journal mode"); prevMode = std::string((const char *) sqlite3_column_text(stmt, 0)); } if (prevMode != mode && sqlite3_exec(db, ("pragma main.journal_mode = " + mode + ";").c_str(), 0, 0, 0) != SQLITE_OK) - throwSQLiteError(db, "setting journal mode"); + SQLiteError::throw_(db, "setting journal mode"); /* Increase the auto-checkpoint interval to 40000 pages. This seems enough to ensure that instantiating the NixOS system derivation is done in a single fsync(). */ if (mode == "wal" && sqlite3_exec(db, "pragma wal_autocheckpoint = 40000;", 0, 0, 0) != SQLITE_OK) - throwSQLiteError(db, "setting autocheckpoint interval"); + SQLiteError::throw_(db, "setting autocheckpoint interval"); /* Initialise the database schema, if necessary. */ if (create) { diff --git a/src/libstore/s3.hh b/src/libstore/s3.hh index 3f55c74db..cdb3e5908 100644 --- a/src/libstore/s3.hh +++ b/src/libstore/s3.hh @@ -5,6 +5,7 @@ #include "ref.hh" #include +#include namespace Aws { namespace Client { class ClientConfiguration; } } namespace Aws { namespace S3 { class S3Client; } } diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc index 1d82b4ab1..2090beabd 100644 --- a/src/libstore/sqlite.cc +++ b/src/libstore/sqlite.cc @@ -8,22 +8,32 @@ namespace nix { -[[noreturn]] void throwSQLiteError(sqlite3 * db, const FormatOrString & fs) +SQLiteError::SQLiteError(const char *path, int errNo, int extendedErrNo, hintformat && hf) + : Error(""), path(path), errNo(errNo), extendedErrNo(extendedErrNo) +{ + err.msg = hintfmt("%s: %s (in '%s')", + normaltxt(hf.str()), + sqlite3_errstr(extendedErrNo), + path ? path : "(in-memory)"); +} + +[[noreturn]] void SQLiteError::throw_(sqlite3 * db, hintformat && hf) { int err = sqlite3_errcode(db); int exterr = sqlite3_extended_errcode(db); auto path = sqlite3_db_filename(db, nullptr); - if (!path) path = "(in-memory)"; if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { - throw SQLiteBusy( + auto exp = SQLiteBusy(path, err, exterr, std::move(hf)); + exp.err.msg = hintfmt( err == SQLITE_PROTOCOL - ? fmt("SQLite database '%s' is busy (SQLITE_PROTOCOL)", path) - : fmt("SQLite database '%s' is busy", path)); - } - else - throw SQLiteError("%s: %s (in '%s')", fs.s, sqlite3_errstr(exterr), path); + ? "SQLite database '%s' is busy (SQLITE_PROTOCOL)" + : "SQLite database '%s' is busy", + path ? path : "(in-memory)"); + throw exp; + } else + throw SQLiteError(path, err, exterr, std::move(hf)); } SQLite::SQLite(const Path & path, bool create) @@ -37,7 +47,7 @@ SQLite::SQLite(const Path & path, bool create) throw Error("cannot open SQLite database '%s'", path); if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK) - throwSQLiteError(db, "setting timeout"); + SQLiteError::throw_(db, "setting timeout"); exec("pragma foreign_keys = 1"); } @@ -46,7 +56,7 @@ SQLite::~SQLite() { try { if (db && sqlite3_close(db) != SQLITE_OK) - throwSQLiteError(db, "closing database"); + SQLiteError::throw_(db, "closing database"); } catch (...) { ignoreException(); } @@ -62,7 +72,7 @@ void SQLite::exec(const std::string & stmt) { retrySQLite([&]() { if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK) - throwSQLiteError(db, format("executing SQLite statement '%s'") % stmt); + SQLiteError::throw_(db, "executing SQLite statement '%s'", stmt); }); } @@ -76,7 +86,7 @@ void SQLiteStmt::create(sqlite3 * db, const std::string & sql) checkInterrupt(); assert(!stmt); if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0) != SQLITE_OK) - throwSQLiteError(db, fmt("creating statement '%s'", sql)); + SQLiteError::throw_(db, "creating statement '%s'", sql); this->db = db; this->sql = sql; } @@ -85,7 +95,7 @@ SQLiteStmt::~SQLiteStmt() { try { if (stmt && sqlite3_finalize(stmt) != SQLITE_OK) - throwSQLiteError(db, fmt("finalizing statement '%s'", sql)); + SQLiteError::throw_(db, "finalizing statement '%s'", sql); } catch (...) { ignoreException(); } @@ -109,7 +119,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (std::string_view value, bool not { if (notNull) { if (sqlite3_bind_text(stmt, curArg++, value.data(), -1, SQLITE_TRANSIENT) != SQLITE_OK) - throwSQLiteError(stmt.db, "binding argument"); + SQLiteError::throw_(stmt.db, "binding argument"); } else bind(); return *this; @@ -119,7 +129,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (const unsigned char * data, size { if (notNull) { if (sqlite3_bind_blob(stmt, curArg++, data, len, SQLITE_TRANSIENT) != SQLITE_OK) - throwSQLiteError(stmt.db, "binding argument"); + SQLiteError::throw_(stmt.db, "binding argument"); } else bind(); return *this; @@ -129,7 +139,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (int64_t value, bool notNull) { if (notNull) { if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK) - throwSQLiteError(stmt.db, "binding argument"); + SQLiteError::throw_(stmt.db, "binding argument"); } else bind(); return *this; @@ -138,7 +148,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (int64_t value, bool notNull) SQLiteStmt::Use & SQLiteStmt::Use::bind() { if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK) - throwSQLiteError(stmt.db, "binding argument"); + SQLiteError::throw_(stmt.db, "binding argument"); return *this; } @@ -152,14 +162,14 @@ void SQLiteStmt::Use::exec() int r = step(); assert(r != SQLITE_ROW); if (r != SQLITE_DONE) - throwSQLiteError(stmt.db, fmt("executing SQLite statement '%s'", sqlite3_expanded_sql(stmt.stmt))); + SQLiteError::throw_(stmt.db, fmt("executing SQLite statement '%s'", sqlite3_expanded_sql(stmt.stmt))); } bool SQLiteStmt::Use::next() { int r = step(); if (r != SQLITE_DONE && r != SQLITE_ROW) - throwSQLiteError(stmt.db, fmt("executing SQLite query '%s'", sqlite3_expanded_sql(stmt.stmt))); + SQLiteError::throw_(stmt.db, fmt("executing SQLite query '%s'", sqlite3_expanded_sql(stmt.stmt))); return r == SQLITE_ROW; } @@ -185,14 +195,14 @@ SQLiteTxn::SQLiteTxn(sqlite3 * db) { this->db = db; if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK) - throwSQLiteError(db, "starting transaction"); + SQLiteError::throw_(db, "starting transaction"); active = true; } void SQLiteTxn::commit() { if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK) - throwSQLiteError(db, "committing transaction"); + SQLiteError::throw_(db, "committing transaction"); active = false; } @@ -200,7 +210,7 @@ SQLiteTxn::~SQLiteTxn() { try { if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK) - throwSQLiteError(db, "aborting transaction"); + SQLiteError::throw_(db, "aborting transaction"); } catch (...) { ignoreException(); } diff --git a/src/libstore/sqlite.hh b/src/libstore/sqlite.hh index 99f0d56ce..1d1c553ea 100644 --- a/src/libstore/sqlite.hh +++ b/src/libstore/sqlite.hh @@ -96,10 +96,30 @@ struct SQLiteTxn }; -MakeError(SQLiteError, Error); -MakeError(SQLiteBusy, SQLiteError); +struct SQLiteError : Error +{ + const char *path; + int errNo, extendedErrNo; -[[noreturn]] void throwSQLiteError(sqlite3 * db, const FormatOrString & fs); + template + [[noreturn]] static void throw_(sqlite3 * db, const std::string & fs, const Args & ... args) { + throw_(db, hintfmt(fs, args...)); + } + + SQLiteError(const char *path, int errNo, int extendedErrNo, hintformat && hf); + +protected: + + template + SQLiteError(const char *path, int errNo, int extendedErrNo, const std::string & fs, const Args & ... args) + : SQLiteError(path, errNo, extendedErrNo, hintfmt(fs, args...)) + { } + + [[noreturn]] static void throw_(sqlite3 * db, hintformat && hf); + +}; + +MakeError(SQLiteBusy, SQLiteError); void handleSQLiteBusy(const SQLiteBusy & e); diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 69aa0d094..4b8c55686 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -127,11 +127,11 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) if (flag.handler.arity == ArityAny) break; throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); } - if (flag.completer) - if (auto prefix = needsCompletion(*pos)) { - anyCompleted = true; + if (auto prefix = needsCompletion(*pos)) { + anyCompleted = true; + if (flag.completer) flag.completer(n, *prefix); - } + } args.push_back(*pos++); } if (!anyCompleted) @@ -146,6 +146,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) && hasPrefix(name, std::string(*prefix, 2))) completions->add("--" + name, flag->description); } + return false; } auto i = longFlags.find(std::string(*pos, 2)); if (i == longFlags.end()) return false; @@ -187,10 +188,12 @@ bool Args::processArgs(const Strings & args, bool finish) { std::vector ss; for (const auto &[n, s] : enumerate(args)) { - ss.push_back(s); - if (exp.completer) - if (auto prefix = needsCompletion(s)) + if (auto prefix = needsCompletion(s)) { + ss.push_back(*prefix); + if (exp.completer) exp.completer(n, *prefix); + } else + ss.push_back(s); } exp.handler.fun(ss); expectedArgs.pop_front(); @@ -279,21 +282,22 @@ static void _completePath(std::string_view prefix, bool onlyDirs) { completionType = ctFilenames; glob_t globbuf; - int flags = GLOB_NOESCAPE | GLOB_TILDE; + int flags = GLOB_NOESCAPE; #ifdef GLOB_ONLYDIR if (onlyDirs) flags |= GLOB_ONLYDIR; #endif - if (glob((std::string(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) { + // using expandTilde here instead of GLOB_TILDE(_CHECK) so that ~ expands to /home/user/ + if (glob((expandTilde(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) { for (size_t i = 0; i < globbuf.gl_pathc; ++i) { if (onlyDirs) { - auto st = lstat(globbuf.gl_pathv[i]); + auto st = stat(globbuf.gl_pathv[i]); if (!S_ISDIR(st.st_mode)) continue; } completions->add(globbuf.gl_pathv[i]); } - globfree(&globbuf); } + globfree(&globbuf); } void completePath(size_t, std::string_view prefix) @@ -322,11 +326,6 @@ MultiCommand::MultiCommand(const Commands & commands_) .optional = true, .handler = {[=](std::string s) { assert(!command); - if (auto prefix = needsCompletion(s)) { - for (auto & [name, command] : commands) - if (hasPrefix(name, *prefix)) - completions->add(name); - } auto i = commands.find(s); if (i == commands.end()) { std::set commandNames; @@ -337,6 +336,11 @@ MultiCommand::MultiCommand(const Commands & commands_) } command = {s, i->second()}; command->second->parent = this; + }}, + .completer = {[&](size_t, std::string_view prefix) { + for (auto & [name, command] : commands) + if (hasPrefix(name, prefix)) + completions->add(name); }} }); diff --git a/src/libutil/chunked-vector.hh b/src/libutil/chunked-vector.hh new file mode 100644 index 000000000..0a4f0b400 --- /dev/null +++ b/src/libutil/chunked-vector.hh @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include + +namespace nix { + +/* Provides an indexable container like vector<> with memory overhead + guarantees like list<> by allocating storage in chunks of ChunkSize + elements instead of using a contiguous memory allocation like vector<> + does. Not using a single vector that is resized reduces memory overhead + on large data sets by on average (growth factor)/2, mostly + eliminates copies within the vector during resizing, and provides stable + references to its elements. */ +template +class ChunkedVector { +private: + uint32_t size_ = 0; + std::vector> chunks; + + /* keep this out of the ::add hot path */ + [[gnu::noinline]] + auto & addChunk() + { + if (size_ >= std::numeric_limits::max() - ChunkSize) + abort(); + chunks.emplace_back(); + chunks.back().reserve(ChunkSize); + return chunks.back(); + } + +public: + ChunkedVector(uint32_t reserve) + { + chunks.reserve(reserve); + addChunk(); + } + + uint32_t size() const { return size_; } + + std::pair add(T value) + { + const auto idx = size_++; + auto & chunk = [&] () -> auto & { + if (auto & back = chunks.back(); back.size() < ChunkSize) + return back; + return addChunk(); + }(); + auto & result = chunk.emplace_back(std::move(value)); + return {result, idx}; + } + + const T & operator[](uint32_t idx) const + { + return chunks[idx / ChunkSize][idx % ChunkSize]; + } + + template + void forEach(Fn fn) const + { + for (const auto & c : chunks) + for (const auto & e : c) + fn(e); + } +}; +} diff --git a/src/libutil/error.hh b/src/libutil/error.hh index e31311c80..6eb80fb9e 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -87,11 +87,7 @@ struct ErrPos { origin = pos.origin; line = pos.line; column = pos.column; - // is file symbol null? - if (pos.file.set()) - file = pos.file; - else - file = ""; + file = pos.file; return *this; } diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index e033a4116..df37edf57 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -58,4 +58,18 @@ std::ostream & operator <<(std::ostream & str, const ExperimentalFeature & featu return str << showExperimentalFeature(feature); } +void to_json(nlohmann::json& j, const ExperimentalFeature& feature) { + j = showExperimentalFeature(feature); +} + +void from_json(const nlohmann::json& j, ExperimentalFeature& feature) { + const std::string input = j; + const auto parsed = parseExperimentalFeature(input); + + if (parsed.has_value()) + feature = *parsed; + else + throw Error("Unknown experimental feature '%s' in JSON input", input); +} + } diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index 266e41a22..a6d080094 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -51,4 +51,11 @@ public: MissingExperimentalFeature(ExperimentalFeature); }; +/** + * Semi-magic conversion to and from json. + * See the nlohmann/json readme for more details. + */ +void to_json(nlohmann::json&, const ExperimentalFeature&); +void from_json(const nlohmann::json&, ExperimentalFeature&); + } diff --git a/src/libutil/fmt.hh b/src/libutil/fmt.hh index 0821b3b74..7664e5c04 100644 --- a/src/libutil/fmt.hh +++ b/src/libutil/fmt.hh @@ -2,7 +2,6 @@ #include #include -#include #include "ansicolor.hh" @@ -155,15 +154,4 @@ inline hintformat hintfmt(std::string plain_string) return hintfmt("%s", normaltxt(plain_string)); } -/* Highlight all the given matches in the given string `s` by wrapping - them between `prefix` and `postfix`. - - If some matches overlap, then their union will be wrapped rather - than the individual matches. */ -std::string hiliteMatches( - std::string_view s, - std::vector matches, - std::string_view prefix, - std::string_view postfix); - } diff --git a/src/libutil/fmt.cc b/src/libutil/hilite.cc similarity index 96% rename from src/libutil/fmt.cc rename to src/libutil/hilite.cc index 3dd93d73e..a5991ca39 100644 --- a/src/libutil/fmt.cc +++ b/src/libutil/hilite.cc @@ -1,6 +1,4 @@ -#include "fmt.hh" - -#include +#include "hilite.hh" namespace nix { diff --git a/src/libutil/hilite.hh b/src/libutil/hilite.hh new file mode 100644 index 000000000..f8bdbfc55 --- /dev/null +++ b/src/libutil/hilite.hh @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +namespace nix { + +/* Highlight all the given matches in the given string `s` by wrapping + them between `prefix` and `postfix`. + + If some matches overlap, then their union will be wrapped rather + than the individual matches. */ +std::string hiliteMatches( + std::string_view s, + std::vector matches, + std::string_view prefix, + std::string_view postfix); + +} diff --git a/src/libutil/ref.hh b/src/libutil/ref.hh index 347b81f73..f9578afc7 100644 --- a/src/libutil/ref.hh +++ b/src/libutil/ref.hh @@ -99,47 +99,4 @@ make_ref(Args&&... args) return ref(p); } - -/* A non-nullable pointer. - This is similar to a C++ "& reference", but mutable. - This is similar to ref but backed by a regular pointer instead of a smart pointer. - */ -template -class ptr { -private: - T * p; - -public: - ptr(const ptr & r) - : p(r.p) - { } - - explicit ptr(T * p) - : p(p) - { - if (!p) - throw std::invalid_argument("null pointer cast to ptr"); - } - - T* operator ->() const - { - return &*p; - } - - T& operator *() const - { - return *p; - } - - bool operator == (const ptr & other) const - { - return p == other.p; - } - - bool operator != (const ptr & other) const - { - return p != other.p; - } -}; - } diff --git a/src/libutil/tests/chunked-vector.cc b/src/libutil/tests/chunked-vector.cc new file mode 100644 index 000000000..868d11f6f --- /dev/null +++ b/src/libutil/tests/chunked-vector.cc @@ -0,0 +1,54 @@ +#include "chunked-vector.hh" + +#include + +namespace nix { + TEST(ChunkedVector, InitEmpty) { + auto v = ChunkedVector(100); + ASSERT_EQ(v.size(), 0); + } + + TEST(ChunkedVector, GrowsCorrectly) { + auto v = ChunkedVector(100); + for (auto i = 1; i < 20; i++) { + v.add(i); + ASSERT_EQ(v.size(), i); + } + } + + TEST(ChunkedVector, AddAndGet) { + auto v = ChunkedVector(100); + for (auto i = 1; i < 20; i++) { + auto [i2, idx] = v.add(i); + auto & i3 = v[idx]; + ASSERT_EQ(i, i2); + ASSERT_EQ(&i2, &i3); + } + } + + TEST(ChunkedVector, ForEach) { + auto v = ChunkedVector(100); + for (auto i = 1; i < 20; i++) { + v.add(i); + } + int count = 0; + v.forEach([&count](int elt) { + count++; + }); + ASSERT_EQ(count, v.size()); + } + + TEST(ChunkedVector, OverflowOK) { + // Similar to the AddAndGet, but intentionnally use a small + // initial ChunkedVector to force it to overflow + auto v = ChunkedVector(2); + for (auto i = 1; i < 20; i++) { + auto [i2, idx] = v.add(i); + auto & i3 = v[idx]; + ASSERT_EQ(i, i2); + ASSERT_EQ(&i2, &i3); + } + } + +} + diff --git a/src/libutil/tests/fmt.cc b/src/libutil/tests/hilite.cc similarity index 98% rename from src/libutil/tests/fmt.cc rename to src/libutil/tests/hilite.cc index 33772162c..1ff5980d5 100644 --- a/src/libutil/tests/fmt.cc +++ b/src/libutil/tests/hilite.cc @@ -1,9 +1,7 @@ -#include "fmt.hh" +#include "hilite.hh" #include -#include - namespace nix { /* ----------- tests for fmt.hh -------------------------------------------------*/ diff --git a/src/libutil/types.hh b/src/libutil/types.hh index 00ba567c6..6bcbd7e1d 100644 --- a/src/libutil/types.hh +++ b/src/libutil/types.hh @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include diff --git a/src/libutil/util.cc b/src/libutil/util.cc index c075a14b4..b49c1e466 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -198,6 +198,17 @@ std::string_view baseNameOf(std::string_view path) } +std::string expandTilde(std::string_view path) +{ + // TODO: expand ~user ? + auto tilde = path.substr(0, 2); + if (tilde == "~/" || tilde == "~") + return getHome() + std::string(path.substr(1)); + else + return std::string(path); +} + + bool isInDir(std::string_view path, std::string_view dir) { return path.substr(0, 1) == "/" @@ -213,6 +224,15 @@ bool isDirOrInDir(std::string_view path, std::string_view dir) } +struct stat stat(const Path & path) +{ + struct stat st; + if (stat(path.c_str(), &st)) + throw SysError("getting status of '%1%'", path); + return st; +} + + struct stat lstat(const Path & path) { struct stat st; @@ -1062,7 +1082,7 @@ std::string runProgram(Path program, bool searchPath, const Strings & args, auto res = runProgram(RunOptions {.program = program, .searchPath = searchPath, .args = args, .input = input}); if (!statusOk(res.first)) - throw ExecError(res.first, fmt("program '%1%' %2%", program, statusToString(res.first))); + throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first)); return res.second; } @@ -1190,7 +1210,7 @@ void runProgram2(const RunOptions & options) if (source) promise.get_future().get(); if (status) - throw ExecError(status, fmt("program '%1%' %2%", options.program, statusToString(status))); + throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status)); } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 20591952d..a1d0e0e6b 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -68,6 +68,9 @@ Path dirOf(const PathView path); following the final `/' (trailing slashes are removed). */ std::string_view baseNameOf(std::string_view path); +/* Perform tilde expansion on a path. */ +std::string expandTilde(std::string_view path); + /* Check whether 'path' is a descendant of 'dir'. Both paths must be canonicalized. */ bool isInDir(std::string_view path, std::string_view dir); @@ -77,6 +80,7 @@ bool isInDir(std::string_view path, std::string_view dir); bool isDirOrInDir(std::string_view path, std::string_view dir); /* Get status of `path'. */ +struct stat stat(const Path & path); struct stat lstat(const Path & path); /* Return true iff the given path exists. */ diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index 9a68899cd..96f3c3b26 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -1241,7 +1241,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) Attr & a(*attrs.find(i.name)); if(a.value->type() != nString) continue; XMLAttrs attrs3; - attrs3["type"] = i.name; + attrs3["type"] = globals.state->symbols[i.name]; attrs3["value"] = a.value->string.s; xml.writeEmptyElement("string", attrs3); } diff --git a/src/nix-env/user-env.cc b/src/nix-env/user-env.cc index 78692b9c6..4b1202be3 100644 --- a/src/nix-env/user-env.cc +++ b/src/nix-env/user-env.cc @@ -106,7 +106,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems, the store; we need it for future modifications of the environment. */ std::ostringstream str; - manifest.print(str, true); + manifest.print(state.symbols, str, true); auto manifestFile = state.store->addTextToStore("env-manifest.nix", str.str(), references); @@ -134,9 +134,9 @@ bool createUserEnv(EvalState & state, DrvInfos & elems, state.forceValue(topLevel, [&]() { return topLevel.determinePos(noPos); }); PathSet context; Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath)); - auto topLevelDrv = state.coerceToStorePath(*aDrvPath.pos, *aDrvPath.value, context); + auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context); Attr & aOutPath(*topLevel.attrs->find(state.sOutPath)); - auto topLevelOut = state.coerceToStorePath(*aOutPath.pos, *aOutPath.value, context); + auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context); /* Realise the resulting store expression. */ debug("building user environment"); diff --git a/src/nix-instantiate/nix-instantiate.cc b/src/nix-instantiate/nix-instantiate.cc index 3ec0e6e7c..d3144e131 100644 --- a/src/nix-instantiate/nix-instantiate.cc +++ b/src/nix-instantiate/nix-instantiate.cc @@ -31,7 +31,8 @@ void processExpr(EvalState & state, const Strings & attrPaths, bool evalOnly, OutputKind output, bool location, Expr * e) { if (parseOnly) { - std::cout << format("%1%\n") % *e; + e->show(state.symbols, std::cout); + std::cout << "\n"; return; } @@ -55,7 +56,8 @@ void processExpr(EvalState & state, const Strings & attrPaths, printValueAsJSON(state, strict, vRes, v.determinePos(noPos), std::cout, context); else { if (strict) state.forceValueDeep(vRes); - std::cout << vRes << std::endl; + vRes.print(state.symbols, std::cout); + std::cout << std::endl; } } else { DrvInfos drvs; diff --git a/src/nix/app.cc b/src/nix/app.cc index 803d028f0..cce84d026 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -35,7 +35,7 @@ struct InstallableDerivedPath : Installable /** * Return the rewrites that are needed to resolve a string whose context is - * included in `dependencies` + * included in `dependencies`. */ StringPairs resolveRewrites(Store & store, const BuiltPaths dependencies) { @@ -51,7 +51,7 @@ StringPairs resolveRewrites(Store & store, const BuiltPaths dependencies) } /** - * Resolve the given string assuming the given context + * Resolve the given string assuming the given context. */ std::string resolveString(Store & store, const std::string & toResolve, const BuiltPaths dependencies) { @@ -61,14 +61,18 @@ std::string resolveString(Store & store, const std::string & toResolve, const Bu UnresolvedApp Installable::toApp(EvalState & state) { - auto [cursor, attrPath] = getCursor(state); + auto cursor = getCursor(state); + auto attrPath = cursor->getAttrPath(); auto type = cursor->getAttr("type")->getString(); + std::string expected = !attrPath.empty() && state.symbols[attrPath[0]] == "apps" ? "app" : "derivation"; + if (type != expected) + throw Error("attribute '%s' should have type '%s'", cursor->getAttrPathStr(), expected); + if (type == "app") { auto [program, context] = cursor->getAttr("program")->getStringWithContext(); - std::vector context2; for (auto & [path, name] : context) context2.push_back({path, {name}}); @@ -101,7 +105,7 @@ UnresolvedApp Installable::toApp(EvalState & state) } else - throw Error("attribute '%s' has unsupported type '%s'", attrPath, type); + throw Error("attribute '%s' has unsupported type '%s'", cursor->getAttrPathStr(), type); } // FIXME: move to libcmd diff --git a/src/nix/build.cc b/src/nix/build.cc index 840c7ca38..9c648d28e 100644 --- a/src/nix/build.cc +++ b/src/nix/build.cc @@ -4,6 +4,7 @@ #include "shared.hh" #include "store-api.hh" #include "local-fs-store.hh" +#include "progress-bar.hh" #include @@ -12,6 +13,7 @@ using namespace nix; struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile { Path outLink = "result"; + bool printOutputPaths = false; BuildMode buildMode = bmNormal; CmdBuild() @@ -31,6 +33,12 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile .handler = {&outLink, Path("")}, }); + addFlag({ + .longName = "print-out-paths", + .description = "Print the resulting output paths", + .handler = {&printOutputPaths, true}, + }); + addFlag({ .longName = "rebuild", .description = "Rebuild an already built package and compare the result to the existing store paths.", @@ -93,6 +101,22 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile }, buildable.raw()); } + if (printOutputPaths) { + stopProgressBar(); + for (auto & buildable : buildables) { + std::visit(overloaded { + [&](const BuiltPath::Opaque & bo) { + std::cout << store->printStorePath(bo.path) << std::endl; + }, + [&](const BuiltPath::Built & bfd) { + for (auto & output : bfd.outputs) { + std::cout << store->printStorePath(output.second) << std::endl; + } + }, + }, buildable.raw()); + } + } + updateProfile(buildables); } }; diff --git a/src/nix/build.md b/src/nix/build.md index 20138b7e0..6a79f308c 100644 --- a/src/nix/build.md +++ b/src/nix/build.md @@ -25,6 +25,13 @@ R""( lrwxrwxrwx 1 … result-1 -> /nix/store/rkfrm0z6x6jmi7d3gsmma4j53h15mg33-cowsay-3.03+dfsg2 ``` +* Build GNU Hello and print the resulting store path. + + ```console + # nix build nixpkgs#hello --print-out-paths + /nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10 + ``` + * Build a specific output: ```console diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc index 81fb8464a..2421adf4e 100644 --- a/src/nix/bundle.cc +++ b/src/nix/bundle.cc @@ -97,21 +97,23 @@ struct CmdBundle : InstallableCommand throw Error("the bundler '%s' does not produce a derivation", bundler.what()); PathSet context2; - auto drvPath = evalState->coerceToStorePath(*attr1->pos, *attr1->value, context2); + auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2); auto attr2 = vRes->attrs->get(evalState->sOutPath); if (!attr2) throw Error("the bundler '%s' does not produce a derivation", bundler.what()); - auto outPath = evalState->coerceToStorePath(*attr2->pos, *attr2->value, context2); + auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2); store->buildPaths({ DerivedPath::Built { drvPath } }); auto outPathS = store->printStorePath(outPath); if (!outLink) { - auto &attr = vRes->attrs->need(evalState->sName); - outLink = evalState->forceStringNoCtx(*attr.value,*attr.pos); + auto * attr = vRes->attrs->get(evalState->sName); + if (!attr) + throw Error("attribute 'name' missing"); + outLink = evalState->forceStringNoCtx(*attr->value, attr->pos); } // TODO: will crash if not a localFSStore? diff --git a/src/nix/edit.cc b/src/nix/edit.cc index fc48db0d7..76a134b1f 100644 --- a/src/nix/edit.cc +++ b/src/nix/edit.cc @@ -28,19 +28,19 @@ struct CmdEdit : InstallableCommand { auto state = getEvalState(); - auto [v, pos] = installable->toValue(*state); + const auto [file, line] = [&] { + auto [v, pos] = installable->toValue(*state); - try { - pos = findPackageFilename(*state, *v, installable->what()); - } catch (NoPositionInfo &) { - } - - if (pos == noPos) - throw Error("cannot find position information for '%s", installable->what()); + try { + return findPackageFilename(*state, *v, installable->what()); + } catch (NoPositionInfo &) { + throw Error("cannot find position information for '%s", installable->what()); + } + }(); stopProgressBar(); - auto args = editorFor(pos); + auto args = editorFor(file, line); restoreProcessContext(); diff --git a/src/nix/eval.cc b/src/nix/eval.cc index 733b93661..967dc8519 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -77,9 +77,9 @@ struct CmdEval : MixJSON, InstallableCommand if (pathExists(*writeTo)) throw Error("path '%s' already exists", *writeTo); - std::function recurse; + std::function recurse; - recurse = [&](Value & v, const Pos & pos, const Path & path) + recurse = [&](Value & v, const PosIdx pos, const Path & path) { state->forceValue(v, pos); if (v.type() == nString) @@ -88,18 +88,22 @@ struct CmdEval : MixJSON, InstallableCommand else if (v.type() == nAttrs) { if (mkdir(path.c_str(), 0777) == -1) throw SysError("creating directory '%s'", path); - for (auto & attr : *v.attrs) + for (auto & attr : *v.attrs) { + std::string_view name = state->symbols[attr.name]; try { - if (attr.name == "." || attr.name == "..") - throw Error("invalid file name '%s'", attr.name); - recurse(*attr.value, *attr.pos, path + "/" + std::string(attr.name)); + if (name == "." || name == "..") + throw Error("invalid file name '%s'", name); + recurse(*attr.value, attr.pos, concatStrings(path, "/", name)); } catch (Error & e) { - e.addTrace(*attr.pos, hintfmt("while evaluating the attribute '%s'", attr.name)); + e.addTrace( + state->positions[attr.pos], + hintfmt("while evaluating the attribute '%s'", name)); throw; } + } } else - throw TypeError("value at '%s' is not a string or an attribute set", pos); + throw TypeError("value at '%s' is not a string or an attribute set", state->positions[pos]); }; recurse(*v, pos, *writeTo); @@ -117,7 +121,7 @@ struct CmdEval : MixJSON, InstallableCommand else { state->forceValueDeep(*v); - logger->cout("%s", *v); + logger->cout("%s", printValue(*state, *v)); } } }; diff --git a/src/nix/flake.cc b/src/nix/flake.cc index a876bb3af..040c1c7af 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -123,7 +123,7 @@ struct CmdFlakeLock : FlakeCommand }; static void enumerateOutputs(EvalState & state, Value & vFlake, - std::function callback) + std::function callback) { auto pos = vFlake.determinePos(noPos); state.forceAttrs(vFlake, pos); @@ -139,11 +139,11 @@ static void enumerateOutputs(EvalState & state, Value & vFlake, else. This way we can disable IFD for hydraJobs and then enable it for other outputs. */ if (auto attr = aOutputs->value->attrs->get(sHydraJobs)) - callback(attr->name, *attr->value, *attr->pos); + callback(state.symbols[attr->name], *attr->value, attr->pos); for (auto & attr : *aOutputs->value->attrs) { if (attr.name != sHydraJobs) - callback(attr.name, *attr.value, *attr.pos); + callback(state.symbols[attr.name], *attr.value, attr.pos); } } @@ -254,14 +254,6 @@ struct CmdFlakeInfo : CmdFlakeMetadata } }; -static bool argHasName(std::string_view arg, std::string_view expected) -{ - return - arg == expected - || arg == "_" - || (hasPrefix(arg, "_") && arg.substr(1) == expected); -} - struct CmdFlakeCheck : FlakeCommand { bool build = true; @@ -315,13 +307,25 @@ struct CmdFlakeCheck : FlakeCommand // FIXME: rewrite to use EvalCache. - auto checkSystemName = [&](const std::string & system, const Pos & pos) { - // FIXME: what's the format of "system"? - if (system.find('-') == std::string::npos) - reportError(Error("'%s' is not a valid system type, at %s", system, pos)); + auto resolve = [&] (PosIdx p) { + return state->positions[p]; }; - auto checkDerivation = [&](const std::string & attrPath, Value & v, const Pos & pos) -> std::optional { + auto argHasName = [&] (Symbol arg, std::string_view expected) { + std::string_view name = state->symbols[arg]; + return + name == expected + || name == "_" + || (hasPrefix(name, "_") && name.substr(1) == expected); + }; + + auto checkSystemName = [&](const std::string & system, const PosIdx pos) { + // FIXME: what's the format of "system"? + if (system.find('-') == std::string::npos) + reportError(Error("'%s' is not a valid system type, at %s", system, resolve(pos))); + }; + + auto checkDerivation = [&](const std::string & attrPath, Value & v, const PosIdx pos) -> std::optional { try { auto drvInfo = getDerivation(*state, v, false); if (!drvInfo) @@ -329,7 +333,7 @@ struct CmdFlakeCheck : FlakeCommand // FIXME: check meta attributes return drvInfo->queryDrvPath(); } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the derivation '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the derivation '%s'", attrPath)); reportError(e); } return std::nullopt; @@ -337,7 +341,7 @@ struct CmdFlakeCheck : FlakeCommand std::vector drvPaths; - auto checkApp = [&](const std::string & attrPath, Value & v, const Pos & pos) { + auto checkApp = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { #if 0 // FIXME @@ -348,12 +352,12 @@ struct CmdFlakeCheck : FlakeCommand } #endif } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the app definition '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the app definition '%s'", attrPath)); reportError(e); } }; - auto checkOverlay = [&](const std::string & attrPath, Value & v, const Pos & pos) { + auto checkOverlay = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { state->forceValue(v, pos); if (!v.isLambda() @@ -368,12 +372,12 @@ struct CmdFlakeCheck : FlakeCommand // FIXME: if we have a 'nixpkgs' input, use it to // evaluate the overlay. } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the overlay '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the overlay '%s'", attrPath)); reportError(e); } }; - auto checkModule = [&](const std::string & attrPath, Value & v, const Pos & pos) { + auto checkModule = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { state->forceValue(v, pos); if (v.isLambda()) { @@ -382,9 +386,11 @@ struct CmdFlakeCheck : FlakeCommand } else if (v.type() == nAttrs) { for (auto & attr : *v.attrs) try { - state->forceValue(*attr.value, *attr.pos); + state->forceValue(*attr.value, attr.pos); } catch (Error & e) { - e.addTrace(*attr.pos, hintfmt("while evaluating the option '%s'", attr.name)); + e.addTrace( + state->positions[attr.pos], + hintfmt("while evaluating the option '%s'", state->symbols[attr.name])); throw; } } else @@ -392,14 +398,14 @@ struct CmdFlakeCheck : FlakeCommand // FIXME: if we have a 'nixpkgs' input, use it to // check the module. } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the NixOS module '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the NixOS module '%s'", attrPath)); reportError(e); } }; - std::function checkHydraJobs; + std::function checkHydraJobs; - checkHydraJobs = [&](const std::string & attrPath, Value & v, const Pos & pos) { + checkHydraJobs = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { state->forceAttrs(v, pos); @@ -407,23 +413,23 @@ struct CmdFlakeCheck : FlakeCommand throw Error("jobset should not be a derivation at top-level"); for (auto & attr : *v.attrs) { - state->forceAttrs(*attr.value, *attr.pos); - auto attrPath2 = attrPath + "." + (std::string) attr.name; + state->forceAttrs(*attr.value, attr.pos); + auto attrPath2 = concatStrings(attrPath, ".", state->symbols[attr.name]); if (state->isDerivation(*attr.value)) { Activity act(*logger, lvlChatty, actUnknown, fmt("checking Hydra job '%s'", attrPath2)); - checkDerivation(attrPath2, *attr.value, *attr.pos); + checkDerivation(attrPath2, *attr.value, attr.pos); } else - checkHydraJobs(attrPath2, *attr.value, *attr.pos); + checkHydraJobs(attrPath2, *attr.value, attr.pos); } } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the Hydra jobset '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the Hydra jobset '%s'", attrPath)); reportError(e); } }; - auto checkNixOSConfiguration = [&](const std::string & attrPath, Value & v, const Pos & pos) { + auto checkNixOSConfiguration = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { Activity act(*logger, lvlChatty, actUnknown, fmt("checking NixOS configuration '%s'", attrPath)); @@ -433,12 +439,12 @@ struct CmdFlakeCheck : FlakeCommand if (!state->isDerivation(*vToplevel)) throw Error("attribute 'config.system.build.toplevel' is not a derivation"); } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the NixOS configuration '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the NixOS configuration '%s'", attrPath)); reportError(e); } }; - auto checkTemplate = [&](const std::string & attrPath, Value & v, const Pos & pos) { + auto checkTemplate = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { Activity act(*logger, lvlChatty, actUnknown, fmt("checking template '%s'", attrPath)); @@ -448,7 +454,7 @@ struct CmdFlakeCheck : FlakeCommand if (auto attr = v.attrs->get(state->symbols.create("path"))) { if (attr->name == state->symbols.create("path")) { PathSet context; - auto path = state->coerceToPath(*attr->pos, *attr->value, context); + auto path = state->coerceToPath(attr->pos, *attr->value, context); if (!store->isInStore(path)) throw Error("template '%s' has a bad 'path' attribute"); // TODO: recursively check the flake in 'path'. @@ -457,29 +463,29 @@ struct CmdFlakeCheck : FlakeCommand throw Error("template '%s' lacks attribute 'path'", attrPath); if (auto attr = v.attrs->get(state->symbols.create("description"))) - state->forceStringNoCtx(*attr->value, *attr->pos); + state->forceStringNoCtx(*attr->value, attr->pos); else throw Error("template '%s' lacks attribute 'description'", attrPath); for (auto & attr : *v.attrs) { - std::string name(attr.name); + std::string_view name(state->symbols[attr.name]); if (name != "path" && name != "description" && name != "welcomeText") throw Error("template '%s' has unsupported attribute '%s'", attrPath, name); } } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the template '%s'", attrPath)); reportError(e); } }; - auto checkBundler = [&](const std::string & attrPath, Value & v, const Pos & pos) { + auto checkBundler = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { state->forceValue(v, pos); if (!v.isLambda()) throw Error("bundler must be a function"); // TODO: check types of inputs/outputs? } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the template '%s'", attrPath)); reportError(e); } }; @@ -492,7 +498,7 @@ struct CmdFlakeCheck : FlakeCommand enumerateOutputs(*state, *vFlake, - [&](const std::string & name, Value & vOutput, const Pos & pos) { + [&](const std::string & name, Value & vOutput, const PosIdx pos) { Activity act(*logger, lvlChatty, actUnknown, fmt("checking flake output '%s'", name)); @@ -516,66 +522,82 @@ struct CmdFlakeCheck : FlakeCommand if (name == "checks") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); - state->forceAttrs(*attr.value, *attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); + state->forceAttrs(*attr.value, attr.pos); for (auto & attr2 : *attr.value->attrs) { auto drvPath = checkDerivation( - fmt("%s.%s.%s", name, attr.name, attr2.name), - *attr2.value, *attr2.pos); - if (drvPath && (std::string) attr.name == settings.thisSystem.get()) + fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), + *attr2.value, attr2.pos); + if (drvPath && attr_name == settings.thisSystem.get()) drvPaths.push_back(DerivedPath::Built{*drvPath}); } } } + else if (name == "formatter") { + state->forceAttrs(vOutput, pos); + for (auto & attr : *vOutput.attrs) { + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); + checkApp( + fmt("%s.%s", name, attr_name), + *attr.value, attr.pos); + } + } + else if (name == "packages" || name == "devShells") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); - state->forceAttrs(*attr.value, *attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); + state->forceAttrs(*attr.value, attr.pos); for (auto & attr2 : *attr.value->attrs) checkDerivation( - fmt("%s.%s.%s", name, attr.name, attr2.name), - *attr2.value, *attr2.pos); + fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), + *attr2.value, attr2.pos); } } else if (name == "apps") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); - state->forceAttrs(*attr.value, *attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); + state->forceAttrs(*attr.value, attr.pos); for (auto & attr2 : *attr.value->attrs) checkApp( - fmt("%s.%s.%s", name, attr.name, attr2.name), - *attr2.value, *attr2.pos); + fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), + *attr2.value, attr2.pos); } } else if (name == "defaultPackage" || name == "devShell") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); checkDerivation( - fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + fmt("%s.%s", name, attr_name), + *attr.value, attr.pos); } } else if (name == "defaultApp") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); checkApp( - fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + fmt("%s.%s", name, attr_name), + *attr.value, attr.pos); } } else if (name == "legacyPackages") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); + checkSystemName(state->symbols[attr.name], attr.pos); // FIXME: do getDerivations? } } @@ -586,8 +608,8 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "overlays") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) - checkOverlay(fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + checkOverlay(fmt("%s.%s", name, state->symbols[attr.name]), + *attr.value, attr.pos); } else if (name == "nixosModule") @@ -596,15 +618,15 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "nixosModules") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) - checkModule(fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + checkModule(fmt("%s.%s", name, state->symbols[attr.name]), + *attr.value, attr.pos); } else if (name == "nixosConfigurations") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) - checkNixOSConfiguration(fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + checkNixOSConfiguration(fmt("%s.%s", name, state->symbols[attr.name]), + *attr.value, attr.pos); } else if (name == "hydraJobs") @@ -616,29 +638,31 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "templates") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) - checkTemplate(fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + checkTemplate(fmt("%s.%s", name, state->symbols[attr.name]), + *attr.value, attr.pos); } else if (name == "defaultBundler") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); checkBundler( - fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + fmt("%s.%s", name, attr_name), + *attr.value, attr.pos); } } else if (name == "bundlers") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); - state->forceAttrs(*attr.value, *attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); + state->forceAttrs(*attr.value, attr.pos); for (auto & attr2 : *attr.value->attrs) { checkBundler( - fmt("%s.%s.%s", name, attr.name, attr2.name), - *attr2.value, *attr2.pos); + fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), + *attr2.value, attr2.pos); } } } @@ -647,7 +671,7 @@ struct CmdFlakeCheck : FlakeCommand warn("unknown flake output '%s'", name); } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking flake output '%s'", name)); + e.addTrace(resolve(pos), hintfmt("while checking flake output '%s'", name)); reportError(e); } }); @@ -705,7 +729,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand defaultTemplateAttrPathsPrefixes, lockFlags); - auto [cursor, attrPath] = installable.getCursor(*evalState); + auto cursor = installable.getCursor(*evalState); auto templateDirAttr = cursor->getAttr("path"); auto templateDir = templateDirAttr->getString(); @@ -962,8 +986,11 @@ struct CmdFlakeShow : FlakeCommand, MixJSON { auto j = nlohmann::json::object(); + auto attrPathS = state->symbols.resolve(attrPath); + Activity act(*logger, lvlInfo, actUnknown, - fmt("evaluating '%s'", concatStringsSep(".", attrPath))); + fmt("evaluating '%s'", concatStringsSep(".", attrPathS))); + try { auto recurse = [&]() { @@ -971,14 +998,15 @@ struct CmdFlakeShow : FlakeCommand, MixJSON logger->cout("%s", headerPrefix); auto attrs = visitor.getAttrs(); for (const auto & [i, attr] : enumerate(attrs)) { + const auto & attrName = state->symbols[attr]; bool last = i + 1 == attrs.size(); - auto visitor2 = visitor.getAttr(attr); + auto visitor2 = visitor.getAttr(attrName); auto attrPath2(attrPath); attrPath2.push_back(attr); auto j2 = visit(*visitor2, attrPath2, - fmt(ANSI_GREEN "%s%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL, nextPrefix, last ? treeLast : treeConn, attr), + fmt(ANSI_GREEN "%s%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL, nextPrefix, last ? treeLast : treeConn, attrName), nextPrefix + (last ? treeNull : treeLine)); - if (json) j.emplace(attr, std::move(j2)); + if (json) j.emplace(attrName, std::move(j2)); } }; @@ -998,10 +1026,10 @@ struct CmdFlakeShow : FlakeCommand, MixJSON } else { logger->cout("%s: %s '%s'", headerPrefix, - attrPath.size() == 2 && attrPath[0] == "devShell" ? "development environment" : - attrPath.size() >= 2 && attrPath[0] == "devShells" ? "development environment" : - attrPath.size() == 3 && attrPath[0] == "checks" ? "derivation" : - attrPath.size() >= 1 && attrPath[0] == "hydraJobs" ? "derivation" : + attrPath.size() == 2 && attrPathS[0] == "devShell" ? "development environment" : + attrPath.size() >= 2 && attrPathS[0] == "devShells" ? "development environment" : + attrPath.size() == 3 && attrPathS[0] == "checks" ? "derivation" : + attrPath.size() >= 1 && attrPathS[0] == "hydraJobs" ? "derivation" : "package", name); } @@ -1009,26 +1037,27 @@ struct CmdFlakeShow : FlakeCommand, MixJSON if (attrPath.size() == 0 || (attrPath.size() == 1 && ( - attrPath[0] == "defaultPackage" - || attrPath[0] == "devShell" - || attrPath[0] == "nixosConfigurations" - || attrPath[0] == "nixosModules" - || attrPath[0] == "defaultApp" - || attrPath[0] == "templates" - || attrPath[0] == "overlays")) + attrPathS[0] == "defaultPackage" + || attrPathS[0] == "devShell" + || attrPathS[0] == "formatter" + || attrPathS[0] == "nixosConfigurations" + || attrPathS[0] == "nixosModules" + || attrPathS[0] == "defaultApp" + || attrPathS[0] == "templates" + || attrPathS[0] == "overlays")) || ((attrPath.size() == 1 || attrPath.size() == 2) - && (attrPath[0] == "checks" - || attrPath[0] == "packages" - || attrPath[0] == "devShells" - || attrPath[0] == "apps")) + && (attrPathS[0] == "checks" + || attrPathS[0] == "packages" + || attrPathS[0] == "devShells" + || attrPathS[0] == "apps")) ) { recurse(); } else if ( - (attrPath.size() == 2 && (attrPath[0] == "defaultPackage" || attrPath[0] == "devShell")) - || (attrPath.size() == 3 && (attrPath[0] == "checks" || attrPath[0] == "packages" || attrPath[0] == "devShells")) + (attrPath.size() == 2 && (attrPathS[0] == "defaultPackage" || attrPathS[0] == "devShell" || attrPathS[0] == "formatter")) + || (attrPath.size() == 3 && (attrPathS[0] == "checks" || attrPathS[0] == "packages" || attrPathS[0] == "devShells")) ) { if (visitor.isDerivation()) @@ -1037,14 +1066,14 @@ struct CmdFlakeShow : FlakeCommand, MixJSON throw Error("expected a derivation"); } - else if (attrPath.size() > 0 && attrPath[0] == "hydraJobs") { + else if (attrPath.size() > 0 && attrPathS[0] == "hydraJobs") { if (visitor.isDerivation()) showDerivation(); else recurse(); } - else if (attrPath.size() > 0 && attrPath[0] == "legacyPackages") { + else if (attrPath.size() > 0 && attrPathS[0] == "legacyPackages") { if (attrPath.size() == 1) recurse(); else if (!showLegacy) @@ -1059,8 +1088,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON } else if ( - (attrPath.size() == 2 && attrPath[0] == "defaultApp") || - (attrPath.size() == 3 && attrPath[0] == "apps")) + (attrPath.size() == 2 && attrPathS[0] == "defaultApp") || + (attrPath.size() == 3 && attrPathS[0] == "apps")) { auto aType = visitor.maybeGetAttr("type"); if (!aType || aType->getString() != "app") @@ -1073,8 +1102,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON } else if ( - (attrPath.size() == 1 && attrPath[0] == "defaultTemplate") || - (attrPath.size() == 2 && attrPath[0] == "templates")) + (attrPath.size() == 1 && attrPathS[0] == "defaultTemplate") || + (attrPath.size() == 2 && attrPathS[0] == "templates")) { auto description = visitor.getAttr("description")->getString(); if (json) { @@ -1087,11 +1116,11 @@ struct CmdFlakeShow : FlakeCommand, MixJSON else { auto [type, description] = - (attrPath.size() == 1 && attrPath[0] == "overlay") - || (attrPath.size() == 2 && attrPath[0] == "overlays") ? std::make_pair("nixpkgs-overlay", "Nixpkgs overlay") : - attrPath.size() == 2 && attrPath[0] == "nixosConfigurations" ? std::make_pair("nixos-configuration", "NixOS configuration") : - (attrPath.size() == 1 && attrPath[0] == "nixosModule") - || (attrPath.size() == 2 && attrPath[0] == "nixosModules") ? std::make_pair("nixos-module", "NixOS module") : + (attrPath.size() == 1 && attrPathS[0] == "overlay") + || (attrPath.size() == 2 && attrPathS[0] == "overlays") ? std::make_pair("nixpkgs-overlay", "Nixpkgs overlay") : + attrPath.size() == 2 && attrPathS[0] == "nixosConfigurations" ? std::make_pair("nixos-configuration", "NixOS configuration") : + (attrPath.size() == 1 && attrPathS[0] == "nixosModule") + || (attrPath.size() == 2 && attrPathS[0] == "nixosModules") ? std::make_pair("nixos-module", "NixOS module") : std::make_pair("unknown", "unknown"); if (json) { j.emplace("type", type); @@ -1100,7 +1129,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON } } } catch (EvalError & e) { - if (!(attrPath.size() > 0 && attrPath[0] == "legacyPackages")) + if (!(attrPath.size() > 0 && attrPathS[0] == "legacyPackages")) throw; } diff --git a/src/nix/flake.md b/src/nix/flake.md index d59915eeb..7d179a6c4 100644 --- a/src/nix/flake.md +++ b/src/nix/flake.md @@ -177,8 +177,8 @@ Currently the `type` attribute can be one of the following: attribute `url`. In URL form, the schema must be `http://`, `https://` or `file://` - URLs and the extension must be `.zip`, `.tar`, `.tar.gz`, `.tar.xz`, - `.tar.bz2` or `.tar.zst`. + URLs and the extension must be `.zip`, `.tar`, `.tgz`, `.tar.gz`, + `.tar.xz`, `.tar.bz2` or `.tar.zst`. * `github`: A more efficient way to fetch repositories from GitHub. The following attributes are required: diff --git a/src/nix/fmt.cc b/src/nix/fmt.cc new file mode 100644 index 000000000..6f6a4a632 --- /dev/null +++ b/src/nix/fmt.cc @@ -0,0 +1,54 @@ +#include "command.hh" +#include "run.hh" + +using namespace nix; + +struct CmdFmt : SourceExprCommand { + std::vector args; + + CmdFmt() { expectArgs({.label = "args", .handler = {&args}}); } + + std::string description() override { + return "reformat your code in the standard style"; + } + + std::string doc() override { + return + #include "fmt.md" + ; + } + + Category category() override { return catSecondary; } + + Strings getDefaultFlakeAttrPaths() override { + return Strings{"formatter." + settings.thisSystem.get()}; + } + + Strings getDefaultFlakeAttrPathPrefixes() override { return Strings{}; } + + void run(ref store) override + { + auto evalState = getEvalState(); + auto evalStore = getEvalStore(); + + auto installable = parseInstallable(store, "."); + auto app = installable->toApp(*evalState).resolve(evalStore, store); + + Strings programArgs{app.program}; + + // Propagate arguments from the CLI + if (args.empty()) { + // Format the current flake out of the box + programArgs.push_back("."); + } else { + // User wants more power, let them decide which paths to include/exclude + for (auto &i : args) { + programArgs.push_back(i); + } + } + + runProgramInStore(store, app.program, programArgs); + }; +}; + +static auto r2 = registerCommand("fmt"); diff --git a/src/nix/fmt.md b/src/nix/fmt.md new file mode 100644 index 000000000..1c78bb36f --- /dev/null +++ b/src/nix/fmt.md @@ -0,0 +1,53 @@ +R""( + +# Examples + +With [nixpkgs-fmt](https://github.com/nix-community/nixpkgs-fmt): + +```nix +# flake.nix +{ + outputs = { nixpkgs, self }: { + formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixpkgs-fmt; + }; +} +``` + +- Format the current flake: `$ nix fmt` + +- Format a specific folder or file: `$ nix fmt ./folder ./file.nix` + +With [nixfmt](https://github.com/serokell/nixfmt): + +```nix +# flake.nix +{ + outputs = { nixpkgs, self }: { + formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt; + }; +} +``` + +- Format specific files: `$ nix fmt ./file1.nix ./file2.nix` + +With [Alejandra](https://github.com/kamadorueda/alejandra): + +```nix +# flake.nix +{ + outputs = { nixpkgs, self }: { + formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.alejandra; + }; +} +``` + +- Format the current flake: `$ nix fmt` + +- Format a specific folder or file: `$ nix fmt ./folder ./file.nix` + +# Description + +`nix fmt` will rewrite all Nix files (\*.nix) to a canonical format +using the formatter specified in your flake. + +)"" diff --git a/src/nix/main.cc b/src/nix/main.cc index 6198681e7..6d0f6ce6e 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -302,7 +302,7 @@ void mainWrapped(int argc, char * * argv) b["arity"] = primOp->arity; b["args"] = primOp->args; b["doc"] = trim(stripIndentation(primOp->doc)); - res[(std::string) builtin.name] = std::move(b); + res[state.symbols[builtin.name]] = std::move(b); } std::cout << res.dump() << "\n"; return; diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc index f2dd44ba4..ce3288dc1 100644 --- a/src/nix/prefetch.cc +++ b/src/nix/prefetch.cc @@ -199,11 +199,13 @@ static int main_nix_prefetch_url(int argc, char * * argv) state->forceAttrs(v, noPos); /* Extract the URL. */ - auto & attr = v.attrs->need(state->symbols.create("urls")); - state->forceList(*attr.value, noPos); - if (attr.value->listSize() < 1) + auto * attr = v.attrs->get(state->symbols.create("urls")); + if (!attr) + throw Error("attribute 'urls' missing"); + state->forceList(*attr->value, noPos); + if (attr->value->listSize() < 1) throw Error("'urls' list is empty"); - url = state->forceString(*attr.value->listElems()[0]); + url = state->forceString(*attr->value->listElems()[0]); /* Extract the hash mode. */ auto attr2 = v.attrs->get(state->symbols.create("outputHashMode")); diff --git a/src/nix/search.cc b/src/nix/search.cc index e9307342c..76451f810 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -9,7 +9,7 @@ #include "shared.hh" #include "eval-cache.hh" #include "attr-path.hh" -#include "fmt.hh" +#include "hilite.hh" #include #include @@ -77,13 +77,15 @@ struct CmdSearch : InstallableCommand, MixJSON visit = [&](eval_cache::AttrCursor & cursor, const std::vector & attrPath, bool initialRecurse) { + auto attrPathS = state->symbols.resolve(attrPath); + Activity act(*logger, lvlInfo, actUnknown, - fmt("evaluating '%s'", concatStringsSep(".", attrPath))); + fmt("evaluating '%s'", concatStringsSep(".", attrPathS))); try { auto recurse = [&]() { for (const auto & attr : cursor.getAttrs()) { - auto cursor2 = cursor.getAttr(attr); + auto cursor2 = cursor.getAttr(state->symbols[attr]); auto attrPath2(attrPath); attrPath2.push_back(attr); visit(*cursor2, attrPath2, false); @@ -97,7 +99,7 @@ struct CmdSearch : InstallableCommand, MixJSON auto aDescription = aMeta ? aMeta->maybeGetAttr("description") : nullptr; auto description = aDescription ? aDescription->getString() : ""; std::replace(description.begin(), description.end(), '\n', ' '); - auto attrPath2 = concatStringsSep(".", attrPath); + auto attrPath2 = concatStringsSep(".", attrPathS); std::vector attrPathMatches; std::vector descriptionMatches; @@ -146,27 +148,27 @@ struct CmdSearch : InstallableCommand, MixJSON else if ( attrPath.size() == 0 - || (attrPath[0] == "legacyPackages" && attrPath.size() <= 2) - || (attrPath[0] == "packages" && attrPath.size() <= 2)) + || (attrPathS[0] == "legacyPackages" && attrPath.size() <= 2) + || (attrPathS[0] == "packages" && attrPath.size() <= 2)) recurse(); else if (initialRecurse) recurse(); - else if (attrPath[0] == "legacyPackages" && attrPath.size() > 2) { + else if (attrPathS[0] == "legacyPackages" && attrPath.size() > 2) { auto attr = cursor.maybeGetAttr(state->sRecurseForDerivations); if (attr && attr->getBool()) recurse(); } } catch (EvalError & e) { - if (!(attrPath.size() > 0 && attrPath[0] == "legacyPackages")) + if (!(attrPath.size() > 0 && attrPathS[0] == "legacyPackages")) throw; } }; - for (auto & [cursor, prefix] : installable->getCursors(*state)) - visit(*cursor, parseAttrPath(*state, prefix), true); + for (auto & cursor : installable->getCursors(*state)) + visit(*cursor, cursor->getAttrPath(), true); if (!json && !results) throw Error("no results for the given search term(s)!"); diff --git a/tests/build-remote-content-addressed-floating.sh b/tests/build-remote-content-addressed-floating.sh index 1f474dde0..e83b42b41 100644 --- a/tests/build-remote-content-addressed-floating.sh +++ b/tests/build-remote-content-addressed-floating.sh @@ -2,7 +2,7 @@ source common.sh file=build-hook-ca-floating.nix -enableFeatures "ca-derivations ca-references" +enableFeatures "ca-derivations" CONTENT_ADDRESSED=true diff --git a/tests/build-remote.sh b/tests/build-remote.sh index 094366872..d1da134dc 100644 --- a/tests/build-remote.sh +++ b/tests/build-remote.sh @@ -34,6 +34,14 @@ outPath=$(readlink -f $TEST_ROOT/result) grep 'FOO BAR BAZ' $TEST_ROOT/machine0/$outPath +testPrintOutPath=$(nix build -L -v -f $file --print-out-paths --max-jobs 0 \ + --arg busybox $busybox \ + --store $TEST_ROOT/machine0 \ + --builders "$(join_by '; ' "${builders[@]}")" +) + +[[ $testPrintOutPath =~ store.*build-remote ]] + set -o pipefail # Ensure that input1 was built on store1 due to the required feature. diff --git a/tests/ca/common.sh b/tests/ca/common.sh index b9d415863..b104b5a78 100644 --- a/tests/ca/common.sh +++ b/tests/ca/common.sh @@ -1,5 +1,5 @@ source ../common.sh -enableFeatures "ca-derivations ca-references" +enableFeatures "ca-derivations" restartDaemon diff --git a/tests/ca/selfref-gc.sh b/tests/ca/selfref-gc.sh new file mode 100755 index 000000000..248778894 --- /dev/null +++ b/tests/ca/selfref-gc.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +source common.sh + +requireDaemonNewerThan "2.4pre20210626" + +enableFeatures "ca-derivations nix-command flakes" + +export NIX_TESTS_CA_BY_DEFAULT=1 +cd .. +source ./selfref-gc.sh diff --git a/tests/eval.sh b/tests/eval.sh index 2e5ceb969..d74976019 100644 --- a/tests/eval.sh +++ b/tests/eval.sh @@ -20,6 +20,8 @@ nix eval --expr 'assert 1 + 2 == 3; true' [[ $(nix eval attr --json -f "./eval.nix") == '{"foo":"bar"}' ]] [[ $(nix eval int -f - < "./eval.nix") == 123 ]] +# Check if toFile can be utilized during restricted eval +[[ $(nix eval --restrict-eval --expr 'import (builtins.toFile "source" "42")') == 42 ]] nix-instantiate --eval -E 'assert 1 + 2 == 3; true' [[ $(nix-instantiate -A int --eval "./eval.nix") == 123 ]] diff --git a/tests/flakes-run.sh b/tests/flakes-run.sh new file mode 100644 index 000000000..88fc3e628 --- /dev/null +++ b/tests/flakes-run.sh @@ -0,0 +1,29 @@ +source common.sh + +clearStore +rm -rf $TEST_HOME/.cache $TEST_HOME/.config $TEST_HOME/.local +cp ./shell-hello.nix ./config.nix $TEST_HOME +cd $TEST_HOME + +cat < flake.nix +{ + outputs = {self}: { + packages.$system.pkgAsPkg = (import ./shell-hello.nix).hello; + packages.$system.appAsApp = self.packages.$system.appAsApp; + + apps.$system.pkgAsApp = self.packages.$system.pkgAsPkg; + apps.$system.appAsApp = { + type = "app"; + program = "\${(import ./shell-hello.nix).hello}/bin/hello"; + }; + }; +} +EOF +nix run --no-write-lock-file .#appAsApp +nix run --no-write-lock-file .#pkgAsPkg + +! nix run --no-write-lock-file .#pkgAsApp || fail "'nix run' shouldn’t accept an 'app' defined under 'packages'" +! nix run --no-write-lock-file .#appAsPkg || fail "elements of 'apps' should be of type 'app'" + +clearStore + diff --git a/tests/fmt.sh b/tests/fmt.sh new file mode 100644 index 000000000..bc05118ff --- /dev/null +++ b/tests/fmt.sh @@ -0,0 +1,30 @@ +source common.sh + +set -o pipefail + +clearStore +rm -rf $TEST_HOME/.cache $TEST_HOME/.config $TEST_HOME/.local + +cp ./simple.nix ./simple.builder.sh ./fmt.simple.sh ./config.nix $TEST_HOME + +cd $TEST_HOME + +nix fmt --help | grep "Format" + +cat << EOF > flake.nix +{ + outputs = _: { + formatter.$system = + with import ./config.nix; + mkDerivation { + name = "formatter"; + buildCommand = "mkdir -p \$out/bin; cp \${./fmt.simple.sh} \$out/bin/formatter"; + }; + }; +} +EOF +nix fmt ./file ./folder | grep 'Formatting: ./file ./folder' +nix flake check +nix flake show | grep -P "package 'formatter'" + +clearStore diff --git a/tests/fmt.simple.sh b/tests/fmt.simple.sh new file mode 100755 index 000000000..4c8c67ebb --- /dev/null +++ b/tests/fmt.simple.sh @@ -0,0 +1 @@ +echo Formatting: "${@}" diff --git a/tests/local.mk b/tests/local.mk index 668b34500..e3c4ff4eb 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -1,5 +1,6 @@ nix_tests = \ flakes.sh \ + flakes-run.sh \ ca/gc.sh \ gc.sh \ remote-store.sh \ @@ -79,6 +80,7 @@ nix_tests = \ post-hook.sh \ function-trace.sh \ flake-local-settings.sh \ + fmt.sh \ eval-store.sh \ why-depends.sh \ import-derivation.sh \ @@ -90,6 +92,7 @@ nix_tests = \ plugins.sh \ build.sh \ ca/nix-run.sh \ + selfref-gc.sh ca/selfref-gc.sh \ db-migration.sh \ bash-profile.sh \ pass-as-file.sh \ diff --git a/tests/nix-profile.sh b/tests/nix-profile.sh index a7a4d4fa2..fad62b993 100644 --- a/tests/nix-profile.sh +++ b/tests/nix-profile.sh @@ -3,7 +3,7 @@ source common.sh clearStore clearProfiles -enableFeatures "ca-derivations ca-references" +enableFeatures "ca-derivations" restartDaemon # Make a flake. diff --git a/tests/plugins/plugintest.cc b/tests/plugins/plugintest.cc index cd7c9f8b1..04b791021 100644 --- a/tests/plugins/plugintest.cc +++ b/tests/plugins/plugintest.cc @@ -13,7 +13,7 @@ MySettings mySettings; static GlobalConfig::Register rs(&mySettings); -static void prim_anotherNull (EvalState & state, const Pos & pos, Value ** args, Value & v) +static void prim_anotherNull (EvalState & state, const PosIdx pos, Value ** args, Value & v) { if (mySettings.settingSet) v.mkNull(); diff --git a/tests/post-hook.sh b/tests/post-hook.sh index 049e40749..4eff5f511 100644 --- a/tests/post-hook.sh +++ b/tests/post-hook.sh @@ -9,12 +9,12 @@ echo 'require-sigs = false' >> $NIX_CONF_DIR/nix.conf restartDaemon -# Build the dependencies and push them to the remote store +# Build the dependencies and push them to the remote store. nix-build -o $TEST_ROOT/result dependencies.nix --post-build-hook $PWD/push-to-store.sh clearStore -# Ensure that we the remote store contains both the runtime and buildtime -# closure of what we've just built +# Ensure that the remote store contains both the runtime and build-time +# closure of what we've just built. nix copy --from "$REMOTE_STORE" --no-require-sigs -f dependencies.nix nix copy --from "$REMOTE_STORE" --no-require-sigs -f dependencies.nix input1_drv diff --git a/tests/push-to-store.sh b/tests/push-to-store.sh index 25352c751..b1495c9e2 100755 --- a/tests/push-to-store.sh +++ b/tests/push-to-store.sh @@ -1,6 +1,10 @@ #!/bin/sh set -x +set -e + +[ -n "$OUT_PATHS" ] +[ -n "$DRV_PATH" ] echo Pushing "$OUT_PATHS" to "$REMOTE_STORE" printf "%s" "$DRV_PATH" | xargs nix copy --to "$REMOTE_STORE" --no-require-sigs diff --git a/tests/repl.sh b/tests/repl.sh index 6505f1741..b6937b9e9 100644 --- a/tests/repl.sh +++ b/tests/repl.sh @@ -1,29 +1,37 @@ source common.sh +testDir="$PWD" +cd "$TEST_ROOT" + replCmds=" simple = 1 -simple = import ./simple.nix -:b simple +simple = import $testDir/simple.nix +:bl simple :log simple " replFailingCmds=" -failing = import ./simple-failing.nix +failing = import $testDir/simple-failing.nix :b failing :log failing " replUndefinedVariable=" -import ./undefined-variable.nix +import $testDir/undefined-variable.nix " testRepl () { local nixArgs=("$@") + rm -rf repl-result-out || true # cleanup from other runs backed by a foreign nix store local replOutput="$(nix repl "${nixArgs[@]}" <<< "$replCmds")" echo "$replOutput" local outPath=$(echo "$replOutput" |& grep -o -E "$NIX_STORE_DIR/\w*-simple") nix path-info "${nixArgs[@]}" "$outPath" + [ "$(realpath ./repl-result-out)" == "$outPath" ] || fail "nix repl :bl doesn't make a symlink" + # run it again without checking the output to ensure the previously created symlink gets overwritten + nix repl "${nixArgs[@]}" <<< "$replCmds" || fail "nix repl does not work twice with the same inputs" + # simple.nix prints a PATH during build echo "$replOutput" | grep -qs 'PATH=' || fail "nix repl :log doesn't output logs" local replOutput="$(nix repl "${nixArgs[@]}" <<< "$replFailingCmds" 2>&1)" diff --git a/tests/selfref-gc.sh b/tests/selfref-gc.sh new file mode 100644 index 000000000..3f1f50eea --- /dev/null +++ b/tests/selfref-gc.sh @@ -0,0 +1,30 @@ +source common.sh + +requireDaemonNewerThan "2.6.0pre20211215" + +clearStore + +nix-build --no-out-link -E ' + with import ./config.nix; + + let d1 = mkDerivation { + name = "selfref-gc"; + outputs = [ "out" ]; + buildCommand = " + echo SELF_REF: $out > $out + "; + }; in + + # the only change from d1 is d1 as an (unused) build input + # to get identical store path in CA. + mkDerivation { + name = "selfref-gc"; + outputs = [ "out" ]; + buildCommand = " + echo UNUSED: ${d1} + echo SELF_REF: $out > $out + "; + } +' + +nix-collect-garbage