[clang-tools-extra] r340530 - [clangd] Suggest code-completions for overriding base class virtual methods.
Kadir Cetinkaya via cfe-commits
cfe-commits at lists.llvm.org
Thu Aug 23 06:14:50 PDT 2018
Author: kadircet
Date: Thu Aug 23 06:14:50 2018
New Revision: 340530
URL: http://llvm.org/viewvc/llvm-project?rev=340530&view=rev
Log:
[clangd] Suggest code-completions for overriding base class virtual methods.
Summary:
Whenever a code-completion is triggered within a class/struct/union looks at
base classes and figures out non-overriden virtual functions. Than suggests
completions for those.
Reviewers: ilya-biryukov, hokein, ioeric
Reviewed By: hokein
Subscribers: MaskRay, jkorous, arphaman, cfe-commits
Differential Revision: https://reviews.llvm.org/D50898
Modified:
clang-tools-extra/trunk/clangd/CodeComplete.cpp
clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp
Modified: clang-tools-extra/trunk/clangd/CodeComplete.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/CodeComplete.cpp?rev=340530&r1=340529&r2=340530&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/CodeComplete.cpp (original)
+++ clang-tools-extra/trunk/clangd/CodeComplete.cpp Thu Aug 23 06:14:50 2018
@@ -188,6 +188,55 @@ static llvm::Expected<HeaderFile> toHead
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<CodeCompletionResult>
+getNonOverridenMethodCompletionResults(const DeclContext *DC, Sema *S) {
+ const auto *CR = llvm::dyn_cast<CXXRecordDecl>(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<std::vector<FunctionDecl *>> Overrides;
+ for (auto *Method : CR->methods()) {
+ if (!Method->isVirtual())
+ continue;
+ Overrides[Method->getName()].push_back(Method);
+ }
+
+ std::vector<CodeCompletionResult> Results;
+ for (const auto &Base : CR->bases()) {
+ const auto *BR = Base.getType().getTypePtr()->getAsCXXRecordDecl();
+ if (!BR)
+ continue;
+ for (auto *Method : BR->methods()) {
+ if (!Method->isVirtual())
+ continue;
+ const auto it = Overrides.find(Method->getName());
+ 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 {
@@ -196,6 +245,9 @@ struct CompletionCandidate {
const CodeCompletionResult *SemaResult = nullptr;
const Symbol *IndexResult = nullptr;
+ // States whether this item is an override suggestion.
+ bool IsOverride = false;
+
// Returns a token identifying the overload set this is part of.
// 0 indicates it's not part of any overload set.
size_t overloadSet() const {
@@ -352,6 +404,8 @@ struct CodeCompletionBuilder {
Completion.Documentation = getDocComment(ASTCtx, *C.SemaResult,
/*CommentsFromHeader=*/false);
}
+ if (C.IsOverride)
+ S.OverrideSuffix = true;
}
CodeCompletion build() {
@@ -359,6 +413,12 @@ struct CodeCompletionBuilder {
Completion.Signature = summarizeSignature();
Completion.SnippetSuffix = summarizeSnippet();
Completion.BundleSize = Bundled.size();
+ if (summarizeOverride()) {
+ Completion.Name = Completion.ReturnType + ' ' +
+ std::move(Completion.Name) +
+ std::move(Completion.Signature) + " override";
+ Completion.Signature.clear();
+ }
return std::move(Completion);
}
@@ -367,6 +427,7 @@ private:
std::string SnippetSuffix;
std::string Signature;
std::string ReturnType;
+ bool OverrideSuffix;
};
// If all BundledEntrys have the same value for a property, return it.
@@ -379,6 +440,14 @@ private:
return &(B->*Member);
}
+ template <bool BundledEntry::*Member> const bool *onlyValue() const {
+ auto B = Bundled.begin(), E = Bundled.end();
+ for (auto I = B + 1; I != E; ++I)
+ if (I->*Member != B->*Member)
+ return nullptr;
+ return &(B->*Member);
+ }
+
std::string summarizeReturnType() const {
if (auto *RT = onlyValue<&BundledEntry::ReturnType>())
return *RT;
@@ -406,6 +475,12 @@ private:
return "(â¦)";
}
+ bool summarizeOverride() const {
+ if (auto *OverrideSuffix = onlyValue<&BundledEntry::OverrideSuffix>())
+ return *OverrideSuffix;
+ return false;
+ }
+
ASTContext &ASTCtx;
CodeCompletion Completion;
SmallVector<BundledEntry, 1> Bundled;
@@ -1181,10 +1256,14 @@ private:
auto IndexResults = (Opts.Index && allowIndex(Recorder->CCContext))
? queryIndex()
: 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.
+ // Merge Sema, Index and Override results, score them, and pick the
+ // winners.
+ const auto Overrides = getNonOverridenMethodCompletionResults(
+ Recorder->CCSema->CurContext, Recorder->CCSema);
+ auto Top = mergeResults(Recorder->Results, IndexResults, Overrides);
CodeCompleteResult Output;
+
+ // 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;
@@ -1192,6 +1271,7 @@ private:
}
Output.HasMore = Incomplete;
Output.Context = Recorder->CCContext.getKind();
+
return Output;
}
@@ -1218,20 +1298,24 @@ private:
return std::move(ResultsBuilder).build();
}
- // Merges Sema and Index results where possible, to form CompletionCandidates.
- // Groups overloads if desired, to form CompletionCandidate::Bundles.
- // The bundles are scored and top results are returned, best to worst.
+ // Merges Sema, Index and Override results where possible, to form
+ // CompletionCandidates. Groups overloads if desired, to form
+ // CompletionCandidate::Bundles. The bundles are scored and top results are
+ // returned, best to worst.
std::vector<ScoredBundle>
- mergeResults(const std::vector<CodeCompletionResult> &SemaResults,
- const SymbolSlab &IndexResults) {
+ mergeResults(const std::vector<CodeCompletionResult> &SemaResults,
+ const SymbolSlab &IndexResults,
+ const std::vector<CodeCompletionResult> &OverrideResults) {
trace::Span Tracer("Merge and score results");
std::vector<CompletionCandidate::Bundle> Bundles;
llvm::DenseMap<size_t, size_t> BundleLookup;
auto AddToBundles = [&](const CodeCompletionResult *SemaResult,
- const Symbol *IndexResult) {
+ const Symbol *IndexResult,
+ bool IsOverride = false) {
CompletionCandidate C;
C.SemaResult = SemaResult;
C.IndexResult = IndexResult;
+ C.IsOverride = IsOverride;
C.Name = IndexResult ? IndexResult->Name : Recorder->getName(*SemaResult);
if (auto OverloadSet = Opts.BundleOverloads ? C.overloadSet() : 0) {
auto Ret = BundleLookup.try_emplace(OverloadSet, Bundles.size());
@@ -1258,6 +1342,13 @@ private:
// Emit all Sema results, merging them with Index results if possible.
for (auto &SemaResult : Recorder->Results)
AddToBundles(&SemaResult, CorrespondingIndexResult(SemaResult));
+ // Handle OverrideResults the same way we deal with SemaResults. Since these
+ // results use the same structs as a SemaResult it is safe to do that, but
+ // we need to make sure we dont' duplicate things in future if Sema starts
+ // to provide them as well.
+ for (auto &OverrideResult : OverrideResults)
+ AddToBundles(&OverrideResult, CorrespondingIndexResult(OverrideResult),
+ true);
// Now emit any Index-only results.
for (const auto &IndexResult : IndexResults) {
if (UsedIndexResults.count(&IndexResult))
Modified: clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp?rev=340530&r1=340529&r2=340530&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp (original)
+++ clang-tools-extra/trunk/unittests/clangd/CodeCompleteTests.cpp Thu Aug 23 06:14:50 2018
@@ -1680,6 +1680,31 @@ TEST(CompletionTest, CompletionFunctionA
}
}
+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) const;
+ 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(Labeled("void vfunc(bool param, int p) override")),
+ Contains(Labeled("void ttt(bool param) const override")),
+ Not(Contains(Labeled("void vfunc(bool param) override")))));
+}
+
} // namespace
} // namespace clangd
} // namespace clang
More information about the cfe-commits
mailing list