From 2cedd1a818b7311d49dfd64d32760bc8f93c7030 Mon Sep 17 00:00:00 2001 From: Aetherall Date: Mon, 13 Jan 2025 14:23:40 +0100 Subject: [PATCH] fix(completion): correctly parse incomplete attrsets and provide completions --- libnixf/include/nixf/Basic/Range.h | 9 ++ libnixf/src/Parse/ParseExpr.cpp | 11 ++- libnixf/test/Parse/ParseAttrs.cpp | 154 +++++++++++++++++++++++++++-- nixd/lib/Controller/Completion.cpp | 1 - 4 files changed, 163 insertions(+), 12 deletions(-) diff --git a/libnixf/include/nixf/Basic/Range.h b/libnixf/include/nixf/Basic/Range.h index 8151d616b..e4d35c000 100644 --- a/libnixf/include/nixf/Basic/Range.h +++ b/libnixf/include/nixf/Basic/Range.h @@ -2,6 +2,7 @@ #include // std::size_t #include +#include namespace nixf { @@ -28,6 +29,10 @@ class Position { friend bool operator<=(const Position &LHS, const Position &RHS) { return LHS < RHS || LHS == RHS; } + + std::string to_string() const { + return std::to_string(Line) + ":" + std::to_string(Column); + } }; class PositionRange { @@ -123,6 +128,10 @@ class LexerCursorRange { [[nodiscard]] PositionRange range() const { return {LCur.position(), RCur.position()}; } + + std::string to_string() const { + return LCur.position().to_string() + " -> " + RCur.position().to_string(); + } }; } // namespace nixf diff --git a/libnixf/src/Parse/ParseExpr.cpp b/libnixf/src/Parse/ParseExpr.cpp index 111baea2d..35303a7de 100644 --- a/libnixf/src/Parse/ParseExpr.cpp +++ b/libnixf/src/Parse/ParseExpr.cpp @@ -71,7 +71,7 @@ std::shared_ptr Parser::parseExprApp(int Limit) { } std::shared_ptr Parser::parseExpr() { - // Look ahead 3 tokens. + // Look ahead 4 tokens. switch (peek().kind()) { case tok_id: { switch (peek(1).kind()) { @@ -93,8 +93,15 @@ std::shared_ptr Parser::parseExpr() { case tok_comma: // { a , case tok_id: // { a b case tok_ellipsis: // { a ... - case tok_r_curly: return parseExprLambda(); + case tok_r_curly: + switch (peek(3).kind()) { + case tok_colon: // { a } : + case tok_at: // { a } @ + return parseExprLambda(); + default: + return parseExprAttrs(); + } default: break; } diff --git a/libnixf/test/Parse/ParseAttrs.cpp b/libnixf/test/Parse/ParseAttrs.cpp index 45913e1ac..fa5b85bd6 100644 --- a/libnixf/test/Parse/ParseAttrs.cpp +++ b/libnixf/test/Parse/ParseAttrs.cpp @@ -223,25 +223,161 @@ TEST(Parser, AttrsBinding) { } )"sv; + std::vector Diags; + auto AST = nixf::parse(Src, Diags); + ASSERT_TRUE(AST); + ASSERT_EQ(Diags.size(), 0); + + ASSERT_EQ(AST->kind(), Node::NK_ExprAttrs); + ASSERT_EQ(AST->range().to_string(), "1:0 -> 4:1"); + + auto &attrs = *static_cast(AST.get()); + auto &bindings = attrs.binds()->bindings(); + + ASSERT_EQ(bindings.size(), 2); + + ASSERT_EQ(bindings[0]->range().to_string(), "2:2 -> 2:8"); + ASSERT_EQ(bindings[1]->range().to_string(), "3:2 -> 3:8"); +} + +TEST(Parser, AttrsBindingWriting) { + auto Src = R"( +{ + a +} + )"sv; + + std::vector Diags; + auto AST = nixf::parse(Src, Diags); + + ASSERT_TRUE(AST); + ASSERT_EQ(AST->kind(), Node::NK_ExprAttrs); + + auto &attrs = *static_cast(AST.get()); + ASSERT_EQ(attrs.binds()->bindings().size(), 1); + + const auto &binding = + static_cast(attrs.binds()->bindings()[0].get()); + + ASSERT_EQ(binding->kind(), Node::NK_Binding); + + const auto &path = binding->path(); + ASSERT_EQ(path.names().size(), 1); + + const auto &name = path.names().at(0); + + ASSERT_EQ(name->kind(), Node::NK_Interpolation); + ASSERT_EQ(name->id()->kind(), Node::NK_Identifier); + ASSERT_EQ(name->id()->name(), "a"); + ASSERT_EQ(name->range().to_string(), "2:2 -> 2:3"); +} + +TEST(Parser, AttrsBindingWritingDot) { + auto Src = R"( +{ + a. +} + )"sv; + + std::vector Diags; + auto AST = nixf::parse(Src, Diags); + + ASSERT_TRUE(AST); + ASSERT_EQ(AST->kind(), Node::NK_ExprAttrs); + + auto &attrs = *static_cast(AST.get()); + ASSERT_EQ(attrs.binds()->bindings().size(), 1); + + const auto &binding = + static_cast(attrs.binds()->bindings()[0].get()); + + ASSERT_EQ(binding->kind(), Node::NK_Binding); + + const auto &path = binding->path(); + ASSERT_EQ(path.names().size(), 1); + + const auto &name = path.names().at(0); + + ASSERT_EQ(name->kind(), Node::NK_Interpolation); + ASSERT_EQ(name->id()->kind(), Node::NK_Identifier); + ASSERT_EQ(name->id()->name(), "a"); + ASSERT_EQ(name->range().to_string(), "2:2 -> 2:3"); +} + +TEST(Parser, AttrsNestedInLambda) { + auto Src = R"( +{...}: { + a = { + b = 1; + }; +} + )"sv; + + std::vector Diags; + auto AST = nixf::parse(Src, Diags); + + ASSERT_TRUE(AST); + ASSERT_EQ(AST->kind(), Node::NK_ExprLambda); + ASSERT_EQ(AST->range().to_string(), "1:0 -> 5:1"); + + ASSERT_EQ(Diags.size(), 0); + + const auto &attrs = + static_cast(static_cast(AST.get())->body()); + ASSERT_EQ(attrs->kind(), Node::NK_ExprAttrs); + + const auto &binding = + static_cast(attrs->binds()->bindings()[0].get()); + + ASSERT_EQ(binding->kind(), Node::NK_Binding); + + const auto &nestedAttrs = static_cast(binding->value().get()); + + ASSERT_EQ(nestedAttrs->kind(), Node::NK_ExprAttrs); +} + +TEST(Parser, AttrsNested) { + auto Src = R"( +{ + a = { + b = 1; + }; +} + )"sv; + std::vector Diags; auto AST = nixf::parse(Src, Diags); ASSERT_TRUE(AST); ASSERT_EQ(AST->kind(), Node::NK_ExprAttrs); ASSERT_TRUE(AST->range().lCur().isAt(1, 0, 1)); - ASSERT_TRUE(AST->range().rCur().isAt(4, 1, 22)); + ASSERT_TRUE(AST->range().rCur().isAt(5, 1, 28)); ASSERT_EQ(Diags.size(), 0); // Check the bindings. - auto &B = *static_cast(AST.get()); - assert(B.binds() && "expected bindings"); - ASSERT_EQ(B.binds()->bindings().size(), 2); - ASSERT_TRUE(B.binds()->bindings()[0]->range().lCur().isAt(2, 2, 5)); - ASSERT_TRUE(B.binds()->bindings()[0]->range().rCur().isAt(2, 8, 11)); - - ASSERT_TRUE(B.binds()->bindings()[1]->range().lCur().isAt(3, 2, 14)); - ASSERT_TRUE(B.binds()->bindings()[1]->range().rCur().isAt(3, 8, 20)); + auto &B = static_cast(AST.get())->binds()->bindings(); + ASSERT_EQ(B.size(), 1); + + const auto &Ba = static_cast(B[0].get()); + + ASSERT_EQ(Ba->kind(), Node::NK_Binding); + ASSERT_TRUE(Ba->range().lCur().isAt(2, 2, 5)); + ASSERT_TRUE(Ba->range().rCur().isAt(4, 4, 26)); + + ASSERT_EQ(Ba->path().names().at(0)->id()->name(), "a"); + ASSERT_EQ(Ba->value()->kind(), Node::NK_ExprAttrs); + + auto &BaRHS = + static_cast(Ba->value().get())->binds()->bindings(); + ASSERT_EQ(BaRHS.size(), 1); + ASSERT_EQ(BaRHS[0]->kind(), Node::NK_Binding); + + const auto &BaRHSb = static_cast(BaRHS[0].get()); + + ASSERT_EQ(BaRHSb->range().to_string(), "3:4 -> 3:10"); + ASSERT_EQ(BaRHSb->path().names().at(0)->id()->name(), "b"); + ASSERT_EQ(BaRHSb->value()->kind(), Node::NK_ExprInt); } TEST(Parser, AttrsBindingEarlyExit) { diff --git a/nixd/lib/Controller/Completion.cpp b/nixd/lib/Controller/Completion.cpp index adc4895e6..51950c657 100644 --- a/nixd/lib/Controller/Completion.cpp +++ b/nixd/lib/Controller/Completion.cpp @@ -386,7 +386,6 @@ void Controller::onCompletion(const CompletionParams &Params, const auto AST = CheckDefault(getAST(*TU)); const auto *Desc = AST->descend({Pos, Pos}); - CheckDefault(Desc && Desc->children().empty()); const auto &N = *Desc; const auto &PM = *TU->parentMap();