mirror of
https://github.com/NixOS/nix
synced 2025-06-24 22:11:15 +02:00
libexpr: make "or"-as-variable less bogus
The previous place where OR_KW was inserted into the grammar to allow expressions like "map or [...]" led to a number of weird outcomes. By moving it to expr_simple, expressions using "or" as a variable are now parsed consistently with the rest of the language. Conflicts are prevented by telling Bison that OR_KW has higher precedence than '.'.
This commit is contained in:
parent
aa1629ca35
commit
31431de634
7 changed files with 19 additions and 82 deletions
|
@ -295,7 +295,13 @@ namespace nix {
|
||||||
ASSERT_THAT(*b->value, IsIntEq(1));
|
ASSERT_THAT(*b->value, IsIntEq(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TrivialExpressionTest, orCantBeUsed) {
|
TEST_F(TrivialExpressionTest, orCanBeUsed) {
|
||||||
ASSERT_THROW(eval("let or = 1; in or"), Error);
|
auto v = eval("let or = 1; in or");
|
||||||
|
ASSERT_THAT(v, IsIntEq(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, orHasCorrectPrecedence) {
|
||||||
|
auto v = eval("let inherit (builtins) add; or = 2; in add 1 or");
|
||||||
|
ASSERT_THAT(v, IsIntEq(3));
|
||||||
}
|
}
|
||||||
} /* namespace nix */
|
} /* namespace nix */
|
||||||
|
|
|
@ -96,10 +96,6 @@ struct Expr
|
||||||
virtual void setName(Symbol name);
|
virtual void setName(Symbol name);
|
||||||
virtual void setDocComment(DocComment docComment) { };
|
virtual void setDocComment(DocComment docComment) { };
|
||||||
virtual PosIdx getPos() const { return noPos; }
|
virtual PosIdx getPos() const { return noPos; }
|
||||||
|
|
||||||
// These are temporary methods to be used only in parser.y
|
|
||||||
virtual void resetCursedOr() { };
|
|
||||||
virtual void warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions) { };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#define COMMON_METHODS \
|
#define COMMON_METHODS \
|
||||||
|
@ -361,16 +357,10 @@ struct ExprCall : Expr
|
||||||
Expr * fun;
|
Expr * fun;
|
||||||
std::vector<Expr *> args;
|
std::vector<Expr *> args;
|
||||||
PosIdx pos;
|
PosIdx pos;
|
||||||
std::optional<PosIdx> cursedOrEndPos; // used during parsing to warn about https://github.com/NixOS/nix/issues/11118
|
|
||||||
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args)
|
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args)
|
||||||
: fun(fun), args(args), pos(pos), cursedOrEndPos({})
|
: fun(fun), args(args), pos(pos)
|
||||||
{ }
|
|
||||||
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args, PosIdx && cursedOrEndPos)
|
|
||||||
: fun(fun), args(args), pos(pos), cursedOrEndPos(cursedOrEndPos)
|
|
||||||
{ }
|
{ }
|
||||||
PosIdx getPos() const override { return pos; }
|
PosIdx getPos() const override { return pos; }
|
||||||
virtual void resetCursedOr() override;
|
|
||||||
virtual void warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions) override;
|
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -628,32 +628,4 @@ std::string DocComment::getInnerText(const PosTable & positions) const {
|
||||||
return docStr;
|
return docStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ‘Cursed or’ handling.
|
|
||||||
*
|
|
||||||
* In parser.y, every use of expr_select in a production must call one of the
|
|
||||||
* two below functions.
|
|
||||||
*
|
|
||||||
* To be removed by https://github.com/NixOS/nix/pull/11121
|
|
||||||
*/
|
|
||||||
|
|
||||||
void ExprCall::resetCursedOr()
|
|
||||||
{
|
|
||||||
cursedOrEndPos.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExprCall::warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions)
|
|
||||||
{
|
|
||||||
if (cursedOrEndPos.has_value()) {
|
|
||||||
std::ostringstream out;
|
|
||||||
out << "at " << positions[pos] << ": "
|
|
||||||
"This expression uses `or` as an identifier in a way that will change in a future Nix release.\n"
|
|
||||||
"Wrap this entire expression in parentheses to preserve its current meaning:\n"
|
|
||||||
" (" << positions[pos].getSnippetUpTo(positions[*cursedOrEndPos]).value_or("could not read expression") << ")\n"
|
|
||||||
"Give feedback at https://github.com/NixOS/nix/pull/11121";
|
|
||||||
warn(out.str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,6 +177,9 @@ static Expr * makeCall(PosIdx pos, Expr * fn, Expr * arg) {
|
||||||
%nonassoc '?'
|
%nonassoc '?'
|
||||||
%nonassoc NEGATE
|
%nonassoc NEGATE
|
||||||
|
|
||||||
|
%precedence '.'
|
||||||
|
%precedence OR_KW
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
start: expr {
|
start: expr {
|
||||||
|
@ -269,28 +272,15 @@ expr_op
|
||||||
;
|
;
|
||||||
|
|
||||||
expr_app
|
expr_app
|
||||||
: expr_app expr_select { $$ = makeCall(CUR_POS, $1, $2); $2->warnIfCursedOr(state->symbols, state->positions); }
|
: expr_app expr_select { $$ = makeCall(CUR_POS, $1, $2); }
|
||||||
| /* Once a ‘cursed or’ reaches this nonterminal, it is no longer cursed,
|
| expr_select
|
||||||
because the uncursed parse would also produce an expr_app. But we need
|
|
||||||
to remove the cursed status in order to prevent valid things like
|
|
||||||
`f (g or)` from triggering the warning. */
|
|
||||||
expr_select { $$ = $1; $$->resetCursedOr(); }
|
|
||||||
;
|
;
|
||||||
|
|
||||||
expr_select
|
expr_select
|
||||||
: expr_simple '.' attrpath
|
: expr_simple '.' attrpath
|
||||||
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), nullptr); delete $3; }
|
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), nullptr); delete $3; }
|
||||||
| expr_simple '.' attrpath OR_KW expr_select
|
| expr_simple '.' attrpath OR_KW expr_select
|
||||||
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), $5); delete $3; $5->warnIfCursedOr(state->symbols, state->positions); }
|
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), $5); delete $3; }
|
||||||
| /* Backwards compatibility: because Nixpkgs has a function named ‘or’,
|
|
||||||
allow stuff like ‘map or [...]’. This production is problematic (see
|
|
||||||
https://github.com/NixOS/nix/issues/11118) and will be refactored in the
|
|
||||||
future by treating `or` as a regular identifier. The refactor will (in
|
|
||||||
very rare cases, we think) change the meaning of expressions, so we mark
|
|
||||||
the ExprCall with data (establishing that it is a ‘cursed or’) that can
|
|
||||||
be used to emit a warning when an affected expression is parsed. */
|
|
||||||
expr_simple OR_KW
|
|
||||||
{ $$ = new ExprCall(CUR_POS, $1, {new ExprVar(CUR_POS, state->s.or_)}, state->positions.add(state->origin, @$.endOffset)); }
|
|
||||||
| expr_simple
|
| expr_simple
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -302,6 +292,9 @@ expr_simple
|
||||||
else
|
else
|
||||||
$$ = new ExprVar(CUR_POS, state->symbols.create($1));
|
$$ = new ExprVar(CUR_POS, state->symbols.create($1));
|
||||||
}
|
}
|
||||||
|
| /* Backwards compatibility: because Nixpkgs has a function named ‘or’,
|
||||||
|
allow stuff like ‘map or [...]’. */
|
||||||
|
OR_KW { $$ = new ExprVar(CUR_POS, state->s.or_); }
|
||||||
| INT_LIT { $$ = new ExprInt($1); }
|
| INT_LIT { $$ = new ExprInt($1); }
|
||||||
| FLOAT_LIT { $$ = new ExprFloat($1); }
|
| FLOAT_LIT { $$ = new ExprFloat($1); }
|
||||||
| '"' string_parts '"' { $$ = $2; }
|
| '"' string_parts '"' { $$ = $2; }
|
||||||
|
@ -493,7 +486,7 @@ string_attr
|
||||||
;
|
;
|
||||||
|
|
||||||
expr_list
|
expr_list
|
||||||
: expr_list expr_select { $$ = $1; $1->elems.push_back($2); /* !!! dangerous */; $2->warnIfCursedOr(state->symbols, state->positions); }
|
: expr_list expr_select { $$ = $1; $1->elems.push_back($2); /* !!! dangerous */ }
|
||||||
| { $$ = new ExprList; }
|
| { $$ = new ExprList; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
warning: at /pwd/lang/eval-okay-deprecate-cursed-or.nix:3:47: This expression uses `or` as an identifier in a way that will change in a future Nix release.
|
|
||||||
Wrap this entire expression in parentheses to preserve its current meaning:
|
|
||||||
((x: x) or)
|
|
||||||
Give feedback at https://github.com/NixOS/nix/pull/11121
|
|
||||||
warning: at /pwd/lang/eval-okay-deprecate-cursed-or.nix:4:39: This expression uses `or` as an identifier in a way that will change in a future Nix release.
|
|
||||||
Wrap this entire expression in parentheses to preserve its current meaning:
|
|
||||||
((x: x + 1) or)
|
|
||||||
Give feedback at https://github.com/NixOS/nix/pull/11121
|
|
||||||
warning: at /pwd/lang/eval-okay-deprecate-cursed-or.nix:5:44: This expression uses `or` as an identifier in a way that will change in a future Nix release.
|
|
||||||
Wrap this entire expression in parentheses to preserve its current meaning:
|
|
||||||
((x: x) or)
|
|
||||||
Give feedback at https://github.com/NixOS/nix/pull/11121
|
|
|
@ -1 +0,0 @@
|
||||||
0
|
|
|
@ -1,11 +0,0 @@
|
||||||
let
|
|
||||||
# These are cursed and should warn
|
|
||||||
cursed0 = builtins.length (let or = 1; in [ (x: x) or ]);
|
|
||||||
cursed1 = let or = 1; in (x: x * 2) (x: x + 1) or;
|
|
||||||
cursed2 = let or = 1; in { a = 2; }.a or (x: x) or;
|
|
||||||
|
|
||||||
# These are uses of `or` as an identifier that are not cursed
|
|
||||||
allowed0 = let or = (x: x); in map or [];
|
|
||||||
allowed1 = let f = (x: x); or = f; in f (f or);
|
|
||||||
in
|
|
||||||
0
|
|
Loading…
Add table
Add a link
Reference in a new issue