diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index f1ff3a6e082b..8163ed89fc81 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1283,9 +1283,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.hasFormals() ? lambda.formals->formals.size() : 0); + auto size = lambda.getEnvSize(); Env & env2(allocEnv(size)); env2.up = vCur.lambda.env; @@ -1326,6 +1324,21 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name); abort(); // can't happen } + + if (lambda.formals->ellipsis && lambda.formals->ellipsis->set()) { + Value *otherAttrs = new Value(); + Bindings *bindings = new Bindings(args[0]->attrs->size()); + + otherAttrs->mkAttrs(bindings); + + for (auto &i : *args[0]->attrs) { + if (lambda.formals->argNames.find(i.name) == + lambda.formals->argNames.end()) { + otherAttrs->attrs->push_back(i); + } + } + env2.values[displ++] = otherAttrs; + } } nrFunctionCalls++; diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 57c2f6e447a2..ed6cfb33a278 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -135,6 +135,10 @@ void ExprLambda::show(std::ostream & str) const if (formals->ellipsis) { if (!first) str << ", "; str << "..."; + if (formals->ellipsis->set()) { + str << " @ "; + str << *formals->ellipsis; + } } str << " }"; if (!arg.empty()) str << " @ "; @@ -354,10 +358,7 @@ void ExprList::bindVars(const StaticEnv & env) void ExprLambda::bindVars(const StaticEnv & env) { - StaticEnv newEnv( - false, &env, - (hasFormals() ? formals->formals.size() : 0) + - (arg.empty() ? 0 : 1)); + StaticEnv newEnv(false, &env, getEnvSize()); Displacement displ = 0; @@ -367,6 +368,10 @@ void ExprLambda::bindVars(const StaticEnv & env) for (auto & i : formals->formals) newEnv.vars.emplace_back(i.name, displ++); + if (formals->ellipsis->set()) { + newEnv.vars.emplace_back(*formals->ellipsis, displ++); + } + newEnv.sort(); for (auto & i : formals->formals) @@ -460,6 +465,13 @@ void ExprLambda::setName(Symbol & name) body->setName(name); } +size_t ExprLambda::getEnvSize() { + size_t s = 0; + s += hasFormals() ? formals->formals.size() : 0; + s += hasFormals() && formals->ellipsis && formals->ellipsis->set() ? 1 : 0; + s += arg.empty() ? 0 : 1; + return s; +} string ExprLambda::showNamePos() const { diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 13256272cb51..ba0479751b86 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -224,9 +224,10 @@ struct Formal struct Formals { typedef std::list Formals_; + typedef std::optional Ellipsis; Formals_ formals; std::set argNames; // used during parsing - bool ellipsis; + Ellipsis ellipsis; }; struct ExprLambda : Expr @@ -247,6 +248,7 @@ struct ExprLambda : Expr }; void setName(Symbol & name); string showNamePos() const; + size_t getEnvSize(); inline bool hasFormals() const { return formals != nullptr; } COMMON_METHODS }; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 923997bf63d0..2fbde6e00f76 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -157,6 +157,24 @@ static void addFormal(const Pos & pos, Formals * formals, const Formal & formal) formals->formals.push_front(formal); } +static void setEllipsis(const Pos &, Formals * formals, bool ellipsis) +{ + if (ellipsis) formals->ellipsis.emplace(); + else formals->ellipsis.reset(); +} + +static void setEllipsis(const Pos & pos, Formals * formals, Symbol name) { + setEllipsis(pos, formals, true); + formals->ellipsis.emplace(name); + // Check that the symbol we use is not also a formal argument: + settings.requireExperimentalFeature(Xp::NamedEllipsis); + if (!formals->argNames.insert(name).second) + throw ParseError({ + .msg = hintfmt("duplicate formal function argument '%1%'", name), + .errPos = pos + }); +} + static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, vector & es) { @@ -570,11 +588,13 @@ formals : formal ',' formals { $$ = $3; addFormal(CUR_POS, $$, *$1); } | formal - { $$ = new Formals; addFormal(CUR_POS, $$, *$1); $$->ellipsis = false; } + { $$ = new Formals; addFormal(CUR_POS, $$, *$1); setEllipsis(CUR_POS, $$, false); } | - { $$ = new Formals; $$->ellipsis = false; } + { $$ = new Formals; setEllipsis(CUR_POS, $$, false); } + | ELLIPSIS '@' ID + { $$ = new Formals; setEllipsis(CUR_POS, $$, data->symbols.create($3)); } | ELLIPSIS - { $$ = new Formals; $$->ellipsis = true; } + { $$ = new Formals; setEllipsis(CUR_POS, $$, true); } ; formal diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index b49f47e1d45b..e82df17fd174 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -11,6 +11,7 @@ std::map stringifiedXpFeatures = { { Xp::NixCommand, "nix-command" }, { Xp::RecursiveNix, "recursive-nix" }, { Xp::NoUrlLiterals, "no-url-literals" }, + { Xp::NamedEllipsis, "named-ellipsis" }, }; const std::optional parseExperimentalFeature(const std::string_view & name) diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index 291a58e32dd7..f6c829b23aa1 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -19,7 +19,8 @@ enum struct ExperimentalFeature Flakes, NixCommand, RecursiveNix, - NoUrlLiterals + NoUrlLiterals, + NamedEllipsis, }; /**