libexpr: Require whitespace between certain tokens

Fixes #135, #136

Co-authored-by: Commentator2.0 <lix@crystal-cavern.systems>
Change-Id: Ia1880633c1ee3b9242487fbc30b6d781d88987fb
This commit is contained in:
piegames
2026-01-31 20:44:34 +01:00
co-authored by Commentator2.0
parent 98d0215ca2
commit 7e68f93ed7
87 changed files with 1152 additions and 11 deletions
@@ -14,3 +14,4 @@ You can opt in into the old behavior with `--extra-deprecated-features` or any e
- `rec-set-merges` Attribute sets like `{ foo = {}; foo.bar = 42;}` implicitly merge at parse time, however if one of them is marked as recursive but not the others then the recursive attribute may get lost (order-dependent). Therefore, merging attrs with mixed-`rec` is now forbidden.
- `rec-set-dynamic-attrs` Dynamic attributes have weird semantics in the presence of recursive attrsets (they evaluate *after* the rest of the set). This is now forbidden.
- `or-as-identifier` `or` as an identifier has always been weird since the `or` (almost-)keyword has been introduced. We are deprecating the backcompat hacks from the early days of Nix in favor of making `or` a full and proper keyword.
- `tokens-no-whitespace` Function applications without space around the arguments like `0a`, `0.00.0` or `foo"1"2` are now forbidden. The same applies to list elements. The primary reason for this deprecation is to remove foot guns around surprising tokenization rules regarding number literals, but this will also free up some syntax for other purposes (e.g. `r""` strings) for reuse at some point in the future.
+28 -5
View File
@@ -12,6 +12,8 @@
// eolf rules in favor of reproducing the old flex lexer as faithfully as
// possible, and deferring calculation of positions to downstream users.
/* clang-format absolutely butchers the template arguments */
// clang-format off
namespace nix::parser::grammar::v1 {
using namespace tao::pegtl;
@@ -154,13 +156,20 @@ struct identifier : _not_at_any_keyword<
star<c::id_rest>
> {};
struct _integer {
// Add a special case to specifically catch and forbid `0.a`, which otherwise parses as selection
// TODO this is only an artifact of `0.` not being a float, and should be eliminated once that is corrected
struct at_dot_id : success {};
};
// floats may extend ints, thus these rules are very similar.
struct integer : seq<
struct integer : _integer, seq<
sor<
seq<range<'1', '9'>, star<digit>, not_at<one<'.'>>>,
seq<one<'0'>, not_at<one<'.'>, digit>, star<digit>>
>,
not_at<_extend_as_path>
not_at<_extend_as_path>,
opt<at<seq<one<'.'>, sor<c::id_first, one<'"'>>>>, _integer::at_dot_id> // This will be not_at in the future once it becomes a hard error
> {};
struct _floating {
@@ -169,6 +178,7 @@ struct _floating {
struct floating : _floating, seq<
sor<
seq<range<'1', '9'>, star<digit>, one<'.'>, star<digit>>,
// TODO once the deprecations don't have an opt-out anymore, parse `0.` like `1.` by using `star<digit>`
seq<one<'0'>, one<'.'>, plus<digit>>,
_floating::no_leading_zero
>,
@@ -536,14 +546,26 @@ struct _expr {
must<one<']'>>
> {};
struct _simple : sor<
struct _simple {
struct noseps : semantic, success {};
// the subset of `simple` that may not directly follow each other without whitespace as a token boundary
struct token_simple : sor<
id,
int_,
float_,
string,
ind_string,
path,
uri,
uri
> { };
};
struct simple : _simple, sor<
seq<
_simple::token_simple,
// error if we have <token_simple><token_simple> without whitespace in-between
opt<at<_simple::token_simple>, _simple::noseps>
>,
seq<one<'('>, seps, must<expr>, seps, must<one<')'>>>,
ancient_let,
rec_set,
@@ -552,7 +574,7 @@ struct _expr {
> {};
struct _select {
struct head : _simple {};
struct head : simple {};
struct attr : semantic, seq<attrpath> {};
struct attr_or : semantic, must<select> {};
struct as_app_or : semantic, t::kw_or {};
@@ -793,3 +815,4 @@ public:
}
};
}
// clang-format on
+22
View File
@@ -294,6 +294,17 @@ template<> struct BuildAST<grammar::v1::formals> : change_head<FormalsState> {
}
};
template<>
struct BuildAST<grammar::v1::expr::simple::noseps>
{
static void apply(const auto & in, auto &, State & ps)
{
if (!ps.featureSettings.isEnabled(Dep::TokensNoWhitespace)) {
ps.whitespaceBetweenTokensRequired(ps.at(in));
}
}
};
template<> struct BuildAST<grammar::v1::expr::lambda::arg> {
static void apply(const auto & in, auto & s, State & ps) {
s.pattern.name = ps.symbols.create(in.string_view());
@@ -521,6 +532,17 @@ template<> struct BuildAST<grammar::v1::expr::int_> {
}
};
template<>
struct BuildAST<grammar::v1::t::integer::at_dot_id>
{
static void apply(const auto & in, auto &, State & ps)
{
if (!ps.featureSettings.isEnabled(Dep::TokensNoWhitespace)) {
ps.whitespaceBetweenTokensRequired(ps.at(in));
}
}
};
template<> struct BuildAST<grammar::v1::t::floating::no_leading_zero> {
static void apply(const auto & in, ExprState & s, State & ps) {
if (!ps.featureSettings.isEnabled(Dep::FloatingWithoutZero)) {
+14
View File
@@ -44,6 +44,7 @@ struct State
void recSetDynamicAttrFound(const PosIdx pos);
void orIdentifierFound(const PosIdx pos);
void orArgumentFound(const PosIdx pos);
void whitespaceBetweenTokensRequired(const PosIdx pos);
void addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_ptr<Expr> e, const PosIdx pos);
void mergeAttrs(AttrPath & attrPath, ExprSet * source, ExprSet * target);
void validateLambdaAttrs(AttrsPattern & pattern, PosIdx pos = noPos);
@@ -228,6 +229,19 @@ inline void State::orArgumentFound(const PosIdx pos)
});
}
// Added 2026-01-30
inline void State::whitespaceBetweenTokensRequired(const PosIdx pos)
{
throw ParseError(
{.msg = HintFmt(
"whitespace between function arguments or list elements is required here. Use %s to "
"disable this error",
"--extra-deprecated-features tokens-no-whitespace"
),
.pos = positions[pos]}
);
}
inline void State::addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_ptr<Expr> e, const PosIdx pos)
{
AttrPath::iterator i;
@@ -0,0 +1,18 @@
---
name: tokens-no-whitespace
internalName: TokensNoWhitespace
timeline:
- date: 2026-01-30
release: 2.95.0
cls: [4782]
message: Introduced as parser error.
---
The grammar has several bugs around token boundaries, where two adjacent literals are not always required to have whitespace between them: `0a`, `0.00.0` or `foo"1"2`.
This results in the following unexpected behaviors in the language:
- In lists, adjacent tokens will be parsed as several distinct elements, but outside of lists they will be parsed as a function application.
- Because leading zeroes in floating point literals are not allowed, `00012.3` unexpectedly parses as `12 0.3`.
- Nix does not have hexadecimal number literal notation, but a user naively typing `0x10` will not receive a parser error (because it validly parses as `0 x10` instead).
Because the parser cannot be fixed without introducing breaking changes to the language, all token sequences with known confusing semantics are deprecated with a parse error.
To fix this, insert whitespace between tokens to properly separate them.
+1
View File
@@ -186,6 +186,7 @@ deprecated_feature_definitions = files(
'deprecated-features/rec-set-merges.md',
'deprecated-features/rec-set-overrides.md',
'deprecated-features/shadow-internal-symbols.md',
'deprecated-features/tokens-no-whitespace.md',
'deprecated-features/url-literals.md',
# keep-sorted end
)
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
}
],
"fun": {
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
}
}
}
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
}
],
"fun": {
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
}
}
}
@@ -0,0 +1,4 @@
warning: Found floating point literal without leading zero. To fix this warning, add a zero before the dot. Use --extra-deprecated-feature floating-without-zero to silence this warning
at «string»:1:14:
1| with {}; ((a).0)
| ^
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprLiteral",
"value": 0.0,
"valueType": "Float"
}
],
"fun": {
"_type": "ExprVar",
"value": "a"
}
}
}
@@ -0,0 +1,4 @@
warning: Found floating point literal without leading zero. To fix this warning, add a zero before the dot. Use --extra-deprecated-feature floating-without-zero to silence this warning
at «string»:1:14:
1| with {}; ((a).0)
| ^
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprLiteral",
"value": 0.0,
"valueType": "Float"
}
],
"fun": {
"_type": "ExprVar",
"value": "a"
}
}
}
@@ -0,0 +1,17 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprSelect",
"attrs": [
"a"
],
"e": {
"_type": "ExprVar",
"value": "a"
}
}
}
@@ -0,0 +1,17 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprSelect",
"attrs": [
"a"
],
"e": {
"_type": "ExprVar",
"value": "a"
}
}
}
@@ -0,0 +1,18 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprSelect",
"attrs": [
""
],
"e": {
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
}
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:12:
1| with {}; (0."")
| ^
@@ -0,0 +1,4 @@
error: syntax error, expecting ')'
at «string»:1:12:
1| with {}; (0.)
| ^
@@ -0,0 +1,4 @@
error: syntax error, expecting ')'
at «string»:1:12:
1| with {}; (0.)
| ^
@@ -0,0 +1,4 @@
warning: Found floating point literal without leading zero. To fix this warning, add a zero before the dot. Use --extra-deprecated-feature floating-without-zero to silence this warning
at «string»:1:14:
1| with {}; (0.0.0)
| ^
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprLiteral",
"value": 0.0,
"valueType": "Float"
}
],
"fun": {
"_type": "ExprLiteral",
"value": 0.0,
"valueType": "Float"
}
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:14:
1| with {}; (0.0.0)
| ^
@@ -0,0 +1,18 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprSelect",
"attrs": [
"a"
],
"e": {
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
}
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:12:
1| with {}; (0.a)
| ^
@@ -0,0 +1,4 @@
error: syntax error, expecting ')'
at «string»:1:13:
1| with {}; (00.)
| ^
@@ -0,0 +1,4 @@
error: syntax error, expecting ')'
at «string»:1:13:
1| with {}; (00.)
| ^
@@ -0,0 +1,4 @@
warning: Found floating point literal without leading zero. To fix this warning, add a zero before the dot. Use --extra-deprecated-feature floating-without-zero to silence this warning
at «string»:1:16:
1| with {}; (00012.3)
| ^
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprLiteral",
"value": 0.3,
"valueType": "Float"
}
],
"fun": {
"_type": "ExprLiteral",
"value": 12,
"valueType": "Int"
}
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:16:
1| with {}; (00012.3)
| ^
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprVar",
"value": "a"
}
],
"fun": {
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
}
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:12:
1| with {}; (0a)
| ^
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprLiteral",
"value": "https://a",
"valueType": "String"
}
],
"fun": {
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
}
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:12:
1| with {}; (0https://a)
| ^
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprVar",
"value": "x10"
}
],
"fun": {
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
}
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:12:
1| with {}; (0x10)
| ^
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprLiteral",
"value": "",
"valueType": "String"
}
],
"fun": {
"_type": "ExprLiteral",
"value": 1.0,
"valueType": "Float"
}
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:13:
1| with {}; (1."")
| ^
@@ -0,0 +1,12 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprLiteral",
"value": 1.0,
"valueType": "Float"
}
}
@@ -0,0 +1,12 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprLiteral",
"value": 1.0,
"valueType": "Float"
}
}
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprVar",
"value": "a"
}
],
"fun": {
"_type": "ExprLiteral",
"value": 1.0,
"valueType": "Float"
}
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:13:
1| with {}; (1.a)
| ^
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprLiteral",
"value": "",
"valueType": "String"
}
],
"fun": {
"_type": "ExprVar",
"value": "a"
}
}
}
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprLiteral",
"value": "",
"valueType": "String"
}
],
"fun": {
"_type": "ExprVar",
"value": "a"
}
}
}
@@ -0,0 +1,26 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprCall",
"args": [
{
"_type": "ExprLiteral",
"value": "1",
"valueType": "String"
},
{
"_type": "ExprLiteral",
"value": 2,
"valueType": "Int"
}
],
"fun": {
"_type": "ExprVar",
"value": "foo"
}
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:14:
1| with {}; (foo"1"2)
| ^
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
},
{
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
}
]
}
}
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
},
{
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
}
]
}
}
@@ -0,0 +1,4 @@
warning: Found floating point literal without leading zero. To fix this warning, add a zero before the dot. Use --extra-deprecated-feature floating-without-zero to silence this warning
at «string»:1:14:
1| with {}; [(a).0]
| ^
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprVar",
"value": "a"
},
{
"_type": "ExprLiteral",
"value": 0.0,
"valueType": "Float"
}
]
}
}
@@ -0,0 +1,4 @@
warning: Found floating point literal without leading zero. To fix this warning, add a zero before the dot. Use --extra-deprecated-feature floating-without-zero to silence this warning
at «string»:1:14:
1| with {}; [(a).0]
| ^
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprVar",
"value": "a"
},
{
"_type": "ExprLiteral",
"value": 0.0,
"valueType": "Float"
}
]
}
}
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprSelect",
"attrs": [
"a"
],
"e": {
"_type": "ExprVar",
"value": "a"
}
}
]
}
}
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprSelect",
"attrs": [
"a"
],
"e": {
"_type": "ExprVar",
"value": "a"
}
}
]
}
}
@@ -0,0 +1,23 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprSelect",
"attrs": [
""
],
"e": {
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
}
}
]
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:12:
1| with {}; [0.""]
| ^
@@ -0,0 +1,4 @@
warning: Found floating point literal without leading zero. To fix this warning, add a zero before the dot. Use --extra-deprecated-feature floating-without-zero to silence this warning
at «string»:1:14:
1| with {}; [0.0.0]
| ^
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprLiteral",
"value": 0.0,
"valueType": "Float"
},
{
"_type": "ExprLiteral",
"value": 0.0,
"valueType": "Float"
}
]
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:14:
1| with {}; [0.0.0]
| ^
@@ -0,0 +1,4 @@
error: syntax error, expecting ']'
at «string»:1:12:
1| with {}; [0.]
| ^
@@ -0,0 +1,4 @@
error: syntax error, expecting ']'
at «string»:1:12:
1| with {}; [0.]
| ^
@@ -0,0 +1,23 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprSelect",
"attrs": [
"a"
],
"e": {
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
}
}
]
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:12:
1| with {}; [0.a]
| ^
@@ -0,0 +1,4 @@
error: syntax error, expecting ']'
at «string»:1:13:
1| with {}; [00.]
| ^
@@ -0,0 +1,4 @@
error: syntax error, expecting ']'
at «string»:1:13:
1| with {}; [00.]
| ^
@@ -0,0 +1,4 @@
warning: Found floating point literal without leading zero. To fix this warning, add a zero before the dot. Use --extra-deprecated-feature floating-without-zero to silence this warning
at «string»:1:16:
1| with {}; [00012.3]
| ^
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprLiteral",
"value": 12,
"valueType": "Int"
},
{
"_type": "ExprLiteral",
"value": 0.3,
"valueType": "Float"
}
]
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:16:
1| with {}; [00012.3]
| ^
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
},
{
"_type": "ExprVar",
"value": "a"
}
]
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:12:
1| with {}; [0a]
| ^
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
},
{
"_type": "ExprLiteral",
"value": "https://a",
"valueType": "String"
}
]
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:12:
1| with {}; [0https://a]
| ^
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprLiteral",
"value": 0,
"valueType": "Int"
},
{
"_type": "ExprVar",
"value": "x10"
}
]
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:12:
1| with {}; [0x10]
| ^
@@ -0,0 +1,22 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprLiteral",
"value": 1.0,
"valueType": "Float"
},
{
"_type": "ExprLiteral",
"value": "",
"valueType": "String"
}
]
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:13:
1| with {}; [1.""]
| ^
@@ -0,0 +1,17 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprLiteral",
"value": 1.0,
"valueType": "Float"
}
]
}
}
@@ -0,0 +1,17 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprLiteral",
"value": 1.0,
"valueType": "Float"
}
]
}
}
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprLiteral",
"value": 1.0,
"valueType": "Float"
},
{
"_type": "ExprVar",
"value": "a"
}
]
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:13:
1| with {}; [1.a]
| ^
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprVar",
"value": "a"
},
{
"_type": "ExprLiteral",
"value": "",
"valueType": "String"
}
]
}
}
@@ -0,0 +1,21 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprVar",
"value": "a"
},
{
"_type": "ExprLiteral",
"value": "",
"valueType": "String"
}
]
}
}
@@ -0,0 +1,26 @@
{
"_type": "ExprWith",
"attrs": {
"_type": "ExprSet",
"recursive": false
},
"body": {
"_type": "ExprList",
"elems": [
{
"_type": "ExprVar",
"value": "foo"
},
{
"_type": "ExprLiteral",
"value": "1",
"valueType": "String"
},
{
"_type": "ExprLiteral",
"value": 2,
"valueType": "Int"
}
]
}
}
@@ -0,0 +1,4 @@
error: whitespace between function arguments or list elements is required here. Use --extra-deprecated-features tokens-no-whitespace to disable this error
at «string»:1:14:
1| with {}; [foo"1"2]
| ^
@@ -0,0 +1,80 @@
from pathlib import Path
from collections.abc import Callable
import pytest
from testlib.fixtures.nix import Nix
from testlib.fixtures.snapshot import Snapshot
from testlib.utils import functional2_base_folder
@pytest.mark.parametrize(
("expr", "exit_code", "exit_code_depr"),
[
("00012.3", 1, 0),
("0a", 1, 0),
("0https://a", 1, 0),
("0.0.0", 1, 0),
("""foo"1"2""", 1, 0),
("0x10", 1, 0),
("0.", 1, 1),
("1.", 0, 0),
("0.a", 1, 0),
("1.a", 1, 0),
(""" 0."" """, 1, 0),
(""" 1."" """, 1, 0),
# test against false positives
("(0)(0)", 0, 0),
('a("")', 0, 0),
("(a).a", 0, 0),
# not deprecated atm but probably should be in the future (FIXME piegames; 2026-01-30)
("(a).0", 0, 0),
# unrelated syntax errors which don't trigger the deprecation
("00.", 1, 1),
],
)
def test_whitespace(
nix: Nix,
expr: str,
exit_code: int,
exit_code_depr: int,
snapshot: Callable[[str], Snapshot],
files: Path,
):
expr = expr.strip()
for i, expr in enumerate((f"({expr})", f"[{expr}]")):
f_expr = expr.replace("/", "-")
for f in [
f"{f_expr}.out.exp",
f"{f_expr}.err.exp",
f"{f_expr}-depr.out.exp",
f"{f_expr}-depr.err.exp",
]:
(files / f).symlink_to(functional2_base_folder / "lang" / "parser-token-whitespace" / f)
full_expr = f"with {{}}; {expr}"
res = (
nix.nix_instantiate(
["--parse", "--extra-deprecated-features", "url-literals", "-E", full_expr]
)
.run()
.expect(exit_code)
)
assert snapshot(f"{f_expr}.out.exp") == res.stdout_s
assert snapshot(f"{f_expr}.err.exp") == res.stderr_s
res = (
nix.nix_instantiate(
[
"--parse",
"--extra-deprecated-features",
"tokens-no-whitespace url-literals",
"-E",
full_expr,
]
)
.run()
.expect(exit_code_depr)
)
assert snapshot(f"{f_expr}-depr.out.exp") == res.stdout_s
assert snapshot(f"{f_expr}-depr.err.exp") == res.stderr_s
+8
View File
@@ -0,0 +1,8 @@
[[test]]
runner = "parse-fail"
in = "in.nix"
flags = ["--extra-deprecated-features", "tokens-no-whitespace"]
[[test]]
runner = "eval-fail"
in = "in-2.nix"
+1
View File
@@ -16,6 +16,7 @@ let
"rec-set-merges"
"rec-set-dynamic-attrs"
"or-as-identifier"
"tokens-no-whitespace"
];
in
+1
View File
@@ -25,6 +25,7 @@ let
"rec-set-merges"
"rec-set-dynamic-attrs"
"or-as-identifier"
"tokens-no-whitespace"
];
env.NIX_CONFIG = "extra-deprecated-features = ${concatStringsSep " " deprecatedFeatures}";