Skip to content

Commit 952be9f

Browse files
authored
Merge pull request #14644 from Radvendii/fix-14642
parser.y: properly abstract over to-be-created strings
2 parents 423e732 + 0c0a41a commit 952be9f

15 files changed

+160
-24
lines changed

maintainers/flake-module.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@
7979
# Not supported by nixfmt
8080
''^tests/functional/lang/eval-okay-deprecate-cursed-or\.nix$''
8181
''^tests/functional/lang/eval-okay-attrs5\.nix$''
82+
''^tests/functional/lang/eval-fail-dynamic-attrs-inherit\.nix$''
83+
''^tests/functional/lang/eval-fail-dynamic-attrs-inherit-2\.nix$''
8284

8385
# More syntax tests
8486
# These tests, or parts of them, should have been parse-* test cases.

src/libexpr/include/nix/expr/parser-state.hh

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,79 @@ struct ParserLocation
4747
}
4848
};
4949

50+
/**
51+
* This represents a string-like parse that possibly has yet to be constructed.
52+
*
53+
* Examples:
54+
* "foo"
55+
* ${"foo" + "bar"}
56+
* "foo.bar"
57+
* "foo-${a}"
58+
*
59+
* Using this type allows us to avoid construction altogether in cases where what we actually need is the string
60+
* contents. For example in foo."bar.baz", there is no need to construct an AST node for "bar.baz", but we don't know
61+
* that until we bubble the value up during parsing and see that it's a node in an AttrPath.
62+
*/
63+
class ToBeStringyExpr
64+
{
65+
private:
66+
using Raw = std::variant<std::monostate, std::string_view, Expr *>;
67+
Raw raw;
68+
69+
public:
70+
ToBeStringyExpr() = default;
71+
72+
ToBeStringyExpr(std::string_view v)
73+
: raw(v)
74+
{
75+
}
76+
77+
ToBeStringyExpr(Expr * expr)
78+
: raw(expr)
79+
{
80+
assert(expr);
81+
}
82+
83+
/**
84+
* Visits the expression and invokes an overloaded functor object \ref f.
85+
* If the underlying Expr has a dynamic type of ExprString the overload taking std::string_view
86+
* is invoked.
87+
*
88+
* Used to consistently handle simple StringExpr ${"string"} as non-dynamic attributes.
89+
* @see https://github.com/NixOS/nix/issues/14642
90+
*/
91+
template<class F>
92+
void visit(F && f)
93+
{
94+
std::visit(
95+
overloaded{
96+
[&](std::string_view str) { f(str); },
97+
[&](Expr * expr) {
98+
ExprString * str = dynamic_cast<ExprString *>(expr);
99+
if (str)
100+
f(str->v.string_view());
101+
else
102+
f(expr);
103+
},
104+
[](std::monostate) { unreachable(); }},
105+
raw);
106+
}
107+
108+
/**
109+
* Get or create an Expr from either an existing Expr or from a string.
110+
* Delays the allocation or an AST node in case the parser only cares about string contents.
111+
*/
112+
Expr * toExpr(Exprs & exprs)
113+
{
114+
return std::visit(
115+
overloaded{
116+
[&](std::string_view str) -> Expr * { return exprs.add<ExprString>(exprs.alloc, str); },
117+
[&](Expr * expr) { return expr; },
118+
[](std::monostate) -> Expr * { unreachable(); }},
119+
raw);
120+
}
121+
};
122+
50123
struct LexerState
51124
{
52125
/**

src/libexpr/parser.y

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ static Expr * makeCall(Exprs & exprs, PosIdx pos, Expr * fn, Expr * arg) {
138138
%type <std::vector<std::pair<PosIdx, Expr *>>> string_parts_interpolated
139139
%type <std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>>> ind_string_parts
140140
%type <Expr *> path_start
141-
%type <std::variant<Expr *, std::string_view>> string_parts string_attr
141+
%type <ToBeStringyExpr> string_parts string_attr
142142
%type <StringToken> attr
143143
%token <StringToken> ID
144144
%token <StringToken> STR IND_STR
@@ -297,12 +297,7 @@ expr_simple
297297
}
298298
| INT_LIT { $$ = state->exprs.add<ExprInt>($1); }
299299
| FLOAT_LIT { $$ = state->exprs.add<ExprFloat>($1); }
300-
| '"' string_parts '"' {
301-
std::visit(overloaded{
302-
[&](std::string_view str) { $$ = state->exprs.add<ExprString>(state->exprs.alloc, str); },
303-
[&](Expr * expr) { $$ = expr; }},
304-
$2);
305-
}
300+
| '"' string_parts '"' { $$ = $2.toExpr(state->exprs); }
306301
| IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
307302
$$ = state->stripIndentation(CUR_POS, $2);
308303
}
@@ -342,9 +337,9 @@ expr_simple
342337
;
343338

344339
string_parts
345-
: STR { $$ = $1; }
346-
| string_parts_interpolated { $$ = state->exprs.add<ExprConcatStrings>(state->exprs.alloc, CUR_POS, true, $1); }
347-
| { $$ = std::string_view(); }
340+
: STR { $$ = {$1}; }
341+
| string_parts_interpolated { $$ = {state->exprs.add<ExprConcatStrings>(state->exprs.alloc, CUR_POS, true, $1)}; }
342+
| { $$ = {std::string_view()}; }
348343
;
349344

350345
string_parts_interpolated
@@ -447,15 +442,15 @@ attrs
447442
: attrs attr { $$ = std::move($1); $$.emplace_back(state->symbols.create($2), state->at(@2)); }
448443
| attrs string_attr
449444
{ $$ = std::move($1);
450-
std::visit(overloaded {
445+
$2.visit(overloaded{
451446
[&](std::string_view str) { $$.emplace_back(state->symbols.create(str), state->at(@2)); },
452447
[&](Expr * expr) {
453-
throw ParseError({
454-
.msg = HintFmt("dynamic attributes not allowed in inherit"),
455-
.pos = state->positions[state->at(@2)]
456-
});
457-
}
458-
}, $2);
448+
throw ParseError({
449+
.msg = HintFmt("dynamic attributes not allowed in inherit"),
450+
.pos = state->positions[state->at(@2)]
451+
});
452+
}}
453+
);
459454
}
460455
| { }
461456
;
@@ -464,17 +459,17 @@ attrpath
464459
: attrpath '.' attr { $$ = std::move($1); $$.emplace_back(state->symbols.create($3)); }
465460
| attrpath '.' string_attr
466461
{ $$ = std::move($1);
467-
std::visit(overloaded {
462+
$3.visit(overloaded{
468463
[&](std::string_view str) { $$.emplace_back(state->symbols.create(str)); },
469-
[&](Expr * expr) { $$.emplace_back(expr); }
470-
}, std::move($3));
464+
[&](Expr * expr) { $$.emplace_back(expr); }}
465+
);
471466
}
472467
| attr { $$.emplace_back(state->symbols.create($1)); }
473468
| string_attr
474-
{ std::visit(overloaded {
469+
{ $1.visit(overloaded{
475470
[&](std::string_view str) { $$.emplace_back(state->symbols.create(str)); },
476-
[&](Expr * expr) { $$.emplace_back(expr); }
477-
}, std::move($1));
471+
[&](Expr * expr) { $$.emplace_back(expr); }}
472+
);
478473
}
479474
;
480475

@@ -485,7 +480,7 @@ attr
485480

486481
string_attr
487482
: '"' string_parts '"' { $$ = std::move($2); }
488-
| DOLLAR_CURLY expr '}' { $$ = $2; }
483+
| DOLLAR_CURLY expr '}' { $$ = {$2}; }
489484
;
490485

491486
list
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
error: dynamic attributes not allowed in inherit
2+
at /pwd/lang/eval-fail-dynamic-attrs-inherit-2.nix:5:15:
3+
4| {
4+
5| inherit (a) ${"b" + ""};
5+
| ^
6+
6| }
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
let
2+
a.b = 1;
3+
in
4+
{
5+
inherit (a) ${"b" + ""};
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
error: dynamic attributes not allowed in inherit
2+
at /pwd/lang/eval-fail-dynamic-attrs-inherit.nix:5:11:
3+
4| {
4+
5| inherit ${"a" + ""};
5+
| ^
6+
6| }
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
let
2+
a = 1;
3+
in
4+
{
5+
inherit ${"a" + ""};
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: dynamic attributes not allowed in let
2+
at /pwd/lang/eval-fail-dynamic-attrs-let-2.nix:1:1:
3+
1| let
4+
| ^
5+
2| ${"${"a"}"} = 1;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
let
2+
${"${"a"}"} = 1;
3+
in
4+
a
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: dynamic attributes not allowed in let
2+
at /pwd/lang/eval-fail-dynamic-attrs-let-3.nix:1:1:
3+
1| let
4+
| ^
5+
2| "${"a"}" = 1;

0 commit comments

Comments
 (0)