-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[ClangRepl] Type Directed Code Completion #67349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-clang ChangesDifferential Revision: https://reviews.llvm.org/D159128 Patch is 20.30 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/67349.diff 5 Files Affected:
diff --git a/clang/include/clang/Interpreter/CodeCompletion.h b/clang/include/clang/Interpreter/CodeCompletion.h
index 9adcdf0dc3afac6..d78072cec1a2666 100644
--- a/clang/include/clang/Interpreter/CodeCompletion.h
+++ b/clang/include/clang/Interpreter/CodeCompletion.h
@@ -23,8 +23,12 @@ namespace clang {
class CodeCompletionResult;
class CompilerInstance;
-void codeComplete(CompilerInstance *InterpCI, llvm::StringRef Content,
- unsigned Line, unsigned Col, const CompilerInstance *ParentCI,
- std::vector<std::string> &CCResults);
+struct ReplCodeCompletion {
+ ReplCodeCompletion() = default;
+ std::string Prefix;
+ void codeComplete(CompilerInstance *InterpCI, llvm::StringRef Content,
+ unsigned Line, unsigned Col, const CompilerInstance *ParentCI,
+ std::vector<std::string> &CCResults);
+};
} // namespace clang
#endif
diff --git a/clang/lib/Interpreter/CodeCompletion.cpp b/clang/lib/Interpreter/CodeCompletion.cpp
index c40e11b9d1ece0a..9c812c28f677726 100644
--- a/clang/lib/Interpreter/CodeCompletion.cpp
+++ b/clang/lib/Interpreter/CodeCompletion.cpp
@@ -23,6 +23,9 @@
#include "clang/Sema/CodeCompleteConsumer.h"
#include "clang/Sema/CodeCompleteOptions.h"
#include "clang/Sema/Sema.h"
+#include "llvm/Support/Debug.h"
+#define DEBUG_TYPE "REPLCC"
+
namespace clang {
@@ -37,12 +40,22 @@ clang::CodeCompleteOptions getClangCompleteOpts() {
return Opts;
}
+class CodeCompletionSubContext {
+public:
+ virtual ~CodeCompletionSubContext(){};
+ virtual void
+ HandleCodeCompleteResults(class Sema &S, CodeCompletionResult *InResults,
+ unsigned NumResults,
+ std::vector<std::string> &Results) = 0;
+};
+
class ReplCompletionConsumer : public CodeCompleteConsumer {
public:
- ReplCompletionConsumer(std::vector<std::string> &Results)
+ ReplCompletionConsumer(std::vector<std::string> &Results, ReplCodeCompletion& CC)
: CodeCompleteConsumer(getClangCompleteOpts()),
CCAllocator(std::make_shared<GlobalCodeCompletionAllocator>()),
- CCTUInfo(CCAllocator), Results(Results){};
+ CCTUInfo(CCAllocator), Results(Results), CC(CC) {
+ }
void ProcessCodeCompleteResults(class Sema &S, CodeCompletionContext Context,
CodeCompletionResult *InResults,
@@ -56,26 +69,90 @@ class ReplCompletionConsumer : public CodeCompleteConsumer {
std::shared_ptr<GlobalCodeCompletionAllocator> CCAllocator;
CodeCompletionTUInfo CCTUInfo;
std::vector<std::string> &Results;
+ ReplCodeCompletion& CC;
};
void ReplCompletionConsumer::ProcessCodeCompleteResults(
class Sema &S, CodeCompletionContext Context,
CodeCompletionResult *InResults, unsigned NumResults) {
- for (unsigned I = 0; I < NumResults; ++I) {
- auto &Result = InResults[I];
- switch (Result.Kind) {
- case CodeCompletionResult::RK_Declaration:
- if (auto *ID = Result.Declaration->getIdentifier()) {
- Results.push_back(ID->getName().str());
+
+ // auto& a = S.Context.Idents.get("f1");
+ // LLVM_DEBUG(llvm::dbgs() << "\n FFFFF111111 : " << a.getName() << "\n" );
+ auto Prefix = S.getPreprocessor().getCodeCompletionFilter();
+ CC.Prefix = Prefix;
+ switch (Context.getKind()) {
+ case CodeCompletionContext::CCC_DotMemberAccess: {
+ // FIXME: check BaseType is dependent, or from a typo
+ auto BaseType = Context.getBaseType();
+ LLVM_DEBUG(llvm::dbgs() << "BaseType : " << BaseType.getAsString() << "\n" );
+ for (unsigned I = 0; I < NumResults; ++I) {
+ auto &Result = InResults[I];
+ switch (Result.Kind) {
+ case CodeCompletionResult::RK_Declaration:
+ if (auto *ID = Result.Declaration->getIdentifier()) {
+ if (const auto *Fun = llvm::dyn_cast<CXXMethodDecl>(Result.Declaration)) {
+ if (Fun->getParent()->getCanonicalDecl() == BaseType->getAsCXXRecordDecl()->getCanonicalDecl()) {
+ LLVM_DEBUG(llvm::dbgs() << "[In HandleCodeCompleteDOT] Name : " << ID->getName() << "\n");
+ Results.push_back(ID->getName().str());
+ }
+ }
+ }
+ break;
+ default:
+ break;
}
- break;
- case CodeCompletionResult::RK_Keyword:
- Results.push_back(Result.Keyword);
- break;
- default:
- break;
}
+ break;
}
+ default:
+ auto PreferredType = Context.getPreferredType();
+ for (unsigned I = 0; I < NumResults; I++) {
+ auto &Result = InResults[I];
+ switch (Result.Kind) {
+ case CodeCompletionResult::RK_Declaration:
+ if (Result.Hidden) {
+ break;
+ }
+ if (!Result.Declaration->getDeclName().isIdentifier() || !Result.Declaration->getName().startswith(Prefix)) {
+ break;
+ }
+ if (!PreferredType.isNull()) {
+ if (auto* VD = dyn_cast<VarDecl>(Result.Declaration)) {
+ auto ArgumentType = VD->getType();
+ if (PreferredType->isReferenceType()) {
+ QualType RT = PreferredType->castAs<ReferenceType>()->getPointeeType();
+ Sema::ReferenceConversions RefConv;
+ Sema::ReferenceCompareResult RefRelationship =
+ S.CompareReferenceRelationship(SourceLocation(), RT, ArgumentType,
+ &RefConv);
+ switch (RefRelationship) {
+ case Sema::Ref_Compatible:
+ case Sema::Ref_Related:
+ Results.push_back(VD->getName().str());
+ break;
+ case Sema::Ref_Incompatible:
+ break;
+ }
+ } else if (S.Context.hasSameType(ArgumentType, PreferredType)) {
+ Results.push_back(VD->getName().str());
+ }
+ }
+ } else
+ Results.push_back(Result.Declaration->getName().str());
+ break;
+ case CodeCompletionResult::RK_Keyword:
+ // add keyword to the completion results only if we are in a type-aware situation.
+ if (!Context.getBaseType().isNull() || !PreferredType.isNull())
+ break;
+ if (StringRef(Result.Keyword).startswith(Prefix))
+ Results.push_back(Result.Keyword);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ std::sort(Results.begin(), Results.end());
}
class IncrementalSyntaxOnlyAction : public SyntaxOnlyAction {
@@ -101,8 +178,8 @@ class ExternalSource : public clang::ExternalASTSource {
ASTContext &ParentASTCtxt, FileManager &ParentFM);
bool FindExternalVisibleDeclsByName(const DeclContext *DC,
DeclarationName Name) override;
- void
- completeVisibleDeclsMap(const clang::DeclContext *childDeclContext) override;
+ // void CompleteType(TagDecl *Tag) override;
+ void completeVisibleDeclsMap(const clang::DeclContext *childDeclContext) override;
};
// This method is intended to set up `ExternalASTSource` to the running
@@ -134,6 +211,7 @@ ExternalSource::ExternalSource(ASTContext &ChildASTCtxt, FileManager &ChildFM,
bool ExternalSource::FindExternalVisibleDeclsByName(const DeclContext *DC,
DeclarationName Name) {
+
IdentifierTable &ParentIdTable = ParentASTCtxt.Idents;
auto ParentDeclName =
@@ -148,6 +226,10 @@ bool ExternalSource::FindExternalVisibleDeclsByName(const DeclContext *DC,
return false;
}
+// void ExternalSource::CompleteType(TagDecl *Tag) {
+// LLVM_DEBUG(llvm::dbgs() << "\n CompleteType " << Tag->getName() << ":\n");
+// }
+
void ExternalSource::completeVisibleDeclsMap(
const DeclContext *ChildDeclContext) {
assert(ChildDeclContext && ChildDeclContext == ChildTUDeclCtxt &&
@@ -162,12 +244,36 @@ void ExternalSource::completeVisibleDeclsMap(
if (NamedDecl *Decl = llvm::dyn_cast<NamedDecl>(IDeclContext)) {
if (auto DeclOrErr = Importer->Import(Decl)) {
if (NamedDecl *importedNamedDecl =
- llvm::dyn_cast<NamedDecl>(*DeclOrErr)) {
+ llvm::dyn_cast<NamedDecl>(*DeclOrErr)) {
+ // LLVM_DEBUG(llvm::dbgs() << "\nImporting : " << importedNamedDecl->getName() << "\n");
SetExternalVisibleDeclsForName(ChildDeclContext,
importedNamedDecl->getDeclName(),
importedNamedDecl);
- }
+ if (auto *Record = llvm::dyn_cast<CXXRecordDecl>(importedNamedDecl)) {
+ if (auto Err = Importer->ImportDefinition(Decl))
+ consumeError(std::move(Err));
+ // LLVM_DEBUG(llvm::dbgs() << "\nHello :\n");
+ // Record->getTypeForDecl()->dump();
+ Record->setHasLoadedFieldsFromExternalStorage(true);
+ // auto ToOrErr = Importer->Import(->getTypeForDecl());
+ // if (!ToOrErr) {
+ // consumeError(std::move(ToOrErr.takeError()));
+ // }
+ LLVM_DEBUG(llvm::dbgs()
+ << "\nCXXRecrod : " << Record->getName()
+ << " size(methods): " << std::distance(Record->method_begin(), Record->method_end())
+ << " has def?: " << Record->hasDefinition()
+ << " # (methods): " << std::distance(Record->getDefinition()->method_begin(), Record->getDefinition()->method_end())
+ << "\n");
+ for (auto *Meth : Record->methods()) {
+ SetExternalVisibleDeclsForName(ChildDeclContext, Meth->getDeclName(), Meth);
+ // if (Meth->getDeclName().isIdentifier()) {
+ // LLVM_DEBUG(llvm::dbgs() << "CXXRecrod Method: " << Meth->getName() << "\n");
+ // }
+ }
+ }
+ }
} else {
llvm::consumeError(DeclOrErr.takeError());
}
@@ -177,11 +283,12 @@ void ExternalSource::completeVisibleDeclsMap(
}
}
-void codeComplete(CompilerInstance *InterpCI, llvm::StringRef Content,
+
+void ReplCodeCompletion::codeComplete(CompilerInstance *InterpCI, llvm::StringRef Content,
unsigned Line, unsigned Col, const CompilerInstance *ParentCI,
std::vector<std::string> &CCResults) {
auto DiagOpts = DiagnosticOptions();
- auto consumer = ReplCompletionConsumer(CCResults);
+ auto consumer = ReplCompletionConsumer(CCResults, *this);
auto diag = InterpCI->getDiagnosticsPtr();
std::unique_ptr<ASTUnit> AU(ASTUnit::LoadFromCompilerInvocationAction(
diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index 7dd96e3155e1dd4..522855640921554 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -51,6 +51,8 @@
#include <vector>
#include "OpenCLBuiltins.inc"
+#include "llvm/Support/Debug.h"
+#define DEBUG_TYPE "REPLCC"
using namespace clang;
using namespace sema;
@@ -2748,6 +2750,8 @@ bool Sema::LookupParsedName(LookupResult &R, Scope *S, CXXScopeSpec *SS,
return false;
}
+ Context.getTranslationUnitDecl()->lookups();
+
// Perform unqualified name lookup starting in the given scope.
return LookupName(R, S, AllowBuiltinCreation);
}
diff --git a/clang/tools/clang-repl/ClangRepl.cpp b/clang/tools/clang-repl/ClangRepl.cpp
index 51741fd1a27ef4a..b0d40d5adcf49b3 100644
--- a/clang/tools/clang-repl/ClangRepl.cpp
+++ b/clang/tools/clang-repl/ClangRepl.cpp
@@ -15,6 +15,8 @@
#include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/Interpreter/CodeCompletion.h"
#include "clang/Interpreter/Interpreter.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Sema/Sema.h"
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/LineEditor/LineEditor.h"
@@ -115,22 +117,12 @@ ReplListCompleter::operator()(llvm::StringRef Buffer, size_t Pos,
return {};
}
-
- codeComplete(
- const_cast<clang::CompilerInstance *>((*Interp)->getCompilerInstance()),
- Buffer, Lines, Pos + 1, MainInterp.getCompilerInstance(), Results);
-
- size_t space_pos = Buffer.rfind(" ");
- llvm::StringRef Prefix;
- if (space_pos == llvm::StringRef::npos) {
- Prefix = Buffer;
- } else {
- Prefix = Buffer.substr(space_pos + 1);
- }
-
+ auto *MainCI = const_cast<clang::CompilerInstance *>((*Interp)->getCompilerInstance());
+ auto CC = clang::ReplCodeCompletion();
+ CC.codeComplete(MainCI, Buffer, Lines, Pos + 1, MainInterp.getCompilerInstance(), Results);
for (auto c : Results) {
- if (c.find(Prefix) == 0)
- Comps.push_back(llvm::LineEditor::Completion(c.substr(Prefix.size()), c));
+ if (c.find(CC.Prefix) == 0)
+ Comps.push_back(llvm::LineEditor::Completion(c.substr(CC.Prefix.size()), c));
}
return Comps;
}
diff --git a/clang/unittests/Interpreter/CodeCompletionTest.cpp b/clang/unittests/Interpreter/CodeCompletionTest.cpp
index d616afebc7d0046..7f359a2a754ca57 100644
--- a/clang/unittests/Interpreter/CodeCompletionTest.cpp
+++ b/clang/unittests/Interpreter/CodeCompletionTest.cpp
@@ -1,7 +1,9 @@
#include "clang/Interpreter/CodeCompletion.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Interpreter/Interpreter.h"
+#include "clang/Lex/Preprocessor.h"
#include "clang/Sema/CodeCompleteConsumer.h"
+#include "clang/Sema/Sema.h"
#include "llvm/LineEditor/LineEditor.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
@@ -19,7 +21,7 @@ static std::unique_ptr<Interpreter> createInterpreter() {
}
static std::vector<std::string> runComp(clang::Interpreter &MainInterp,
- llvm::StringRef Prefix,
+ llvm::StringRef Input,
llvm::Error &ErrR) {
auto CI = CB.CreateCpp();
if (auto Err = CI.takeError()) {
@@ -37,17 +39,17 @@ static std::vector<std::string> runComp(clang::Interpreter &MainInterp,
std::vector<std::string> Results;
std::vector<std::string> Comps;
-
- codeComplete(
- const_cast<clang::CompilerInstance *>((*Interp)->getCompilerInstance()),
- Prefix, /* Lines */ 1, Prefix.size(), MainInterp.getCompilerInstance(),
- Results);
+ auto *MainCI = const_cast<clang::CompilerInstance *>((*Interp)->getCompilerInstance());
+ auto CC = ReplCodeCompletion();
+ CC.codeComplete(MainCI,
+ Input, /* Lines */ 1, Input.size()+1, MainInterp.getCompilerInstance(),
+ Results);
for (auto Res : Results)
- if (Res.find(Prefix) == 0)
+ if (Res.find(CC.Prefix) == 0)
Comps.push_back(Res);
-
return Comps;
+
}
TEST(CodeCompletionTest, Sanity) {
@@ -58,8 +60,9 @@ TEST(CodeCompletionTest, Sanity) {
}
auto Err = llvm::Error::success();
auto comps = runComp(*Interp, "f", Err);
- EXPECT_EQ((size_t)2, comps.size()); // foo and float
- EXPECT_EQ(comps[0], std::string("foo"));
+ EXPECT_EQ((size_t)2, comps.size()); // float and foo
+ EXPECT_EQ(comps[0], std::string("float"));
+ EXPECT_EQ(comps[1], std::string("foo"));
EXPECT_EQ((bool)Err, false);
}
@@ -98,4 +101,176 @@ TEST(CodeCompletionTest, CompFunDeclsNoError) {
EXPECT_EQ((bool)Err, false);
}
+TEST(CodeCompletionTest, TypedDirected) {
+ auto Interp = createInterpreter();
+ if (auto R = Interp->ParseAndExecute("int application = 12;")) {
+ consumeError(std::move(R));
+ return;
+ }
+ if (auto R = Interp->ParseAndExecute("char apple = '2';")) {
+ consumeError(std::move(R));
+ return;
+ }
+ if (auto R = Interp->ParseAndExecute("void add(int &SomeInt){}")) {
+ consumeError(std::move(R));
+ return;
+ }
+ {
+ auto Err = llvm::Error::success();
+ auto comps = runComp(*Interp, std::string("add("), Err);
+ EXPECT_EQ((size_t)1, comps.size());
+ EXPECT_EQ((bool)Err, false);
+ }
+
+ if (auto R = Interp->ParseAndExecute("int banana = 42;")) {
+ consumeError(std::move(R));
+ return;
+ }
+
+ {
+ auto Err = llvm::Error::success();
+ auto comps = runComp(*Interp, std::string("add("), Err);
+ EXPECT_EQ((size_t)2, comps.size());
+ EXPECT_EQ(comps[0], "application");
+ EXPECT_EQ(comps[1], "banana");
+ EXPECT_EQ((bool)Err, false);
+ }
+
+ {
+ auto Err = llvm::Error::success();
+ auto comps = runComp(*Interp, std::string("add(b"), Err);
+ EXPECT_EQ((size_t)1, comps.size());
+ EXPECT_EQ(comps[0], "banana");
+ EXPECT_EQ((bool)Err, false);
+ }
+}
+
+TEST(CodeCompletionTest, SanityClasses) {
+ auto Interp = createInterpreter();
+ if (auto R = Interp->ParseAndExecute("struct Apple{};")) {
+ consumeError(std::move(R));
+ return;
+ }
+ if (auto R = Interp->ParseAndExecute("void takeApple(Apple &a1){}")) {
+ consumeError(std::move(R));
+ return;
+ }
+ if (auto R = Interp->ParseAndExecute("Apple a1;")) {
+ consumeError(std::move(R));
+ return;
+ }
+ if (auto R = Interp->ParseAndExecute("void takeAppleCopy(Apple a1){}")) {
+ consumeError(std::move(R));
+ return;
+ }
+
+ {
+ auto Err = llvm::Error::success();
+ auto comps = runComp(*Interp, "takeApple(", Err);
+ EXPECT_EQ((size_t)1, comps.size());
+ EXPECT_EQ(comps[0], std::string("a1"));
+ EXPECT_EQ((bool)Err, false);
+ }
+ {
+ auto Err = llvm::Error::success();
+ auto comps = runComp(*Interp, std::string("takeAppleCopy("), Err);
+ EXPECT_EQ((size_t)1, comps.size());
+ EXPECT_EQ(comps[0], std::string("a1"));
+ EXPECT_EQ((bool)Err, false);
+ }
+}
+
+TEST(CodeCompletionTest, SubClassing) {
+ auto Interp = createInterpreter();
+ if (auto R = Interp->ParseAndExecute("struct Fruit {};")) {
+ consumeError(std::move(R));
+ return;
+ }
+ if (auto R = Interp->ParseAndExecute("struct Apple : Fruit{};")) {
+ consumeError(std::move(R));
+ return;
+ }
+ if (auto R = Interp->ParseAndExecute("void takeFruit(Fruit &f){}")) {
+ consumeError(std::move(R));
+ return;
+ }
+ if (auto R = Interp->ParseAndExecute("Apple a1;")) {
+ consumeError(std::move(R));
+ return;
+ }
+ if (auto R = Interp->ParseAndExecute("Fruit f1;")) {
+ consumeError(std::move(R));
+ return;
+ }
+ auto Err = llvm::Error::success();
+ auto comps = runComp(*Interp, std::string("takeFruit("), Err);
+ EXPECT_EQ((size_t)2, comps.size());
+ EXPECT_EQ(comps[0], std::string("a1"));
+ EXPECT_EQ(comps[1], std::string("f1"));
+ EXPECT_EQ((bool)Err, false);
+}
+
+TEST(CodeCompletionTest, MultipleArguments) {
+ auto Interp = createInterpreter();
+ if (auto R = Interp->ParseAndExecute("int foo = 42;")) {
+ consumeError(std::move(R));
+ return;
+ }
+ if (auto R = Interp->ParseAndExecute("char fowl = 'A';")) {
+ consumeError(std::move(R));
+ return;
+ }
+ if (auto R = Interp->ParseAndExecute("void takeTwo(int &a, char b){}")) {
+ consumeError(std::move(R));
+ return;
+ }
+ auto Err = llvm::Error::success();
+ auto comps = runComp(*Interp, std::string("takeTwo(foo, "), Err);
+ EXPECT_EQ((size_t)1, comps.size());
+ EXPECT_EQ(comps[0], std::string("fowl"));
+ EXPECT_EQ((bool)Err, false);
+}
+
+TEST(CodeCompletionTest, Methods) {
+ auto Interp = createInterpreter();
+ cantFail(Interp->ParseAndExecute("struct Foo{int add(int a){return 42;} int par(int b){return 42;}};"));
+ cantFail(Interp->ParseAndExecute("Foo f1;"));
+
+ auto Err = llvm::Error::success();
+ auto comps = runComp(*Interp, std::string("f1."), Err);
+ EXPECT_EQ((size_t)2, comps.size());
+ EXPECT_EQ(comps[0], std::string("add"));
+ EXPECT_EQ(comps[1], std::string("par"));
+ EXPECT_EQ((bool)Err, false);
+}
+
+TEST(CodeCompletionTest, MethodsInvocations) {
+ auto Interp = createInterpreter();
+ cantFail(Interp->ParseAndExecute("struct Foo{int add(int a){return 42;} int par(int b){return 42;}};"));
+ cantFail(Interp->ParseAndExecute("Foo f1;"));
+ cantFail(Interp->ParseAndExecute("int a = 84;"));
+
+ auto Err = llvm::Error::success();
+ auto comps = runComp(*Interp, std::string("f1.add("), Err);
+ EXPECT_EQ((size_t)1, comps.size());
+ EXPECT_EQ(comps[0], std::string("a"));
+ EXPECT_EQ((bool)Err, false);
+}
+
+TEST(CodeCompletionTest, NestedInvocations) {
+ auto Interp = createInterpreter();
+ cantFail(Interp->ParseAndExecute("struct Foo{int add(int a){return 42;} int par(int b){return 42;}};"));
+ cantFail(Interp->ParseAndExecute("Foo f1;"));
+ cantFail(Interp->ParseAndExecute("int a = 84;"));
+ cantFail(Interp->ParseAndExecu...
[truncated]
|
218c261
to
623290d
Compare
✅ With the latest revision this PR passed the C/C++ code formatter. |
61bb4c4
to
582f652
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is heading in a good direction. Thank you!
a583ada
to
57de74a
Compare
57de74a
to
c3cead2
Compare
3393f32
to
304c772
Compare
304c772
to
2e01c0e
Compare
ping @vgvassilev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Thank you!
@capfredf, can you rebase the PR to trigger a rebuild - the test failure seems unrelated to our changes but maybe it was fixed meanwhile. |
use CodeCompletionContext to implement this feature.
2e01c0e
to
80962a4
Compare
@vgvassilev Thank you very much! |
@vgvassilev there are some issues reported by sanitizers. Let's revert the patch. I will fix those issues and then re-land the patch |
This reverts commit 002d471.
Reverts #67349 There are some issues with the sanitizers. We will reland once that's fixed.
Differential Revision: https://reviews.llvm.org/D159128