diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 030c0085b..24efe15ab 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1366,8 +1366,11 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) if (state.countCalls) state.attrSelects[posCurrent]++; } - state.forceValue(*vCurrent, (posCurrent ? posCurrent : this->pos)); + // Successfully made it to the end of the chain + posCurrent = posCurrent ? posCurrent : this->pos; + state.forceValue(*vCurrent, posCurrent); + v = *vCurrent; } catch (Error & e) { if (posCurrent) { auto pos2r = state.positions[posCurrent]; @@ -1378,10 +1381,83 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) } throw; } - - v = *vCurrent; } +// nix-lang v2 exclusive +void ExprSlice::eval(EvalState & state, Env & env, Value & v) +{ + Value toSlice; + e->eval(state, env, toSlice); + + // Can only slice into attrsets + if (toSlice.type() != nAttrs) { + state.error( + "expected a set but found %s: %s", + showType(toSlice), + ValuePrinter(state, toSlice, errorPrintOptions) + ).addTrace( + getPos(), + HintFmt("while slicing an attribute set") + ).debugThrow(); + } + + if (this->sliceSet) { + BindingsBuilder builder = state.buildBindings(this->slice.size()); + + for (auto & [name, pos] : this->slice) { + Bindings::iterator attrIt = toSlice.attrs->find(name); + + // Item not found, throw missing attr error + if (attrIt == toSlice.attrs->end()) { + std::set allAttrNames; + for (auto const & attr : *toSlice.attrs) { + allAttrNames.insert(state.symbols[attr.name]); + } + auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]); + state.error("attribute '%s' missing", state.symbols[name]) + .addTrace( + getPos(), + HintFmt("while slicing an attribute set into a subset") + ) + .atPos(pos) + .withSuggestions(suggestions) + .withFrame(env, *this) + .debugThrow(); + } + + builder.insert(*attrIt); + } + + v.mkAttrs(builder); + } else { + state.mkList(v, this->slice.size()); + + for (auto [n, v2] : enumerate(v.listItems())) { + auto [name, pos] = this->slice[n]; + Bindings::iterator attrIt = toSlice.attrs->find(name); + + // Item not found, throw missing attr error + if (attrIt == toSlice.attrs->end()) { + std::set allAttrNames; + for (auto const & attr : *toSlice.attrs) { + allAttrNames.insert(state.symbols[attr.name]); + } + auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]); + state.error("attribute '%s' missing", state.symbols[name]) + .addTrace( + getPos(), + HintFmt("while slicing an attribute set into a list") + ) + .atPos(pos) + .withSuggestions(suggestions) + .withFrame(env, *this) + .debugThrow(); + } + + const_cast(v2) = attrIt->value; + } + } +} void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v) { diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 863b74227..0879e933c 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -82,6 +82,29 @@ void ExprSelect::show(const SymbolTable & symbols, std::ostream & str) const } } +void ExprSlice::show(const SymbolTable & symbols, std::ostream & str) const +{ + str << "("; + e->show(symbols, str); + str << ")."; + + if (sliceSet) { + str << "{ "; + } else { + str << "[ "; + } + for (auto & s : slice) { + str << "("; + str << symbols[s.first]; + str << ") "; + } + if (sliceSet) { + str << "}"; + } else { + str << "]"; + } +} + void ExprOpHasAttr::show(const SymbolTable & symbols, std::ostream & str) const { str << "(("; @@ -375,6 +398,14 @@ void ExprSelect::bindVars(EvalState & es, const std::shared_ptr i.expr->bindVars(es, env); } +void ExprSlice::bindVars(EvalState & es, const std::shared_ptr & env) +{ + if (es.debugRepl) + es.exprEnvs.insert(std::make_pair(this, env)); + + e->bindVars(es, env); +} + void ExprOpHasAttr::bindVars(EvalState & es, const std::shared_ptr & env) { if (es.debugRepl) diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 6f0190e7f..160fb249d 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -191,6 +191,25 @@ struct ExprSelect : Expr COMMON_METHODS }; +// nix-lang v2 exclusive +struct ExprSlice : Expr +{ + PosIdx pos; + + /** The expression attributes are being sliced. e.g. `foo` in `foo.[ bar baz ]`. */ + std::unique_ptr e; + + /** Symbols to extract out of `e` */ + std::vector> slice; + + /** Whether to slice into a set instead of a list */ + bool sliceSet = false; + + ExprSlice(const PosIdx & pos, std::unique_ptr e) : pos(pos), e(std::move(e)) {} + PosIdx getPos() const override { return pos; } + COMMON_METHODS +}; + struct ExprOpHasAttr : Expr { std::unique_ptr e; diff --git a/src/libexpr/parser/grammar2.hh b/src/libexpr/parser/grammar2.hh index 7b6b548c5..a6fa7c38a 100644 --- a/src/libexpr/parser/grammar2.hh +++ b/src/libexpr/parser/grammar2.hh @@ -464,9 +464,22 @@ struct _expr { > {}; struct _select { + /* Elements of the slices */ + struct slice_items : p::list { }; + struct head : _simple {}; struct attr : semantic, seq {}; struct attr_or : semantic, must