daiyousei-qz updated this revision to Diff 522898. daiyousei-qz marked 16 inline comments as done. daiyousei-qz added a comment.
- addressed many review comment Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D150635/new/ https://reviews.llvm.org/D150635 Files: clang-tools-extra/clangd/Config.h clang-tools-extra/clangd/ConfigCompile.cpp clang-tools-extra/clangd/ConfigFragment.h clang-tools-extra/clangd/ConfigYAML.cpp clang-tools-extra/clangd/InlayHints.cpp clang-tools-extra/clangd/Protocol.cpp clang-tools-extra/clangd/Protocol.h clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp clang-tools-extra/clangd/unittests/InlayHintTests.cpp
Index: clang-tools-extra/clangd/unittests/InlayHintTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/InlayHintTests.cpp +++ clang-tools-extra/clangd/unittests/InlayHintTests.cpp @@ -77,6 +77,7 @@ C.InlayHints.Parameters = false; C.InlayHints.DeducedTypes = false; C.InlayHints.Designators = false; + C.InlayHints.EndDefinitionComments = false; return C; } @@ -122,6 +123,17 @@ assertHints(InlayHintKind::Designator, AnnotatedSource, Expected...); } +template <typename... ExpectedHints> +void assertEndDefinitionHints(llvm::StringRef AnnotatedSource, + uint32_t MinLines, ExpectedHints... Expected) { + Config Cfg; + Cfg.InlayHints.EndDefinitionComments = true; + Cfg.InlayHints.EndDefinitionCommentMinLines = MinLines; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + assertHints(InlayHintKind::EndDefinitionComment, AnnotatedSource, + Expected...); +} + TEST(ParameterHints, Smoke) { assertParameterHints(R"cpp( void foo(int param); @@ -1550,6 +1562,196 @@ ExpectedHint{"a: ", "param1"}, ExpectedHint{"b: ", "param2"}, ExpectedHint{"c: ", "param3"}); } + +TEST(EndDefinitionHints, Functions) { + assertEndDefinitionHints(R"cpp( + $foo[[int foo() { + return 41; + }]] + + template<int X> + $bar[[int bar() { return X; }]] + + // No hint because this isn't a definition + int buz(); + )cpp", + 0, ExpectedHint{" // foo", "foo"}, + ExpectedHint{" // bar", "bar"}); +} + +TEST(EndDefinitionHints, Methods) { + assertEndDefinitionHints(R"cpp( + class Test { + public: + // No hint because there's no function body + Test() = default; + + $dtor[[~Test() {}]] + + $method1[[void method1() {}]] + + // No hint because this isn't a definition + void method2(); + + template <typename T> + $method3[[void method3() {}]] + + // No hint because this isn't a definition + template <typename T> + void method4(); + } x; + + $method2[[void Test::method2() {}]] + + template <typename T> + $method4[[void Test::method4() {}]] + )cpp", + 0, ExpectedHint{" // ~Test", "dtor"}, + ExpectedHint{" // method1", "method1"}, + ExpectedHint{" // method3", "method3"}, + ExpectedHint{" // Test::method2", "method2"}, + ExpectedHint{" // Test::method4", "method4"}); +} + +TEST(EndDefinitionHints, OverloadedOperators) { + assertEndDefinitionHints(R"cpp( + struct S { + $opId[[S operator+(int) const { + return *this; + }]] + + $opBool[[operator bool() const { + return true; + }]] + + // No hint because there's no function body + operator int() const = delete; + + // No hint because this isn't a definition + operator float() const; + } x; + + $opEq[[bool operator==(const S&, const S&) { + return true; + }]] + )cpp", + 0, ExpectedHint{" // operator+", "opId"}, + ExpectedHint{" // operator bool", "opBool"}, + ExpectedHint{" // operator==", "opEq"}); +} + +TEST(EndDefinitionHints, Namespaces) { + assertEndDefinitionHints( + R"cpp( + $anon[[namespace { + void foo(); + }]] + + $ns[[namespace ns { + void bar(); + }]] + )cpp", + 0, ExpectedHint{" // namespace", "anon"}, + ExpectedHint{" // namespace ns", "ns"}); +} + +TEST(EndDefinitionHints, Types) { + assertEndDefinitionHints( + R"cpp( + $S[[struct S { + };]] + + $C[[class C { + };]] + + $U[[union U { + };]] + + $E1[[enum E1 { + };]] + + $E2[[enum class E2 { + };]] + )cpp", + 0, ExpectedHint{" // struct S", "S"}, ExpectedHint{" // class C", "C"}, + ExpectedHint{" // union U", "U"}, ExpectedHint{" // enum E1", "E1"}, + ExpectedHint{" // enum class E2", "E2"}); +} + +TEST(EndDefinitionHints, BundledTypeVariableDecl) { + assertEndDefinitionHints( + R"cpp( + // No hint because we have a declarator right after '}' + struct { + int x; + } s; + + // Rare case, but yes we'll have a hint here. + $anon[[struct { + int x; + }]] + + s2; + )cpp", + 0, ExpectedHint{" // struct", "anon"}); +} + +TEST(EndDefinitionHints, TrailingSemicolon) { + assertEndDefinitionHints(R"cpp( + // The hint is placed after the trailing ';' + $S1[[struct S1 { + } ;]] + + // The hint is always placed in the same line with the closing '}'. + // So in this case where ';' is missing, it is attached to '}'. + $S2[[struct S2 { + }]] + + ; + + // No hint because only one trailing ';' is allowed + struct S3 { + };; + + // No hint becaus trailing ';' is only allowed for class/struct/union/enum + void foo() { + }; + )cpp", + 0, ExpectedHint{" // struct S1", "S1"}, + ExpectedHint{" // struct S2", "S2"}); +} + +TEST(EndDefinitionHints, TrailingText) { + assertEndDefinitionHints(R"cpp( + $S1[[struct S1 { + } ;]] + + // No hint for S2 because of the trailing comment + struct S2 { + }; /* Put anything here */ + + // No hint for S3 because of the trailing source code + struct S3 {}; $S4[[struct S4 {};]] + + // No hint for ns because of the trailing comment + namespace ns { + + } // namespace ns + )cpp", + 0, ExpectedHint{" // struct S1", "S1"}, + ExpectedHint{" // struct S4", "S4"}); +} + +TEST(EndDefinitionHints, MinLineConfig) { + assertEndDefinitionHints(R"cpp( + struct S1 {}; + + $S2[[struct S2 { + };]] + )cpp", + 2, ExpectedHint{" // struct S2", "S2"}); +} + // FIXME: Low-hanging fruit where we could omit a type hint: // - auto x = TypeName(...); // - auto x = (TypeName) (...); Index: clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp +++ clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp @@ -236,6 +236,8 @@ InlayHints: Enabled: No ParameterNames: Yes + EndDefinitionComments: Yes + EndDefinitionCommentMinLines: 10 )yaml"); auto Results = Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); @@ -244,6 +246,10 @@ EXPECT_THAT(Results[0].InlayHints.Enabled, llvm::ValueIs(val(false))); EXPECT_THAT(Results[0].InlayHints.ParameterNames, llvm::ValueIs(val(true))); EXPECT_EQ(Results[0].InlayHints.DeducedTypes, std::nullopt); + EXPECT_THAT(Results[0].InlayHints.EndDefinitionComments, + llvm::ValueIs(val(true))); + EXPECT_THAT(Results[0].InlayHints.EndDefinitionCommentMinLines, + llvm::ValueIs(val(10))); } TEST(ParseYAML, IncludesIgnoreHeader) { Index: clang-tools-extra/clangd/Protocol.h =================================================================== --- clang-tools-extra/clangd/Protocol.h +++ clang-tools-extra/clangd/Protocol.h @@ -1647,6 +1647,16 @@ /// This is a clangd extension. Designator = 3, + /// A hint after function, type or namespace definition, indicating the + /// defined symbol name of the definition. + /// + /// An example of a decl name hint in this position: + /// void func() { + /// } ^ + /// Uses comment-like syntax like "/* func */". + /// This is a clangd extension. + EndDefinitionComment = 4, + /// Other ideas for hints that are not currently implemented: /// /// * Chaining hints, showing the types of intermediate expressions Index: clang-tools-extra/clangd/Protocol.cpp =================================================================== --- clang-tools-extra/clangd/Protocol.cpp +++ clang-tools-extra/clangd/Protocol.cpp @@ -1435,6 +1435,8 @@ case InlayHintKind::Parameter: return 2; case InlayHintKind::Designator: // This is an extension, don't serialize. + case InlayHintKind::EndDefinitionComment: // This is an extension, don't + // serialize. return nullptr; } llvm_unreachable("Unknown clang.clangd.InlayHintKind"); @@ -1468,6 +1470,8 @@ return "type"; case InlayHintKind::Designator: return "designator"; + case InlayHintKind::EndDefinitionComment: + return "end-definition-comment"; } llvm_unreachable("Unknown clang.clangd.InlayHintKind"); }; Index: clang-tools-extra/clangd/InlayHints.cpp =================================================================== --- clang-tools-extra/clangd/InlayHints.cpp +++ clang-tools-extra/clangd/InlayHints.cpp @@ -274,6 +274,42 @@ addReturnTypeHint(D, FTL.getRParenLoc()); } } + if (Cfg.InlayHints.EndDefinitionComments && + D->isThisDeclarationADefinition()) { + addEndDefinitionCommentHint(*D, "", false); + } + return true; + } + + bool VisitEnumDecl(EnumDecl *D) { + if (Cfg.InlayHints.EndDefinitionComments && + D->isThisDeclarationADefinition()) { + StringRef DeclPrefix; + if (!D->isScoped()) { + DeclPrefix = "enum"; + } else if (D->isScopedUsingClassTag()) { + DeclPrefix = "enum class"; + } else { + DeclPrefix = "enum struct"; + } + + addEndDefinitionCommentHint(*D, DeclPrefix, true); + } + return true; + } + + bool VisitRecordDecl(RecordDecl *D) { + if (Cfg.InlayHints.EndDefinitionComments && + D->isThisDeclarationADefinition()) { + addEndDefinitionCommentHint(*D, D->getKindName(), true); + } + return true; + } + + bool VisitNamespaceDecl(NamespaceDecl *D) { + if (Cfg.InlayHints.EndDefinitionComments) { + addEndDefinitionCommentHint(*D, "namespace", false); + } return true; } @@ -539,6 +575,36 @@ return SourcePrefix.endswith("/*"); } + // Check if the `D` has no trailing characters after the last line of the + // definition, except for whitespace and possibly a ';'. Since the hint should + // be displayed after the trailing whitespaces, the number of characters to + // skip is returned through `SkipChars` + bool shouldHintEndDefinitionComment(const NamedDecl &D, bool SkipSemicolon, + size_t &SkipChars) { + auto &SM = AST.getSourceManager(); + auto FileLoc = SM.getFileLoc(D.getEndLoc()); + auto Decomposed = SM.getDecomposedLoc(FileLoc); + if (Decomposed.first != MainFileID) + return false; + + StringRef Whitespaces = " \t\v"; + StringRef DeclRangeSuffix = MainFileBuf.substr(Decomposed.second); + if (!DeclRangeSuffix.consume_front("}")) + return false; + + // By default, the hint is attached to '}' + SkipChars = 0; + StringRef HintRangeSuffix = DeclRangeSuffix.ltrim(Whitespaces); + if (SkipSemicolon && HintRangeSuffix.consume_front(";")) { + // Attach the hint to ';' + SkipChars = DeclRangeSuffix.size() - HintRangeSuffix.size(); + HintRangeSuffix = HintRangeSuffix.ltrim(Whitespaces); + } + + return HintRangeSuffix.empty() || HintRangeSuffix.starts_with("\n") || + HintRangeSuffix.starts_with("\r\n"); + }; + // If "E" spells a single unqualified identifier, return that name. // Otherwise, return an empty string. static StringRef getSpelledIdentifier(const Expr *E) { @@ -616,6 +682,16 @@ void addInlayHint(SourceRange R, HintSide Side, InlayHintKind Kind, llvm::StringRef Prefix, llvm::StringRef Label, llvm::StringRef Suffix) { + auto LSPRange = getHintRange(R); + if (!LSPRange) + return; + + addInlayHint(*LSPRange, Side, Kind, Prefix, Label, Suffix); + } + + void addInlayHint(Range LSPRange, HintSide Side, InlayHintKind Kind, + llvm::StringRef Prefix, llvm::StringRef Label, + llvm::StringRef Suffix) { // We shouldn't get as far as adding a hint if the category is disabled. // We'd like to disable as much of the analysis as possible above instead. // Assert in debug mode but add a dynamic check in production. @@ -631,20 +707,18 @@ CHECK_KIND(Parameter, Parameters); CHECK_KIND(Type, DeducedTypes); CHECK_KIND(Designator, Designators); + CHECK_KIND(EndDefinitionComment, EndDefinitionComments); #undef CHECK_KIND } - auto LSPRange = getHintRange(R); - if (!LSPRange) - return; - Position LSPPos = Side == HintSide::Left ? LSPRange->start : LSPRange->end; + Position LSPPos = Side == HintSide::Left ? LSPRange.start : LSPRange.end; if (RestrictRange && (LSPPos < RestrictRange->start || !(LSPPos < RestrictRange->end))) return; bool PadLeft = Prefix.consume_front(" "); bool PadRight = Suffix.consume_back(" "); Results.push_back(InlayHint{LSPPos, (Prefix + Label + Suffix).str(), Kind, - PadLeft, PadRight, *LSPRange}); + PadLeft, PadRight, LSPRange}); } // Get the range of the main file that *exactly* corresponds to R. @@ -699,6 +773,41 @@ /*Prefix=*/"", Text, /*Suffix=*/"="); } + void addEndDefinitionCommentHint(const NamedDecl &D, StringRef DeclPrefix, + bool SkipSemicolon) { + size_t SkippedChars = 0; + if (!shouldHintEndDefinitionComment(D, SkipSemicolon, SkippedChars)) + return; + + // Note this range doesn't include the trailing ';' in type definitions. + // So we have to add SkippedChars to the end character. + SourceRange R = D.getSourceRange(); + auto LSPRange = getHintRange(R); + if (!LSPRange || + static_cast<uint32_t>(LSPRange->end.line - LSPRange->start.line) + 1 < + Cfg.InlayHints.EndDefinitionCommentMinLines) + return; + + LSPRange->end.character += SkippedChars; + + std::string Label = DeclPrefix.str(); + if (const auto *FD = dyn_cast_or_null<FunctionDecl>(&D)) { + // `getSimpleName` cannot handle operator overloading, so we handle it + // differently. + assert(Label.empty()); + Label += printName(AST, D); + } else { + StringRef Name = getSimpleName(D); + if (!Name.empty()) { + Label += ' '; + Label += Name; + } + } + + addInlayHint(*LSPRange, HintSide::Right, + InlayHintKind::EndDefinitionComment, " // ", Label, ""); + } + std::vector<InlayHint> &Results; ASTContext &AST; const syntax::TokenBuffer &Tokens; Index: clang-tools-extra/clangd/ConfigYAML.cpp =================================================================== --- clang-tools-extra/clangd/ConfigYAML.cpp +++ clang-tools-extra/clangd/ConfigYAML.cpp @@ -254,10 +254,18 @@ if (auto Value = boolValue(N, "Designators")) F.Designators = *Value; }); + Dict.handle("EndDefinitionComments", [&](Node &N) { + if (auto Value = boolValue(N, "EndDefinitionComments")) + F.EndDefinitionComments = *Value; + }); Dict.handle("TypeNameLimit", [&](Node &N) { if (auto Value = uint32Value(N, "TypeNameLimit")) F.TypeNameLimit = *Value; }); + Dict.handle("EndDefinitionCommentMinLines", [&](Node &N) { + if (auto Value = uint32Value(N, "EndDefinitionCommentMinLines")) + F.EndDefinitionCommentMinLines = *Value; + }); Dict.parse(N); } Index: clang-tools-extra/clangd/ConfigFragment.h =================================================================== --- clang-tools-extra/clangd/ConfigFragment.h +++ clang-tools-extra/clangd/ConfigFragment.h @@ -322,8 +322,12 @@ std::optional<Located<bool>> DeducedTypes; /// Show designators in aggregate initialization. std::optional<Located<bool>> Designators; + /// Show defined symbol names at the end of a definition. + std::optional<Located<bool>> EndDefinitionComments; /// Limit the length of type name hints. (0 means no limit) std::optional<Located<uint32_t>> TypeNameLimit; + /// + std::optional<Located<uint32_t>> EndDefinitionCommentMinLines; }; InlayHintsBlock InlayHints; }; Index: clang-tools-extra/clangd/ConfigCompile.cpp =================================================================== --- clang-tools-extra/clangd/ConfigCompile.cpp +++ clang-tools-extra/clangd/ConfigCompile.cpp @@ -611,11 +611,21 @@ Out.Apply.push_back([Value(**F.Designators)](const Params &, Config &C) { C.InlayHints.Designators = Value; }); + if (F.EndDefinitionComments) + Out.Apply.push_back( + [Value(**F.EndDefinitionComments)](const Params &, Config &C) { + C.InlayHints.EndDefinitionComments = Value; + }); if (F.TypeNameLimit) Out.Apply.push_back( [Value(**F.TypeNameLimit)](const Params &, Config &C) { C.InlayHints.TypeNameLimit = Value; }); + if (F.EndDefinitionCommentMinLines) + Out.Apply.push_back( + [Value(**F.EndDefinitionCommentMinLines)](const Params &, Config &C) { + C.InlayHints.EndDefinitionCommentMinLines = Value; + }); } constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error; Index: clang-tools-extra/clangd/Config.h =================================================================== --- clang-tools-extra/clangd/Config.h +++ clang-tools-extra/clangd/Config.h @@ -147,8 +147,13 @@ bool Parameters = true; bool DeducedTypes = true; bool Designators = true; + bool EndDefinitionComments = true; // Limit the length of type names in inlay hints. (0 means no limit) uint32_t TypeNameLimit = 32; + + // The minimal number of lines of the definition to show the + // end-definition comment hints. + uint32_t EndDefinitionCommentMinLines = 2; } InlayHints; };
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits