libexpr: Deprecate overriding __sub and the like
It was never intended to be a feature to be used, and moreover it is
inconsistent: One cannot override `+`, and overriding `__lessThan` won't
affect the builtins which do comparisons.
Change-Id: Iaba54a05aa4c2eb37cdb3dc0d731fcee5a86deba
This commit is contained in:
parent
66f6dbda32
commit
81d5f0a7d9
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
synopsis: Deprecated language features
|
synopsis: Deprecated language features
|
||||||
issues: [fj#437]
|
issues: [fj#437, 861]
|
||||||
cls: [1785, 1736, 1735, 1744]
|
cls: [1785, 1736, 1735, 1744, 2206]
|
||||||
category: Breaking Changes
|
category: Breaking Changes
|
||||||
credits: [piegames, horrors]
|
credits: [piegames, horrors]
|
||||||
---
|
---
|
||||||
|
@ -15,3 +15,4 @@ and can be disabled via the flags for backwards compatibility (opt-out with `--e
|
||||||
- `rec-set-overrides`: **__overrides** is an old arcane syntax which has not been in use for more than a decade.
|
- `rec-set-overrides`: **__overrides** is an old arcane syntax which has not been in use for more than a decade.
|
||||||
It is soft-deprecated with a warning only, with the plan to turn that into an error in a future release.
|
It is soft-deprecated with a warning only, with the plan to turn that into an error in a future release.
|
||||||
- `ancient-let`: **The old `let` syntax** (`let { body = …; … }`) is soft-deprecated with a warning as well. Use the regular `let … in` instead.
|
- `ancient-let`: **The old `let` syntax** (`let { body = …; … }`) is soft-deprecated with a warning as well. Use the regular `let … in` instead.
|
||||||
|
- `shadow-internal-symbols`: Arithmetic expressions like `5 - 3` internally expand to `__sub 5 3`, where `__sub` maps to a subtraction builtin. Shadowing such a symbols would affect the evaluation of such operations, but in a very inconsistent way, and is therefore deprecated now. **Affected symbols are:** `__sub`, `__mul`, `__div`, `__lessThan`, `__findFile` and `__nixPath`. Note that these symbols may still be used as variable names as long as they do not shadow internal operations, so e.g. `let __sub = x: y: x + y; in __sub 3 5` remains valid code.
|
||||||
|
|
|
@ -342,6 +342,17 @@ void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
|
||||||
} else {
|
} else {
|
||||||
auto i = curEnv->find(name);
|
auto i = curEnv->find(name);
|
||||||
if (i != curEnv->vars.end()) {
|
if (i != curEnv->vars.end()) {
|
||||||
|
if (this->needsRoot && !curEnv->isRoot) {
|
||||||
|
throw ParseError({
|
||||||
|
.msg = HintFmt(
|
||||||
|
"Shadowing symbol '%s' used in internal expressions is not allowed. Use %s to disable this error.",
|
||||||
|
es.symbols[name],
|
||||||
|
"--extra-deprecated-features shadow-internal-symbols"
|
||||||
|
),
|
||||||
|
.pos = es.positions[pos]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this->level = level;
|
this->level = level;
|
||||||
displ = i->second;
|
displ = i->second;
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -132,8 +132,14 @@ struct ExprVar : Expr
|
||||||
Level level;
|
Level level;
|
||||||
Displacement displ;
|
Displacement displ;
|
||||||
|
|
||||||
ExprVar(Symbol name) : name(name) { };
|
/* Variables like `__sub` as generated from expressions like `5 - 3` shouldn't be overridden.
|
||||||
ExprVar(const PosIdx & pos, Symbol name) : pos(pos), name(name) { };
|
* This is implemented by having a blessed "root" env that contains the definitions (usually `builtins`)
|
||||||
|
* and checking that this var only binds against that env.
|
||||||
|
*/
|
||||||
|
bool needsRoot;
|
||||||
|
|
||||||
|
ExprVar(Symbol name) : name(name), needsRoot(false) { };
|
||||||
|
ExprVar(const PosIdx & pos, Symbol name, bool needsRoot = false) : pos(pos), name(name), needsRoot(needsRoot) { };
|
||||||
Value * maybeThunk(EvalState & state, Env & env) override;
|
Value * maybeThunk(EvalState & state, Env & env) override;
|
||||||
PosIdx getPos() const override { return pos; }
|
PosIdx getPos() const override { return pos; }
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
|
@ -476,6 +482,9 @@ struct StaticEnv
|
||||||
typedef std::vector<std::pair<Symbol, Displacement>> Vars;
|
typedef std::vector<std::pair<Symbol, Displacement>> Vars;
|
||||||
Vars vars;
|
Vars vars;
|
||||||
|
|
||||||
|
/* See ExprVar::needsRoot */
|
||||||
|
bool isRoot = false;
|
||||||
|
|
||||||
StaticEnv(ExprWith * isWith, const StaticEnv * up, size_t expectedSize = 0) : isWith(isWith), up(up) {
|
StaticEnv(ExprWith * isWith, const StaticEnv * up, size_t expectedSize = 0) : isWith(isWith), up(up) {
|
||||||
vars.reserve(expectedSize);
|
vars.reserve(expectedSize);
|
||||||
};
|
};
|
||||||
|
|
|
@ -89,12 +89,12 @@ struct ExprState
|
||||||
return std::make_unique<Op>(pos, std::move(left), std::move(right));
|
return std::make_unique<Op>(pos, std::move(left), std::move(right));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Expr> call(PosIdx pos, Symbol fn, bool flip = false)
|
std::unique_ptr<Expr> call(PosIdx pos, State & state, Symbol fn, bool flip = false)
|
||||||
{
|
{
|
||||||
std::vector<std::unique_ptr<Expr>> args(2);
|
std::vector<std::unique_ptr<Expr>> args(2);
|
||||||
args[flip ? 0 : 1] = popExprOnly();
|
args[flip ? 0 : 1] = popExprOnly();
|
||||||
args[flip ? 1 : 0] = popExprOnly();
|
args[flip ? 1 : 0] = popExprOnly();
|
||||||
return std::make_unique<ExprCall>(pos, std::make_unique<ExprVar>(fn), std::move(args));
|
return std::make_unique<ExprCall>(pos, state.mkInternalVar(pos, fn), std::move(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Expr> pipe(PosIdx pos, State & state, bool flip = false)
|
std::unique_ptr<Expr> pipe(PosIdx pos, State & state, bool flip = false)
|
||||||
|
@ -122,7 +122,7 @@ struct ExprState
|
||||||
|
|
||||||
std::unique_ptr<Expr> order(PosIdx pos, bool less, State & state)
|
std::unique_ptr<Expr> order(PosIdx pos, bool less, State & state)
|
||||||
{
|
{
|
||||||
return call(pos, state.s.lessThan, !less);
|
return call(pos, state, state.s.lessThan, !less);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Expr> concatStrings(PosIdx pos)
|
std::unique_ptr<Expr> concatStrings(PosIdx pos)
|
||||||
|
@ -138,7 +138,7 @@ struct ExprState
|
||||||
std::vector<std::unique_ptr<Expr>> args(2);
|
std::vector<std::unique_ptr<Expr>> args(2);
|
||||||
args[0] = std::make_unique<ExprInt>(0);
|
args[0] = std::make_unique<ExprInt>(0);
|
||||||
args[1] = popExprOnly();
|
args[1] = popExprOnly();
|
||||||
return std::make_unique<ExprCall>(pos, std::make_unique<ExprVar>(state.s.sub), std::move(args));
|
return std::make_unique<ExprCall>(pos, state.mkInternalVar(pos, state.s.sub), std::move(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<PosIdx, std::unique_ptr<Expr>> applyOp(PosIdx pos, auto & op, State & state) {
|
std::pair<PosIdx, std::unique_ptr<Expr>> applyOp(PosIdx pos, auto & op, State & state) {
|
||||||
|
@ -163,9 +163,9 @@ struct ExprState
|
||||||
[&] (Op::update) { return applyBinary<ExprOpUpdate>(pos); },
|
[&] (Op::update) { return applyBinary<ExprOpUpdate>(pos); },
|
||||||
[&] (Op::not_) { return applyUnary<ExprOpNot>(); },
|
[&] (Op::not_) { return applyUnary<ExprOpNot>(); },
|
||||||
[&] (Op::plus) { return concatStrings(pos); },
|
[&] (Op::plus) { return concatStrings(pos); },
|
||||||
[&] (Op::minus) { return call(pos, state.s.sub); },
|
[&] (Op::minus) { return call(pos, state, state.s.sub); },
|
||||||
[&] (Op::mul) { return call(pos, state.s.mul); },
|
[&] (Op::mul) { return call(pos, state, state.s.mul); },
|
||||||
[&] (Op::div) { return call(pos, state.s.div); },
|
[&] (Op::div) { return call(pos, state, state.s.div); },
|
||||||
[&] (Op::concat) { return applyBinary<ExprOpConcatLists>(pos); },
|
[&] (Op::concat) { return applyBinary<ExprOpConcatLists>(pos); },
|
||||||
[&] (has_attr & a) { return applyUnary<ExprOpHasAttr>(std::move(a.path)); },
|
[&] (has_attr & a) { return applyUnary<ExprOpHasAttr>(std::move(a.path)); },
|
||||||
[&] (Op::unary_minus) { return negate(pos, state); },
|
[&] (Op::unary_minus) { return negate(pos, state); },
|
||||||
|
@ -608,14 +608,15 @@ template<> struct BuildAST<grammar::v1::path::home_anchor> {
|
||||||
|
|
||||||
template<> struct BuildAST<grammar::v1::path::searched_path> {
|
template<> struct BuildAST<grammar::v1::path::searched_path> {
|
||||||
static void apply(const auto & in, StringState & s, State & ps) {
|
static void apply(const auto & in, StringState & s, State & ps) {
|
||||||
|
auto pos = ps.at(in);
|
||||||
std::vector<std::unique_ptr<Expr>> args{2};
|
std::vector<std::unique_ptr<Expr>> args{2};
|
||||||
args[0] = std::make_unique<ExprVar>(ps.s.nixPath);
|
args[0] = ps.mkInternalVar(pos, ps.s.nixPath);
|
||||||
args[1] = std::make_unique<ExprString>(in.string());
|
args[1] = std::make_unique<ExprString>(in.string());
|
||||||
s.parts.emplace_back(
|
s.parts.emplace_back(
|
||||||
ps.at(in),
|
pos,
|
||||||
std::make_unique<ExprCall>(
|
std::make_unique<ExprCall>(
|
||||||
ps.at(in),
|
pos,
|
||||||
std::make_unique<ExprVar>(ps.s.findFile),
|
ps.mkInternalVar(pos, ps.s.findFile),
|
||||||
std::move(args)));
|
std::move(args)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -39,6 +39,12 @@ struct State
|
||||||
std::unique_ptr<Formals> validateFormals(std::unique_ptr<Formals> formals, PosIdx pos = noPos, Symbol arg = {});
|
std::unique_ptr<Formals> validateFormals(std::unique_ptr<Formals> formals, PosIdx pos = noPos, Symbol arg = {});
|
||||||
std::unique_ptr<Expr> stripIndentation(const PosIdx pos, std::vector<IndStringLine> && line);
|
std::unique_ptr<Expr> stripIndentation(const PosIdx pos, std::vector<IndStringLine> && line);
|
||||||
|
|
||||||
|
/* Creates an ExprVar or an ExprVarRoot depending on the feature settings.
|
||||||
|
* The symbol is synthetic, but for the purpose of error handling the pos is required
|
||||||
|
* and should point to the expression where the var is used
|
||||||
|
*/
|
||||||
|
inline std::unique_ptr<ExprVar> mkInternalVar(PosIdx pos, Symbol name);
|
||||||
|
|
||||||
// lazy positioning means we don't get byte offsets directly, in.position() would work
|
// lazy positioning means we don't get byte offsets directly, in.position() would work
|
||||||
// but also requires line and column (which is expensive)
|
// but also requires line and column (which is expensive)
|
||||||
PosIdx at(const auto & in)
|
PosIdx at(const auto & in)
|
||||||
|
@ -52,6 +58,10 @@ struct State
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<ExprVar> State::mkInternalVar(PosIdx pos, Symbol name) {
|
||||||
|
return std::make_unique<ExprVar>(pos, name, !featureSettings.isEnabled(Dep::ShadowInternalSymbols));
|
||||||
|
}
|
||||||
|
|
||||||
inline void State::dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos)
|
inline void State::dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos)
|
||||||
{
|
{
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
|
|
|
@ -2845,6 +2845,7 @@ void EvalState::createBaseEnv()
|
||||||
baseEnv.values[0]->attrs->sort();
|
baseEnv.values[0]->attrs->sort();
|
||||||
|
|
||||||
staticBaseEnv->sort();
|
staticBaseEnv->sort();
|
||||||
|
staticBaseEnv->isRoot = true;
|
||||||
|
|
||||||
/* Note: we have to initialize the 'derivation' constant *after*
|
/* Note: we have to initialize the 'derivation' constant *after*
|
||||||
building baseEnv/staticBaseEnv because it uses 'builtins'. */
|
building baseEnv/staticBaseEnv because it uses 'builtins'. */
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
name: shadow-internal-symbols
|
||||||
|
internalName: ShadowInternalSymbols
|
||||||
|
---
|
||||||
|
Allow shadowing the symbols `__sub`, `__mul`, `__div`, `__lessThan`, `__findFile` and `__nixPath` when used in the internal AST expansion. (`5 - 3` expands to `__sub 5 3` etc.)
|
|
@ -155,6 +155,7 @@ deprecated_feature_definitions = files(
|
||||||
'deprecated-features/ancient-let.md',
|
'deprecated-features/ancient-let.md',
|
||||||
'deprecated-features/rec-set-overrides.md',
|
'deprecated-features/rec-set-overrides.md',
|
||||||
'deprecated-features/url-literals.md',
|
'deprecated-features/url-literals.md',
|
||||||
|
'deprecated-features/shadow-internal-symbols.md',
|
||||||
)
|
)
|
||||||
|
|
||||||
experimental_features_gen = custom_target(
|
experimental_features_gen = custom_target(
|
||||||
|
|
1
tests/functional/lang/eval-okay-arithmetic-override.exp
Normal file
1
tests/functional/lang/eval-okay-arithmetic-override.exp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[ 8 "multiplied" "subtracted" "subtracted" "divided" "compared" ]
|
|
@ -0,0 +1 @@
|
||||||
|
--extra-deprecated-features shadow-internal-symbols
|
14
tests/functional/lang/eval-okay-arithmetic-override.nix
Normal file
14
tests/functional/lang/eval-okay-arithmetic-override.nix
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
let
|
||||||
|
__sub = _: _: "subtracted";
|
||||||
|
__mul = _: _: "multiplied";
|
||||||
|
__div = _: _: "divided";
|
||||||
|
__lessThan = _: _: "compared";
|
||||||
|
in
|
||||||
|
[
|
||||||
|
(3 + 5)
|
||||||
|
(4 * 4)
|
||||||
|
(-3)
|
||||||
|
(12 - 3)
|
||||||
|
(0 / 1)
|
||||||
|
(42 < 16)
|
||||||
|
]
|
|
@ -1 +1 @@
|
||||||
-I lang/dir1 -I lang/dir2 -I dir5=lang/dir3
|
--extra-deprecated-features shadow-internal-symbols -I lang/dir1 -I lang/dir2 -I dir5=lang/dir3
|
||||||
|
|
1
tests/functional/lang/parse-okay-arithmetic.exp
Normal file
1
tests/functional/lang/parse-okay-arithmetic.exp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(__sub (7 + (__div (__mul (__sub 0 5) 12) 3)) 1)
|
1
tests/functional/lang/parse-okay-arithmetic.nix
Normal file
1
tests/functional/lang/parse-okay-arithmetic.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
7 + (-5) * 12 / 3 - 1
|
|
@ -14,8 +14,8 @@ nix-instantiate --restrict-eval --eval -E 'builtins.readFile ./simple.nix' -I sr
|
||||||
(! nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../lix/legacy')
|
(! nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../lix/legacy')
|
||||||
nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../lix/legacy' -I src=../../lix
|
nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../lix/legacy' -I src=../../lix
|
||||||
|
|
||||||
(! nix-instantiate --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in <foo>')
|
(! nix-instantiate --extra-deprecated-features shadow-internal-symbols --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in <foo>')
|
||||||
nix-instantiate --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in <foo>' -I src=.
|
nix-instantiate --extra-deprecated-features shadow-internal-symbols --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in <foo>' -I src=.
|
||||||
|
|
||||||
p=$(nix eval --raw --expr "builtins.fetchurl \"file://$(pwd)/restricted.sh\"" --impure --restrict-eval --allowed-uris "file://$(pwd)")
|
p=$(nix eval --raw --expr "builtins.fetchurl \"file://$(pwd)/restricted.sh\"" --impure --restrict-eval --allowed-uris "file://$(pwd)")
|
||||||
cmp $p restricted.sh
|
cmp $p restricted.sh
|
||||||
|
|
|
@ -246,4 +246,38 @@ namespace nix {
|
||||||
v = eval("let add = l: r: l + r; in ''a'' |> add <| ''c''", true, mockFeatureSettings);
|
v = eval("let add = l: r: l + r; in ''a'' |> add <| ''c''", true, mockFeatureSettings);
|
||||||
ASSERT_THAT(v, IsStringEq("ac"));
|
ASSERT_THAT(v, IsStringEq("ac"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(TrivialExpressionTest, shadowInternalSymbols) {
|
||||||
|
FeatureSettings mockFeatureSettings;
|
||||||
|
mockFeatureSettings.set("deprecated-features", "shadow-internal-symbols");
|
||||||
|
|
||||||
|
auto v = eval("let __sub = _: _: ''subtracted''; in -3", true, mockFeatureSettings);
|
||||||
|
ASSERT_THAT(v, IsStringEq("subtracted"));
|
||||||
|
ASSERT_THROW(eval("let __sub = _: _: ''subtracted''; in -3"), Error);
|
||||||
|
|
||||||
|
v = eval("let __sub = _: _: ''subtracted''; in 12 - 3", true, mockFeatureSettings);
|
||||||
|
ASSERT_THAT(v, IsStringEq("subtracted"));
|
||||||
|
ASSERT_THROW(eval("let __sub = _: _: ''subtracted''; in 12 - 3"), Error);
|
||||||
|
|
||||||
|
v = eval("let __mul = _: _: ''multiplied''; in 4 * 4", true, mockFeatureSettings);
|
||||||
|
ASSERT_THAT(v, IsStringEq("multiplied"));
|
||||||
|
ASSERT_THROW(eval("let __mul = _: _: ''multiplied''; in 4 * 4"), Error);
|
||||||
|
|
||||||
|
v = eval("let __div = _: _: ''divided''; in 0 / 1", true, mockFeatureSettings);
|
||||||
|
ASSERT_THAT(v, IsStringEq("divided"));
|
||||||
|
ASSERT_THROW(eval("let __div = _: _: ''divided''; in 0 / 1"), Error);
|
||||||
|
|
||||||
|
v = eval("let __lessThan = _: _: ''compared''; in 42 < 16", true, mockFeatureSettings);
|
||||||
|
ASSERT_THAT(v, IsStringEq("compared"));
|
||||||
|
ASSERT_THROW(eval("let __lessThan = _: _: ''compared''; in 42 < 16"), Error);
|
||||||
|
|
||||||
|
v = eval(
|
||||||
|
"let __findFile = path: entry: assert path == ''foo''; entry; __nixPath = ''foo''; in <bar>",
|
||||||
|
true,
|
||||||
|
mockFeatureSettings
|
||||||
|
);
|
||||||
|
ASSERT_THAT(v, IsStringEq("bar"));
|
||||||
|
ASSERT_THROW(eval("let __findFile = _: _: ''found''; in import <foo>"), Error);
|
||||||
|
ASSERT_THROW(eval("let __nixPath = null; in import <foo>"), Error);
|
||||||
|
}
|
||||||
} /* namespace nix */
|
} /* namespace nix */
|
||||||
|
|
Loading…
Reference in a new issue