diff --git a/doc/manual/rl-next/deprecated-features.md b/doc/manual/rl-next/deprecated-features.md index bd3183457..0a72fc82c 100644 --- a/doc/manual/rl-next/deprecated-features.md +++ b/doc/manual/rl-next/deprecated-features.md @@ -1,7 +1,7 @@ --- synopsis: 'more deprecated features' issues: [] -cls: [2092, 2310, 2311, 4638, 4652] +cls: [2092, 2310, 2311, 4638, 4652, 4764] category: Breaking Changes credits: [piegames, commentator2.0] --- @@ -13,3 +13,4 @@ You can opt in into the old behavior with `--extra-deprecated-features` or any e - `floating-without-zero` so far, one was able to declare a float using something like `.123`. This can cause confusion about accessing attributes. Floating point numbers must now always include the leading zero, i.e. `0.123` - `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. diff --git a/lix/libexpr/parser/parser-impl1.inc.cc b/lix/libexpr/parser/parser-impl1.inc.cc index 1dae7d9ad..03d272873 100644 --- a/lix/libexpr/parser/parser-impl1.inc.cc +++ b/lix/libexpr/parser/parser-impl1.inc.cc @@ -324,7 +324,11 @@ struct AttrState : SubexprState { template<> struct BuildAST { static void apply(const auto & in, auto & s, State & ps) { - s.pushAttr(ps.symbols.create(in.string_view()), ps.at(in)); + auto symbol = ps.symbols.create(in.string_view()); + if (!ps.featureSettings.isEnabled(Dep::OrAsIdentifier) && symbol == ps.symbols.sym_or) { + ps.orIdentifierFound(ps.at(in)); + } + s.pushAttr(symbol, ps.at(in)); } }; @@ -956,6 +960,10 @@ template<> struct BuildAST { template<> struct BuildAST { static void apply(const auto & in, SelectState & s, State & ps) { + if (!ps.featureSettings.isEnabled(Dep::OrAsIdentifier)) { + ps.orArgumentFound(ps.at(in)); + } + std::vector> args(1); args[0] = std::make_unique(ps.at(in), ps.symbols.sym_or); s->emplaceExpr(s.pos, s->popExprOnly(), std::move(args)); diff --git a/lix/libexpr/parser/state.hh b/lix/libexpr/parser/state.hh index c0645187c..6bed39143 100644 --- a/lix/libexpr/parser/state.hh +++ b/lix/libexpr/parser/state.hh @@ -42,6 +42,8 @@ struct State void nulFound(const PosIdx pos); void recSetMergeFound(const AttrPath & attrPath, const PosIdx pos); void recSetDynamicAttrFound(const PosIdx pos); + void orIdentifierFound(const PosIdx pos); + void orArgumentFound(const PosIdx pos); void addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_ptr e, const PosIdx pos); void mergeAttrs(AttrPath & attrPath, ExprSet * source, ExprSet * target); void validateLambdaAttrs(AttrsPattern & pattern, PosIdx pos = noPos); @@ -198,6 +200,34 @@ inline void State::recSetDynamicAttrFound(const PosIdx pos) }); } +// Added 2026-01-30 +inline void State::orIdentifierFound(const PosIdx pos) +{ + logWarning({ + .msg = HintFmt( + "using %s as an identifier is deprecated because it cannot be used in most places (try " + "%s). Use %s to disable this warning.", + "or", + "let or = 1; in or", + "--extra-deprecated-features or-as-identifier" + ), + .pos = positions[pos], + }); +} +// Added 2026-01-30 +inline void State::orArgumentFound(const PosIdx pos) +{ + logWarning({ + .msg = HintFmt( + "using %s as an argument is deprecated because it is parsed with the wrong precedence " + "which may cause unexpected behavior. Use %s to disable this warning.", + "or", + "--extra-deprecated-features or-as-identifier" + ), + .pos = positions[pos], + }); +} + inline void State::addAttr(ExprAttrs * attrs, AttrPath && attrPath, std::unique_ptr e, const PosIdx pos) { AttrPath::iterator i; diff --git a/lix/libutil/deprecated-features/or-as-identifier.md b/lix/libutil/deprecated-features/or-as-identifier.md new file mode 100644 index 000000000..d4d9bd608 --- /dev/null +++ b/lix/libutil/deprecated-features/or-as-identifier.md @@ -0,0 +1,15 @@ +--- +name: or-as-identifier +internalName: OrAsIdentifier +timeline: + - date: 2026-01-30 + release: 2.95.0 + cls: [4764] + message: Introduced as a warning. +--- +Back when the `or` operator was introduced, instead of making it a proper keyword, the syntax was adapted in attempt of making it a context-sensitive keyword without disrupting existing code. +This attempt has backfired, because it causes glitches in the operator precedence when a variable is called `or`: +`let or = 1; in [ (x: x) or ]` evaluates to a list with one element, but `let nor = 1; in [ (x: x) nor ]` evaluates to a list with two elements. +These old backwards compatibility hacks are deprecated in favor of treating `or` as a full language keyword. + +To fix this, rename affected attributes or put the attribute name in quotes (`"or"`). diff --git a/lix/libutil/meson.build b/lix/libutil/meson.build index a12cdb52e..39b115c25 100644 --- a/lix/libutil/meson.build +++ b/lix/libutil/meson.build @@ -181,6 +181,7 @@ deprecated_feature_definitions = files( 'deprecated-features/floating-without-zero.md', 'deprecated-features/nix-path-shadow.md', 'deprecated-features/nul-bytes.md', + 'deprecated-features/or-as-identifier.md', 'deprecated-features/rec-set-dynamic-attrs.md', 'deprecated-features/rec-set-merges.md', 'deprecated-features/rec-set-overrides.md', diff --git a/tests/functional2/lang/attrs-or/in-2.nix b/tests/functional2/lang/attrs-or/in-2.nix new file mode 100644 index 000000000..08d034564 --- /dev/null +++ b/tests/functional2/lang/attrs-or/in-2.nix @@ -0,0 +1 @@ +let or = 1; in 1 or diff --git a/tests/functional2/lang/attrs-or/in-3.nix b/tests/functional2/lang/attrs-or/in-3.nix new file mode 100644 index 000000000..ef58b490b --- /dev/null +++ b/tests/functional2/lang/attrs-or/in-3.nix @@ -0,0 +1 @@ +let or = 1; in [ (x: x) or ] diff --git a/tests/functional2/lang/attrs-or/in-4.nix b/tests/functional2/lang/attrs-or/in-4.nix new file mode 100644 index 000000000..3009d5feb --- /dev/null +++ b/tests/functional2/lang/attrs-or/in-4.nix @@ -0,0 +1,8 @@ +let + x = 2; + or = 2; +in + [ + builtins.add 1 x + builtins.add 1 or + ] diff --git a/tests/functional2/lang/attrs-or/in-5.nix b/tests/functional2/lang/attrs-or/in-5.nix new file mode 100644 index 000000000..5674915f3 --- /dev/null +++ b/tests/functional2/lang/attrs-or/in-5.nix @@ -0,0 +1,8 @@ +let + x = 2; + or = 2; +in + [ + [ 1 x ] + [ 1 or ] + ] diff --git a/tests/functional2/lang/attrs-or/in.nix b/tests/functional2/lang/attrs-or/in.nix new file mode 100644 index 000000000..fe4555736 --- /dev/null +++ b/tests/functional2/lang/attrs-or/in.nix @@ -0,0 +1 @@ +let or = 1; in { a = 2; }.a or (x: x) or diff --git a/tests/functional2/lang/attrs-or/parse-okay-2.out.exp b/tests/functional2/lang/attrs-or/parse-okay-2.out.exp new file mode 100644 index 000000000..3c84c4be6 --- /dev/null +++ b/tests/functional2/lang/attrs-or/parse-okay-2.out.exp @@ -0,0 +1,15 @@ +_type: ExprLet +attrs: + or: + _type: ExprLiteral + value: 1 + valueType: Int +body: + _type: ExprCall + args: + - _type: ExprVar + value: or + fun: + _type: ExprLiteral + value: 1 + valueType: Int diff --git a/tests/functional2/lang/attrs-or/parse-okay-3.out.exp b/tests/functional2/lang/attrs-or/parse-okay-3.out.exp new file mode 100644 index 000000000..869f3e3eb --- /dev/null +++ b/tests/functional2/lang/attrs-or/parse-okay-3.out.exp @@ -0,0 +1,19 @@ +_type: ExprLet +attrs: + or: + _type: ExprLiteral + value: 1 + valueType: Int +body: + _type: ExprList + elems: + - _type: ExprCall + args: + - _type: ExprVar + value: or + fun: + _type: ExprLambda + arg: x + body: + _type: ExprVar + value: x diff --git a/tests/functional2/lang/attrs-or/parse-okay-4.out.exp b/tests/functional2/lang/attrs-or/parse-okay-4.out.exp new file mode 100644 index 000000000..37a6f84bc --- /dev/null +++ b/tests/functional2/lang/attrs-or/parse-okay-4.out.exp @@ -0,0 +1,38 @@ +_type: ExprLet +attrs: + or: + _type: ExprLiteral + value: 2 + valueType: Int + x: + _type: ExprLiteral + value: 2 + valueType: Int +body: + _type: ExprList + elems: + - _type: ExprSelect + attrs: + - add + e: + _type: ExprVar + value: builtins + - _type: ExprLiteral + value: 1 + valueType: Int + - _type: ExprVar + value: x + - _type: ExprSelect + attrs: + - add + e: + _type: ExprVar + value: builtins + - _type: ExprCall + args: + - _type: ExprVar + value: or + fun: + _type: ExprLiteral + value: 1 + valueType: Int diff --git a/tests/functional2/lang/attrs-or/parse-okay-5.out.exp b/tests/functional2/lang/attrs-or/parse-okay-5.out.exp new file mode 100644 index 000000000..6521818b4 --- /dev/null +++ b/tests/functional2/lang/attrs-or/parse-okay-5.out.exp @@ -0,0 +1,30 @@ +_type: ExprLet +attrs: + or: + _type: ExprLiteral + value: 2 + valueType: Int + x: + _type: ExprLiteral + value: 2 + valueType: Int +body: + _type: ExprList + elems: + - _type: ExprList + elems: + - _type: ExprLiteral + value: 1 + valueType: Int + - _type: ExprVar + value: x + - _type: ExprList + elems: + - _type: ExprCall + args: + - _type: ExprVar + value: or + fun: + _type: ExprLiteral + value: 1 + valueType: Int diff --git a/tests/functional2/lang/attrs-or/parse-okay.out.exp b/tests/functional2/lang/attrs-or/parse-okay.out.exp new file mode 100644 index 000000000..f5da9cbd9 --- /dev/null +++ b/tests/functional2/lang/attrs-or/parse-okay.out.exp @@ -0,0 +1,29 @@ +_type: ExprLet +attrs: + or: + _type: ExprLiteral + value: 1 + valueType: Int +body: + _type: ExprSelect + attrs: + - a + default: + _type: ExprCall + args: + - _type: ExprVar + value: or + fun: + _type: ExprLambda + arg: x + body: + _type: ExprVar + value: x + e: + _type: ExprSet + attrs: + a: + _type: ExprLiteral + value: 2 + valueType: Int + recursive: false diff --git a/tests/functional2/lang/attrs-or/test.toml b/tests/functional2/lang/attrs-or/test.toml new file mode 100644 index 000000000..ccc7cf871 --- /dev/null +++ b/tests/functional2/lang/attrs-or/test.toml @@ -0,0 +1,9 @@ +[[test]] +runner = "parse-okay" +flags = ["--extra-deprecated-features", "or-as-identifier"] +matrix = true + +[[test]] +name = "warning" +runner = "parse-okay" +matrix = true diff --git a/tests/functional2/lang/attrs-or/warning-2.err.exp b/tests/functional2/lang/attrs-or/warning-2.err.exp new file mode 100644 index 000000000..2b1688091 --- /dev/null +++ b/tests/functional2/lang/attrs-or/warning-2.err.exp @@ -0,0 +1,10 @@ +warning: using or as an identifier is deprecated because it cannot be used in most places (try let or = 1; in or). Use --extra-deprecated-features or-as-identifier to disable this warning. + at /pwd/in.nix:1:5: + 1| let or = 1; in 1 or + | ^ + 2| +warning: using or as an argument is deprecated because it is parsed with the wrong precedence which may cause unexpected behavior. Use --extra-deprecated-features or-as-identifier to disable this warning. + at /pwd/in.nix:1:18: + 1| let or = 1; in 1 or + | ^ + 2| diff --git a/tests/functional2/lang/attrs-or/warning-2.out.exp b/tests/functional2/lang/attrs-or/warning-2.out.exp new file mode 100644 index 000000000..3c84c4be6 --- /dev/null +++ b/tests/functional2/lang/attrs-or/warning-2.out.exp @@ -0,0 +1,15 @@ +_type: ExprLet +attrs: + or: + _type: ExprLiteral + value: 1 + valueType: Int +body: + _type: ExprCall + args: + - _type: ExprVar + value: or + fun: + _type: ExprLiteral + value: 1 + valueType: Int diff --git a/tests/functional2/lang/attrs-or/warning-3.err.exp b/tests/functional2/lang/attrs-or/warning-3.err.exp new file mode 100644 index 000000000..49e1b8806 --- /dev/null +++ b/tests/functional2/lang/attrs-or/warning-3.err.exp @@ -0,0 +1,10 @@ +warning: using or as an identifier is deprecated because it cannot be used in most places (try let or = 1; in or). Use --extra-deprecated-features or-as-identifier to disable this warning. + at /pwd/in.nix:1:5: + 1| let or = 1; in [ (x: x) or ] + | ^ + 2| +warning: using or as an argument is deprecated because it is parsed with the wrong precedence which may cause unexpected behavior. Use --extra-deprecated-features or-as-identifier to disable this warning. + at /pwd/in.nix:1:25: + 1| let or = 1; in [ (x: x) or ] + | ^ + 2| diff --git a/tests/functional2/lang/attrs-or/warning-3.out.exp b/tests/functional2/lang/attrs-or/warning-3.out.exp new file mode 100644 index 000000000..869f3e3eb --- /dev/null +++ b/tests/functional2/lang/attrs-or/warning-3.out.exp @@ -0,0 +1,19 @@ +_type: ExprLet +attrs: + or: + _type: ExprLiteral + value: 1 + valueType: Int +body: + _type: ExprList + elems: + - _type: ExprCall + args: + - _type: ExprVar + value: or + fun: + _type: ExprLambda + arg: x + body: + _type: ExprVar + value: x diff --git a/tests/functional2/lang/attrs-or/warning-4.err.exp b/tests/functional2/lang/attrs-or/warning-4.err.exp new file mode 100644 index 000000000..f297b6867 --- /dev/null +++ b/tests/functional2/lang/attrs-or/warning-4.err.exp @@ -0,0 +1,12 @@ +warning: using or as an identifier is deprecated because it cannot be used in most places (try let or = 1; in or). Use --extra-deprecated-features or-as-identifier to disable this warning. + at /pwd/in.nix:3:5: + 2| x = 2; + 3| or = 2; + | ^ + 4| in +warning: using or as an argument is deprecated because it is parsed with the wrong precedence which may cause unexpected behavior. Use --extra-deprecated-features or-as-identifier to disable this warning. + at /pwd/in.nix:7:24: + 6| builtins.add 1 x + 7| builtins.add 1 or + | ^ + 8| ] diff --git a/tests/functional2/lang/attrs-or/warning-4.out.exp b/tests/functional2/lang/attrs-or/warning-4.out.exp new file mode 100644 index 000000000..37a6f84bc --- /dev/null +++ b/tests/functional2/lang/attrs-or/warning-4.out.exp @@ -0,0 +1,38 @@ +_type: ExprLet +attrs: + or: + _type: ExprLiteral + value: 2 + valueType: Int + x: + _type: ExprLiteral + value: 2 + valueType: Int +body: + _type: ExprList + elems: + - _type: ExprSelect + attrs: + - add + e: + _type: ExprVar + value: builtins + - _type: ExprLiteral + value: 1 + valueType: Int + - _type: ExprVar + value: x + - _type: ExprSelect + attrs: + - add + e: + _type: ExprVar + value: builtins + - _type: ExprCall + args: + - _type: ExprVar + value: or + fun: + _type: ExprLiteral + value: 1 + valueType: Int diff --git a/tests/functional2/lang/attrs-or/warning-5.err.exp b/tests/functional2/lang/attrs-or/warning-5.err.exp new file mode 100644 index 000000000..794cdc300 --- /dev/null +++ b/tests/functional2/lang/attrs-or/warning-5.err.exp @@ -0,0 +1,12 @@ +warning: using or as an identifier is deprecated because it cannot be used in most places (try let or = 1; in or). Use --extra-deprecated-features or-as-identifier to disable this warning. + at /pwd/in.nix:3:5: + 2| x = 2; + 3| or = 2; + | ^ + 4| in +warning: using or as an argument is deprecated because it is parsed with the wrong precedence which may cause unexpected behavior. Use --extra-deprecated-features or-as-identifier to disable this warning. + at /pwd/in.nix:7:13: + 6| [ 1 x ] + 7| [ 1 or ] + | ^ + 8| ] diff --git a/tests/functional2/lang/attrs-or/warning-5.out.exp b/tests/functional2/lang/attrs-or/warning-5.out.exp new file mode 100644 index 000000000..6521818b4 --- /dev/null +++ b/tests/functional2/lang/attrs-or/warning-5.out.exp @@ -0,0 +1,30 @@ +_type: ExprLet +attrs: + or: + _type: ExprLiteral + value: 2 + valueType: Int + x: + _type: ExprLiteral + value: 2 + valueType: Int +body: + _type: ExprList + elems: + - _type: ExprList + elems: + - _type: ExprLiteral + value: 1 + valueType: Int + - _type: ExprVar + value: x + - _type: ExprList + elems: + - _type: ExprCall + args: + - _type: ExprVar + value: or + fun: + _type: ExprLiteral + value: 1 + valueType: Int diff --git a/tests/functional2/lang/attrs-or/warning.err.exp b/tests/functional2/lang/attrs-or/warning.err.exp new file mode 100644 index 000000000..17af810f9 --- /dev/null +++ b/tests/functional2/lang/attrs-or/warning.err.exp @@ -0,0 +1,10 @@ +warning: using or as an identifier is deprecated because it cannot be used in most places (try let or = 1; in or). Use --extra-deprecated-features or-as-identifier to disable this warning. + at /pwd/in.nix:1:5: + 1| let or = 1; in { a = 2; }.a or (x: x) or + | ^ + 2| +warning: using or as an argument is deprecated because it is parsed with the wrong precedence which may cause unexpected behavior. Use --extra-deprecated-features or-as-identifier to disable this warning. + at /pwd/in.nix:1:39: + 1| let or = 1; in { a = 2; }.a or (x: x) or + | ^ + 2| diff --git a/tests/functional2/lang/attrs-or/warning.out.exp b/tests/functional2/lang/attrs-or/warning.out.exp new file mode 100644 index 000000000..f5da9cbd9 --- /dev/null +++ b/tests/functional2/lang/attrs-or/warning.out.exp @@ -0,0 +1,29 @@ +_type: ExprLet +attrs: + or: + _type: ExprLiteral + value: 1 + valueType: Int +body: + _type: ExprSelect + attrs: + - a + default: + _type: ExprCall + args: + - _type: ExprVar + value: or + fun: + _type: ExprLambda + arg: x + body: + _type: ExprVar + value: x + e: + _type: ExprSet + attrs: + a: + _type: ExprLiteral + value: 2 + valueType: Int + recursive: false diff --git a/tests/functional2/lang/attrs/eval-okay-5.err.exp b/tests/functional2/lang/attrs/eval-okay-5.err.exp new file mode 100644 index 000000000..eb65e6535 --- /dev/null +++ b/tests/functional2/lang/attrs/eval-okay-5.err.exp @@ -0,0 +1,12 @@ +warning: using or as an identifier is deprecated because it cannot be used in most places (try let or = 1; in or). Use --extra-deprecated-features or-as-identifier to disable this warning. + at /pwd/in.nix:9:3: + 8| + 9| or = x: y: x || y; + | ^ + 10| +warning: using or as an argument is deprecated because it is parsed with the wrong precedence which may cause unexpected behavior. Use --extra-deprecated-features or-as-identifier to disable this warning. + at /pwd/in.nix:23:11: + 22| # it short-circuits and never runs into the type error + 23| (fold or [] [true false false]) + | ^ + 24| ] diff --git a/tests/functional2/lang/or/in-2.nix b/tests/functional2/lang/or/in-2.nix new file mode 100644 index 000000000..97018fc8f --- /dev/null +++ b/tests/functional2/lang/or/in-2.nix @@ -0,0 +1 @@ +with {}; or diff --git a/tests/functional2/lang/or/in.nix b/tests/functional2/lang/or/in.nix new file mode 100644 index 000000000..d42cd6dab --- /dev/null +++ b/tests/functional2/lang/or/in.nix @@ -0,0 +1 @@ +let or = 1; in or diff --git a/tests/functional2/lang/or/parse-fail-2.err.exp b/tests/functional2/lang/or/parse-fail-2.err.exp new file mode 100644 index 000000000..80f343671 --- /dev/null +++ b/tests/functional2/lang/or/parse-fail-2.err.exp @@ -0,0 +1,5 @@ +error: syntax error, expecting expression + at /pwd/in.nix:1:10: + 1| with {}; or + | ^ + 2| diff --git a/tests/functional2/lang/or/parse-fail.err.exp b/tests/functional2/lang/or/parse-fail.err.exp new file mode 100644 index 000000000..76063b76a --- /dev/null +++ b/tests/functional2/lang/or/parse-fail.err.exp @@ -0,0 +1,10 @@ +warning: using or as an identifier is deprecated because it cannot be used in most places (try let or = 1; in or). Use --extra-deprecated-features or-as-identifier to disable this warning. + at /pwd/in.nix:1:5: + 1| let or = 1; in or + | ^ + 2| +error: syntax error, expecting expression + at /pwd/in.nix:1:16: + 1| let or = 1; in or + | ^ + 2| diff --git a/tests/nixpkgs/eval.nix b/tests/nixpkgs/eval.nix index 10cca95dc..404cfa0a3 100644 --- a/tests/nixpkgs/eval.nix +++ b/tests/nixpkgs/eval.nix @@ -15,6 +15,7 @@ let "broken-string-escape" "rec-set-merges" "rec-set-dynamic-attrs" + "or-as-identifier" ]; in diff --git a/tests/nixpkgs/lib.nix b/tests/nixpkgs/lib.nix index 46112a82a..1f328e105 100644 --- a/tests/nixpkgs/lib.nix +++ b/tests/nixpkgs/lib.nix @@ -24,6 +24,7 @@ let "broken-string-escape" "rec-set-merges" "rec-set-dynamic-attrs" + "or-as-identifier" ]; env.NIX_CONFIG = "extra-deprecated-features = ${concatStringsSep " " deprecatedFeatures}";