libexpr: allow empty attr-names in parseAttrPath if they are quoted

While it doesn't make sense to have `foo..bar`, the attribute-path
`foo."".bar` is valid and shouldn't throw.

Change-Id: Ifcddaad6233c6ba8f17cb5c953c2101d276dfeb6
This commit is contained in:
Maximilian Bosch
2026-03-21 23:16:30 +01:00
parent c5d21b36c5
commit 2a11984a58
4 changed files with 79 additions and 9 deletions
+10
View File
@@ -0,0 +1,10 @@
---
synopsis: "libexpr: allow empty attr-names in parseAttrPath if they are quoted"
cls: [5375]
category: "Miscellany"
credits: [ma27]
---
Empty strings are now allowed in attribute paths as consumed by e.g. `nix-build`.
I.e. `nix-build -A 'foo."".bar'` works now.
The quotes are necessary, i.e. `nix-build -A foo..bar` will throw an error.
+16 -3
View File
@@ -16,6 +16,22 @@ std::vector<std::string> parseAttrPath(std::string_view const s)
auto i = s.begin();
while (i != s.end()) {
if (*i == '.') {
if (!haveData) {
if (res.empty()) {
throw ParseError(
"Leading dot in attribute selection path '%1%' is not allowed! If the attribute name "
"is an empty string, use '\"\".foo.bar'",
s
);
} else {
throw ParseError(
"consecutive dots not allowed in selection path '%1%', use 'foo.\"\".bar' to denote "
"an "
"empty attribute name",
s
);
}
}
res.push_back(cur);
haveData = false;
cur.clear();
@@ -90,9 +106,6 @@ findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & au
according to what is specified in the attrPath. */
if (!attrIndex) {
if (attr.empty())
throw Error("empty attribute name in selection path '%1%'", attrPath);
if (v.type() != nAttrs) {
auto pathPart =
std::vector<std::string>(tokens.begin(), tokens.begin() + attrPathIdx);
+6 -3
View File
@@ -23,12 +23,15 @@ ERR_CASES: list[ShouldError] = [
"1",
"""error: the expression selected by the selection path '1' should be a list but is a set: { }""",
),
ShouldError("{}", ".", """error: empty attribute name in selection path '.'"""),
ShouldError(
'{ x."" = 2; }', 'x.""', """error: empty attribute name in selection path 'x.""'"""
"{}",
".",
"""error: Leading dot in attribute selection path '.' is not allowed! If the attribute name is an empty string, use '\"\".foo.bar'""",
),
ShouldError(
'{ x."".y = 2; }', 'x."".y', """error: empty attribute name in selection path 'x."".y'"""
"{}",
"bla..blub",
"""error: consecutive dots not allowed in selection path 'bla..blub', use 'foo."".bar' to denote an empty attribute name""",
),
ShouldError(
"[]", "1", """error: list index 1 in selection path '1' is out of range for list [ ]"""
+47 -3
View File
@@ -38,12 +38,56 @@ std::pair<Value, PosIdx> AttrPathEval::testFindAlongAttrPath(std::string expr, s
// n.b. I do not know why we throw for empty attrs but they are apparently
// disallowed.
TEST_F(AttrPathEval, emptyAttrsThrows)
TEST_F(AttrPathEval, emptyAttrsThrowsWithoutQuotes)
{
std::string expr = "{a.\"\".b = 2;}";
ASSERT_NO_THROW(testFindAlongAttrPath(expr, "a"));
ASSERT_THROW(testFindAlongAttrPath(expr, "a.\"\".b"), Error);
ASSERT_THROW(testFindAlongAttrPath(expr, "a.\"\""), Error);
ASSERT_NO_THROW(testFindAlongAttrPath(expr, "a.\"\".b"));
ASSERT_THROW(testFindAlongAttrPath(expr, "a..b"), Error);
ASSERT_NO_THROW(testFindAlongAttrPath(expr, "a.\"\""));
}
TEST(attr_path_eval, quotes)
{
auto p1 = parseAttrPath("foo.\"foo bar\".baz");
ASSERT_EQ(3, p1.size());
ASSERT_EQ("foo", p1[0]);
ASSERT_EQ("foo bar", p1[1]);
ASSERT_EQ("baz", p1[2]);
auto p2 = parseAttrPath("foo.\"foo bar\"");
ASSERT_EQ(2, p2.size());
ASSERT_EQ("foo", p2[0]);
ASSERT_EQ("foo bar", p2[1]);
auto p3 = parseAttrPath("\"foo bar\"");
ASSERT_EQ(1, p3.size());
ASSERT_EQ("foo bar", p3[0]);
}
TEST(attr_path_eval, quotes_empty)
{
auto p1 = parseAttrPath("foo.\"\".bar");
ASSERT_EQ(3, p1.size());
ASSERT_EQ("foo", p1[0]);
ASSERT_EQ("", p1[1]);
ASSERT_EQ("bar", p1[2]);
auto p2 = parseAttrPath("foo.\"\"");
ASSERT_EQ(2, p2.size());
ASSERT_EQ("foo", p2[0]);
ASSERT_EQ("", p2[1]);
auto p3 = parseAttrPath("\"\"");
ASSERT_EQ(1, p3.size());
ASSERT_EQ("", p3[0]);
}
TEST(attr_path_eval, quotes_syntax)
{
ASSERT_THROW(parseAttrPath("foo.\"bar"), ParseError);
// escaped quotes (\") are not supported
ASSERT_THROW(parseAttrPath("foo.\"bar\\\"\""), ParseError);
}
}