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:
@@ -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,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);
|
||||
|
||||
@@ -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 [ ]"""
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user