libexpr/nix2: Make null, true and false proper keywords

They cannot be used as identifiers anymore, and thus cannot be
overridden either.

Change-Id: I5f0e8252b4516127bda5f42403015fc837902b57
This commit is contained in:
piegames 2024-09-18 16:43:41 +02:00
parent 791c0e4468
commit b5bb2b9034
8 changed files with 85 additions and 1 deletions

View file

@ -886,6 +886,12 @@ Value * ExprString::maybeThunk(EvalState & state, Env & env)
return &v; return &v;
} }
Value * ExprLiteral::maybeThunk(EvalState & state, Env & env)
{
state.nrAvoided++;
return &v;
}
Value * ExprInt::maybeThunk(EvalState & state, Env & env) Value * ExprInt::maybeThunk(EvalState & state, Env & env)
{ {
state.nrAvoided++; state.nrAvoided++;
@ -1024,6 +1030,10 @@ void Expr::eval(EvalState & state, Env & env, Value & v)
abort(); abort();
} }
void ExprLiteral::eval(EvalState & state, Env & env, Value & v)
{
v = this->v;
}
void ExprInt::eval(EvalState & state, Env & env, Value & v) void ExprInt::eval(EvalState & state, Env & env, Value & v)
{ {

View file

@ -752,6 +752,7 @@ private:
friend struct ExprOpConcatLists; friend struct ExprOpConcatLists;
friend struct ExprVar; friend struct ExprVar;
friend struct ExprString; friend struct ExprString;
friend struct ExprLiteral;
friend struct ExprInt; friend struct ExprInt;
friend struct ExprFloat; friend struct ExprFloat;
friend struct ExprPath; friend struct ExprPath;

View file

@ -34,6 +34,19 @@ void Expr::show(const SymbolTable & symbols, std::ostream & str) const
abort(); abort();
} }
void ExprLiteral::show(const SymbolTable & symbols, std::ostream & str) const
{
// The value printer is useless because it may force values and thus needs state and stuff.
// These are the only literals we currently need it for
if (v.type() == nBool) {
str << (v.boolean ? "true" : "false");
} else if (v.type() == nNull) {
str << "null";
} else {
abort();
}
}
void ExprInt::show(const SymbolTable & symbols, std::ostream & str) const void ExprInt::show(const SymbolTable & symbols, std::ostream & str) const
{ {
str << n; str << n;
@ -300,6 +313,12 @@ void Expr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env
abort(); abort();
} }
void ExprLiteral::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
}
void ExprInt::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) void ExprInt::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl) if (es.debugRepl)

View file

@ -71,6 +71,17 @@ public:
void eval(EvalState & state, Env & env, Value & v) override; \ void eval(EvalState & state, Env & env, Value & v) override; \
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override; void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override;
// Only used in nix-lang v2.
// in v1 null, true and false are just built-in constants
struct ExprLiteral : Expr
{
Value v;
ExprLiteral() { v.mkNull(); }
ExprLiteral(bool b) { v.mkBool(b); };
Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS
};
struct ExprInt : Expr struct ExprInt : Expr
{ {
NixInt n; NixInt n;

View file

@ -76,6 +76,9 @@ struct kw_in : _keyword<TAO_PEGTL_STRING("in")> {};
struct kw_rec : _keyword<TAO_PEGTL_STRING("rec")> {}; struct kw_rec : _keyword<TAO_PEGTL_STRING("rec")> {};
struct kw_inherit : _keyword<TAO_PEGTL_STRING("inherit")> {}; struct kw_inherit : _keyword<TAO_PEGTL_STRING("inherit")> {};
struct kw_or : _keyword<TAO_PEGTL_STRING("or")> {}; struct kw_or : _keyword<TAO_PEGTL_STRING("or")> {};
struct kw_null : _keyword<TAO_PEGTL_STRING("null")> {};
struct kw_true : _keyword<TAO_PEGTL_STRING("true")> {};
struct kw_false : _keyword<TAO_PEGTL_STRING("false")> {};
// `-` can be a unary prefix op, a binary infix op, or the first character // `-` can be a unary prefix op, a binary infix op, or the first character
// of a path or -> (ex 1->1--1) // of a path or -> (ex 1->1--1)
@ -98,7 +101,10 @@ struct _not_at_any_keyword : minus<
TAO_PEGTL_STRING("rec"), TAO_PEGTL_STRING("rec"),
TAO_PEGTL_STRING("if"), TAO_PEGTL_STRING("if"),
TAO_PEGTL_STRING("in"), TAO_PEGTL_STRING("in"),
TAO_PEGTL_STRING("or") TAO_PEGTL_STRING("or"),
TAO_PEGTL_STRING("null"),
TAO_PEGTL_STRING("true"),
TAO_PEGTL_STRING("false")
> >
> {}; > {};
@ -440,6 +446,9 @@ struct _expr {
struct select; struct select;
struct null : semantic, t::kw_null {};
struct _true : semantic, t::kw_true {};
struct _false : semantic, t::kw_false {};
struct id : semantic, t::identifier {}; struct id : semantic, t::identifier {};
struct int_ : semantic, t::integer {}; struct int_ : semantic, t::integer {};
struct float_ : semantic, t::floating {}; struct float_ : semantic, t::floating {};
@ -459,6 +468,9 @@ struct _expr {
> {}; > {};
struct _simple : sor< struct _simple : sor<
null,
_true,
_false,
id, id,
int_, int_,
float_, float_,

View file

@ -388,6 +388,24 @@ template<> struct BuildAST<grammar::v2::binding> {
} }
}; };
template<> struct BuildAST<grammar::v2::expr::null> {
static void apply(const auto & in, ExprState & s, State & ps) {
s.pushExpr<ExprLiteral>(ps.at(in));
}
};
template<> struct BuildAST<grammar::v2::expr::_true> {
static void apply(const auto & in, ExprState & s, State & ps) {
s.pushExpr<ExprLiteral>(ps.at(in), true);
}
};
template<> struct BuildAST<grammar::v2::expr::_false> {
static void apply(const auto & in, ExprState & s, State & ps) {
s.pushExpr<ExprLiteral>(ps.at(in), false);
}
};
template<> struct BuildAST<grammar::v2::expr::id> { template<> struct BuildAST<grammar::v2::expr::id> {
static void apply(const auto & in, ExprState & s, State & ps) { static void apply(const auto & in, ExprState & s, State & ps) {
if (in.string_view() == "__curPos") if (in.string_view() == "__curPos")

View file

@ -195,6 +195,7 @@ constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails
- Number/path token boundaries have been cleaned up. - Number/path token boundaries have been cleaned up.
- `3a` is a parse error instead of a function application. - `3a` is a parse error instead of a function application.
- Ambiguous path/division syntax like `1.0/3` now throws a parse error instead of parsing as path. - Ambiguous path/division syntax like `1.0/3` now throws a parse error instead of parsing as path.
- `null`, `true` and `false` are now proper keywords which cannot simply be shadowed by variables.
- Identifiers have been cleaned up. - Identifiers have been cleaned up.
- String identifiers (`"foo bar"`) are now disallowed in let bindings, as there is no way to reference to these variables anyways. - String identifiers (`"foo bar"`) are now disallowed in let bindings, as there is no way to reference to these variables anyways.
- This includes such names in `inherit` within let bindings - This includes such names in `inherit` within let bindings

View file

@ -278,4 +278,16 @@ namespace nix {
ASSERT_THAT(v, IsStringEq("ac")); ASSERT_THAT(v, IsStringEq("ac"));
} }
TEST_F(TrivialExpressionTest, literalsOverride) {
auto v = eval("let true = \"true\"; false = \"false\"; null = \"null\"; in true + false + null");
ASSERT_THAT(v, IsStringEq("truefalsenull"));
FeatureSettings mockFeatureSettings;
mockFeatureSettings.set("experimental-features", "nix-lang2");
// Need separate tests for each because parser fails at first error
ASSERT_THROW(eval("let true = \"true\"; in true", true, mockFeatureSettings), Error);
ASSERT_THROW(eval("let false = \"false\"; in false", true, mockFeatureSettings), Error);
ASSERT_THROW(eval("let null = \"null\"; in null", true, mockFeatureSettings), Error);
}
} /* namespace nix */ } /* namespace nix */