Index: clangd/CodeComplete.h =================================================================== --- clangd/CodeComplete.h +++ clangd/CodeComplete.h @@ -127,6 +127,9 @@ /// Holds the range of the token we are going to replace with this completion. Range CompletionTokenRange; + /// Whether this completion is for overriding a virtual method. + bool IsOverride = false; + // Scores are used to rank completion items. struct Scores { // The score that items are ranked by. Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -188,6 +188,58 @@ return HeaderFile{std::move(*Resolved), /*Verbatim=*/false}; } +// First traverses all method definitions inside current class/struct/union +// definition. Than traverses base classes to find virtual methods that haven't +// been overriden within current context. +// FIXME(kadircet): Currently we cannot see declarations below completion point. +// It is because Sema gets run only upto completion point. Need to find a +// solution to run it for the whole class/struct/union definition. +static std::vector +getVirtualNonOverridenMethods(const DeclContext *DC, Sema *S) { + const auto *CR = llvm::dyn_cast(DC); + // If not inside a class/struct/union return empty. + if (!CR) + return {}; + // First store overrides within current class. + // These are stored by name to make querying fast in the later step. + llvm::StringMap> Overrides; + for (auto *Method : dyn_cast(DC)->methods()) { + if (!Method->isVirtual()) + continue; + const std::string Name = Method->getNameAsString(); + const auto it = Overrides.find(Name); + if (it == Overrides.end()) + Overrides.insert({Name, {Method}}); + else + it->second.push_back(Method); + } + + std::vector Results; + for (const auto &Base : CR->bases()) { + const auto *BR = Base.getType().getTypePtr()->getAsCXXRecordDecl(); + for (auto *Method : BR->methods()) { + if (!Method->isVirtual()) + continue; + const std::string Name = Method->getNameAsString(); + const auto it = Overrides.find(Name); + bool IsOverriden = false; + if (it != Overrides.end()) + for (auto *MD : it->second) { + // If the method in current body is not an overload of this virtual + // function, that it overrides this one. + if (!S->IsOverload(MD, Method, false)) { + IsOverriden = true; + break; + } + } + if (!IsOverriden) + Results.emplace_back(Method, 0); + } + } + + return Results; +} + /// A code completion result, in clang-native form. /// It may be promoted to a CompletionItem if it's among the top-ranked results. struct CompletionCandidate { @@ -1174,8 +1226,27 @@ : SymbolSlab(); // Merge Sema and Index results, score them, and pick the winners. auto Top = mergeResults(Recorder->Results, IndexResults); - // Convert the results to final form, assembling the expensive strings. CodeCompleteResult Output; + + // Look for override auto-completions. Only triggered if within a + // struct/class/union definition. + const auto Overrides = getVirtualNonOverridenMethods( + Recorder->CCSema->CurContext, Recorder->CCSema); + for (const CodeCompletionResult &CCR : Overrides) { + const CodeCompletionString *SemaCCS = Recorder->codeCompletionString(CCR); + CodeCompletion CC; + CC.ReturnType = getReturnType(*SemaCCS); + getSignature(*SemaCCS, &CC.Signature, &CC.SnippetSuffix, + &CC.RequiredQualifier); + CC.Documentation = + getDocComment(Recorder->CCSema->getASTContext(), CCR, false); + CC.IsOverride = true; + CC.Origin = SymbolOrigin::AST; + CC.Name = SemaCCS->getTypedText(); + CC.Kind = toCompletionItemKind(CCR.Kind, CCR.Declaration); + Output.Completions.push_back(CC); + } + // Convert the results to final form, assembling the expensive strings. for (auto &C : Top) { Output.Completions.push_back(toCodeCompletion(C.first)); Output.Completions.back().Score = C.second; @@ -1183,6 +1254,7 @@ } Output.HasMore = Incomplete; Output.Context = Recorder->CCContext.getKind(); + return Output; } @@ -1387,7 +1459,8 @@ LSP.label = (HeaderInsertion ? Opts.IncludeIndicator.Insert : Opts.IncludeIndicator.NoInsert) + (Opts.ShowOrigins ? "[" + llvm::to_string(Origin) + "]" : "") + - RequiredQualifier + Name + Signature; + (IsOverride ? ReturnType + " " : "") + RequiredQualifier + Name + + Signature + (IsOverride ? " override" : ""); LSP.kind = Kind; LSP.detail = BundleSize > 1 ? llvm::formatv("[{0} overloads]", BundleSize) @@ -1397,7 +1470,10 @@ LSP.documentation = Documentation; LSP.sortText = sortText(Score.Total, Name); LSP.filterText = Name; - LSP.textEdit = {CompletionTokenRange, RequiredQualifier + Name}; + LSP.textEdit = {CompletionTokenRange, + IsOverride ? llvm::formatv("{0} {1}{2} override", ReturnType, + Name, Signature) + : RequiredQualifier + Name}; // Merge continious additionalTextEdits into main edit. The main motivation // behind this is to help LSP clients, it seems most of them are confused when // they are provided with additionalTextEdits that are consecutive to main Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -61,6 +61,7 @@ MATCHER(InsertInclude, "") { return bool(arg.HeaderInsertion); } MATCHER_P(SnippetSuffix, Text, "") { return arg.SnippetSuffix == Text; } MATCHER_P(Origin, OriginSet, "") { return arg.Origin == OriginSet; } +MATCHER(IsOverride, "") { return bool(arg.IsOverride); } // Shorthand for Contains(Named(Name)). Matcher &> Has(std::string Name) { @@ -1648,6 +1649,58 @@ SigDoc("Doc from sema")))); } +TEST(CompletionTest, SuggestOverrides) { + constexpr const char *const Text(R"cpp( + class A { + public: + virtual void vfunc(bool param); + virtual void vfunc(bool param, int p); + void func(bool param); + }; + class B : public A { + virtual void ttt(bool param); + void vfunc(bool param, int p) override; + }; + class C : public B { + public: + void vfunc(bool param) override; + ^ + }; + )cpp"); + const auto Results = completions(Text); + EXPECT_THAT(Results.Completions, + AllOf(Contains(AllOf(Named("vfunc"), IsOverride(), + Labeled("vfunc(bool param, int p)"))), + Contains(AllOf(Named("ttt"), IsOverride(), + Labeled("ttt(bool param)"))), + Not(Contains(AllOf(Named("vfunc"), IsOverride(), + Labeled("vfunc(bool param)")))))); +} + +TEST(CompletionTest, RenderOverride) { + CodeCompletion C; + C.Name = "x"; + C.Signature = "(bool) const"; + C.SnippetSuffix = "(${0:bool})"; + C.ReturnType = "int"; + C.Documentation = "This is x()."; + C.Kind = CompletionItemKind::Method; + C.Score.Total = 1.0; + C.Origin = SymbolOrigin::AST; + C.IsOverride = true; + + CodeCompleteOptions Opts; + Opts.IncludeIndicator.NoInsert = ""; + auto R = C.render(Opts); + EXPECT_EQ(R.label, "int x(bool) const override"); + EXPECT_EQ(R.insertText, "int x(bool) const override"); + EXPECT_EQ(R.insertTextFormat, InsertTextFormat::PlainText); + EXPECT_EQ(R.filterText, "x"); + EXPECT_EQ(R.detail, "int"); + EXPECT_EQ(R.documentation, "This is x()."); + EXPECT_THAT(R.additionalTextEdits, IsEmpty()); +} + } // namespace } // namespace clangd } // namespace clang