Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions maintainers/flake-module.nix
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@
# Not supported by nixfmt
''^tests/functional/lang/eval-okay-deprecate-cursed-or\.nix$''
''^tests/functional/lang/eval-okay-attrs5\.nix$''
''^tests/functional/lang/eval-fail-dynamic-attrs-inherit\.nix$''
''^tests/functional/lang/eval-fail-dynamic-attrs-inherit-2\.nix$''

# More syntax tests
# These tests, or parts of them, should have been parse-* test cases.
Expand Down
73 changes: 73 additions & 0 deletions src/libexpr/include/nix/expr/parser-state.hh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,79 @@ struct ParserLocation
}
};

/**
* This represents a string-like parse that possibly has yet to be constructed.
*
* Examples:
* "foo"
* ${"foo" + "bar"}
* "foo.bar"
* "foo-${a}"
*
* Using this type allows us to avoid construction altogether in cases where what we actually need is the string
* contents. For example in foo."bar.baz", there is no need to construct an AST node for "bar.baz", but we don't know
* that until we bubble the value up during parsing and see that it's a node in an AttrPath.
*/
class ToBeStringyExpr
{
private:
using Raw = std::variant<std::monostate, std::string_view, Expr *>;
Raw raw;

public:
ToBeStringyExpr() = default;

ToBeStringyExpr(std::string_view v)
: raw(v)
{
}

ToBeStringyExpr(Expr * expr)
: raw(expr)
{
assert(expr);
}

/**
* Visits the expression and invokes an overloaded functor object \ref f.
* If the underlying Expr has a dynamic type of ExprString the overload taking std::string_view
* is invoked.
*
* Used to consistently handle simple StringExpr ${"string"} as non-dynamic attributes.
* @see https://github.com/NixOS/nix/issues/14642
*/
template<class F>
void visit(F && f)
{
std::visit(
overloaded{
[&](std::string_view str) { f(str); },
[&](Expr * expr) {
ExprString * str = dynamic_cast<ExprString *>(expr);
if (str)
f(str->v.string_view());
else
f(expr);
},
[](std::monostate) { unreachable(); }},
raw);
}

/**
* Get or create an Expr from either an existing Expr or from a string.
* Delays the allocation or an AST node in case the parser only cares about string contents.
*/
Expr * toExpr(Exprs & exprs)
{
return std::visit(
overloaded{
[&](std::string_view str) -> Expr * { return exprs.add<ExprString>(exprs.alloc, str); },
[&](Expr * expr) { return expr; },
[](std::monostate) -> Expr * { unreachable(); }},
raw);
}
};

struct LexerState
{
/**
Expand Down
43 changes: 19 additions & 24 deletions src/libexpr/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ static Expr * makeCall(Exprs & exprs, PosIdx pos, Expr * fn, Expr * arg) {
%type <std::vector<std::pair<PosIdx, Expr *>>> string_parts_interpolated
%type <std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>>> ind_string_parts
%type <Expr *> path_start
%type <std::variant<Expr *, std::string_view>> string_parts string_attr
%type <ToBeStringyExpr> string_parts string_attr
%type <StringToken> attr
%token <StringToken> ID
%token <StringToken> STR IND_STR
Expand Down Expand Up @@ -297,12 +297,7 @@ expr_simple
}
| INT_LIT { $$ = state->exprs.add<ExprInt>($1); }
| FLOAT_LIT { $$ = state->exprs.add<ExprFloat>($1); }
| '"' string_parts '"' {
std::visit(overloaded{
[&](std::string_view str) { $$ = state->exprs.add<ExprString>(state->exprs.alloc, str); },
[&](Expr * expr) { $$ = expr; }},
$2);
}
| '"' string_parts '"' { $$ = $2.toExpr(state->exprs); }
| IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
$$ = state->stripIndentation(CUR_POS, $2);
}
Expand Down Expand Up @@ -342,9 +337,9 @@ expr_simple
;

string_parts
: STR { $$ = $1; }
| string_parts_interpolated { $$ = state->exprs.add<ExprConcatStrings>(state->exprs.alloc, CUR_POS, true, $1); }
| { $$ = std::string_view(); }
: STR { $$ = {$1}; }
| string_parts_interpolated { $$ = {state->exprs.add<ExprConcatStrings>(state->exprs.alloc, CUR_POS, true, $1)}; }
| { $$ = {std::string_view()}; }
;

string_parts_interpolated
Expand Down Expand Up @@ -447,15 +442,15 @@ attrs
: attrs attr { $$ = std::move($1); $$.emplace_back(state->symbols.create($2), state->at(@2)); }
| attrs string_attr
{ $$ = std::move($1);
std::visit(overloaded {
$2.visit(overloaded{
[&](std::string_view str) { $$.emplace_back(state->symbols.create(str), state->at(@2)); },
[&](Expr * expr) {
throw ParseError({
.msg = HintFmt("dynamic attributes not allowed in inherit"),
.pos = state->positions[state->at(@2)]
});
}
}, $2);
throw ParseError({
.msg = HintFmt("dynamic attributes not allowed in inherit"),
.pos = state->positions[state->at(@2)]
});
}}
);
}
| { }
;
Expand All @@ -464,17 +459,17 @@ attrpath
: attrpath '.' attr { $$ = std::move($1); $$.emplace_back(state->symbols.create($3)); }
| attrpath '.' string_attr
{ $$ = std::move($1);
std::visit(overloaded {
$3.visit(overloaded{
[&](std::string_view str) { $$.emplace_back(state->symbols.create(str)); },
[&](Expr * expr) { $$.emplace_back(expr); }
}, std::move($3));
[&](Expr * expr) { $$.emplace_back(expr); }}
);
}
| attr { $$.emplace_back(state->symbols.create($1)); }
| string_attr
{ std::visit(overloaded {
{ $1.visit(overloaded{
[&](std::string_view str) { $$.emplace_back(state->symbols.create(str)); },
[&](Expr * expr) { $$.emplace_back(expr); }
}, std::move($1));
[&](Expr * expr) { $$.emplace_back(expr); }}
);
}
;

Expand All @@ -485,7 +480,7 @@ attr

string_attr
: '"' string_parts '"' { $$ = std::move($2); }
| DOLLAR_CURLY expr '}' { $$ = $2; }
| DOLLAR_CURLY expr '}' { $$ = {$2}; }
;

list
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
error: dynamic attributes not allowed in inherit
at /pwd/lang/eval-fail-dynamic-attrs-inherit-2.nix:5:15:
4| {
5| inherit (a) ${"b" + ""};
| ^
6| }
6 changes: 6 additions & 0 deletions tests/functional/lang/eval-fail-dynamic-attrs-inherit-2.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
let
a.b = 1;
in
{
inherit (a) ${"b" + ""};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
error: dynamic attributes not allowed in inherit
at /pwd/lang/eval-fail-dynamic-attrs-inherit.nix:5:11:
4| {
5| inherit ${"a" + ""};
| ^
6| }
6 changes: 6 additions & 0 deletions tests/functional/lang/eval-fail-dynamic-attrs-inherit.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
let
a = 1;
in
{
inherit ${"a" + ""};
}
5 changes: 5 additions & 0 deletions tests/functional/lang/eval-fail-dynamic-attrs-let-2.err.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: dynamic attributes not allowed in let
at /pwd/lang/eval-fail-dynamic-attrs-let-2.nix:1:1:
1| let
| ^
2| ${"${"a"}"} = 1;
4 changes: 4 additions & 0 deletions tests/functional/lang/eval-fail-dynamic-attrs-let-2.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
let
${"${"a"}"} = 1;
in
a
5 changes: 5 additions & 0 deletions tests/functional/lang/eval-fail-dynamic-attrs-let-3.err.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: dynamic attributes not allowed in let
at /pwd/lang/eval-fail-dynamic-attrs-let-3.nix:1:1:
1| let
| ^
2| "${"a"}" = 1;
4 changes: 4 additions & 0 deletions tests/functional/lang/eval-fail-dynamic-attrs-let-3.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
let
"${"a"}" = 1;
in
a
5 changes: 5 additions & 0 deletions tests/functional/lang/eval-fail-dynamic-attrs-let.err.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: dynamic attributes not allowed in let
at /pwd/lang/eval-fail-dynamic-attrs-let.nix:1:1:
1| let
| ^
2| ${"a" + ""} = 1;
4 changes: 4 additions & 0 deletions tests/functional/lang/eval-fail-dynamic-attrs-let.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
let
${"a" + ""} = 1;
in
a
1 change: 1 addition & 0 deletions tests/functional/lang/eval-okay-dynamic-attrs-3.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ a = 1; attrs = { b = 1; c = 1; d = 1; }; b = 1; c = 1; d = 1; }
14 changes: 14 additions & 0 deletions tests/functional/lang/eval-okay-dynamic-attrs-3.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# dynamic attrs are not generally allowed in `let`, and inherit, but they are if they only contain a string
let
${"a"} = 1;
attrs = rec {
b = c;
${"c"} = d;
d = a;
};
in
{
inherit ${"a"};
inherit attrs;
inherit (attrs) ${"b"} ${"c"} d;
}
Loading