Skip to content

Commit 35b366a

Browse files
authored
[ClangRepl] Reland Semanic Code Completion (#75556)
This patch contains changes from 002d471, in addition to a bug fix that added a virtual destructor to `CompletionContextHandler` The original changes in the orginal commit piggybacks on clang's semantic modules to enable semantic completion. In particular, we use `CodeCompletionContext` to differentiate two types of code completion. We also extract the relevant type information from it.
1 parent edbd034 commit 35b366a

File tree

6 files changed

+445
-51
lines changed

6 files changed

+445
-51
lines changed

clang/include/clang/Interpreter/CodeCompletion.h

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,27 @@ namespace clang {
2323
class CodeCompletionResult;
2424
class CompilerInstance;
2525

26-
void codeComplete(CompilerInstance *InterpCI, llvm::StringRef Content,
27-
unsigned Line, unsigned Col, const CompilerInstance *ParentCI,
28-
std::vector<std::string> &CCResults);
26+
struct ReplCodeCompleter {
27+
ReplCodeCompleter() = default;
28+
std::string Prefix;
29+
30+
/// \param InterpCI [in] The compiler instance that is used to trigger code
31+
/// completion
32+
33+
/// \param Content [in] The string where code completion is triggered.
34+
35+
/// \param Line [in] The line number of the code completion point.
36+
37+
/// \param Col [in] The column number of the code completion point.
38+
39+
/// \param ParentCI [in] The running interpreter compiler instance that
40+
/// provides ASTContexts.
41+
42+
/// \param CCResults [out] The completion results.
43+
void codeComplete(CompilerInstance *InterpCI, llvm::StringRef Content,
44+
unsigned Line, unsigned Col,
45+
const CompilerInstance *ParentCI,
46+
std::vector<std::string> &CCResults);
47+
};
2948
} // namespace clang
3049
#endif

clang/include/clang/Interpreter/Interpreter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class Interpreter {
101101
const ASTContext &getASTContext() const;
102102
ASTContext &getASTContext();
103103
const CompilerInstance *getCompilerInstance() const;
104+
CompilerInstance *getCompilerInstance();
104105
llvm::Expected<llvm::orc::LLJIT &> getExecutionEngine();
105106

106107
llvm::Expected<PartialTranslationUnit &> Parse(llvm::StringRef Code);

clang/lib/Interpreter/CodeCompletion.cpp

Lines changed: 200 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "clang/Interpreter/CodeCompletion.h"
1414
#include "clang/AST/ASTImporter.h"
15+
#include "clang/AST/DeclLookups.h"
1516
#include "clang/AST/DeclarationName.h"
1617
#include "clang/AST/ExternalASTSource.h"
1718
#include "clang/Basic/IdentifierTable.h"
@@ -23,6 +24,8 @@
2324
#include "clang/Sema/CodeCompleteConsumer.h"
2425
#include "clang/Sema/CodeCompleteOptions.h"
2526
#include "clang/Sema/Sema.h"
27+
#include "llvm/Support/Debug.h"
28+
#define DEBUG_TYPE "REPLCC"
2629

2730
namespace clang {
2831

@@ -39,11 +42,15 @@ clang::CodeCompleteOptions getClangCompleteOpts() {
3942

4043
class ReplCompletionConsumer : public CodeCompleteConsumer {
4144
public:
42-
ReplCompletionConsumer(std::vector<std::string> &Results)
45+
ReplCompletionConsumer(std::vector<std::string> &Results,
46+
ReplCodeCompleter &CC)
4347
: CodeCompleteConsumer(getClangCompleteOpts()),
4448
CCAllocator(std::make_shared<GlobalCodeCompletionAllocator>()),
45-
CCTUInfo(CCAllocator), Results(Results){};
49+
CCTUInfo(CCAllocator), Results(Results), CC(CC) {}
4650

51+
// The entry of handling code completion. When the function is called, we
52+
// create a `Context`-based handler (see classes defined below) to handle each
53+
// completion result.
4754
void ProcessCodeCompleteResults(class Sema &S, CodeCompletionContext Context,
4855
CodeCompletionResult *InResults,
4956
unsigned NumResults) final;
@@ -56,26 +63,147 @@ class ReplCompletionConsumer : public CodeCompleteConsumer {
5663
std::shared_ptr<GlobalCodeCompletionAllocator> CCAllocator;
5764
CodeCompletionTUInfo CCTUInfo;
5865
std::vector<std::string> &Results;
66+
ReplCodeCompleter &CC;
67+
};
68+
69+
/// The class CompletionContextHandler contains four interfaces, each of
70+
/// which handles one type of completion result.
71+
/// Its derived classes are used to create concrete handlers based on
72+
/// \c CodeCompletionContext.
73+
class CompletionContextHandler {
74+
protected:
75+
CodeCompletionContext CCC;
76+
std::vector<std::string> &Results;
77+
78+
private:
79+
Sema &S;
80+
81+
public:
82+
CompletionContextHandler(Sema &S, CodeCompletionContext CCC,
83+
std::vector<std::string> &Results)
84+
: CCC(CCC), Results(Results), S(S) {}
85+
86+
virtual ~CompletionContextHandler() = default;
87+
/// Converts a Declaration completion result to a completion string, and then
88+
/// stores it in Results.
89+
virtual void handleDeclaration(const CodeCompletionResult &Result) {
90+
auto PreferredType = CCC.getPreferredType();
91+
if (PreferredType.isNull()) {
92+
Results.push_back(Result.Declaration->getName().str());
93+
return;
94+
}
95+
96+
if (auto *VD = dyn_cast<VarDecl>(Result.Declaration)) {
97+
auto ArgumentType = VD->getType();
98+
if (PreferredType->isReferenceType()) {
99+
QualType RT = PreferredType->castAs<ReferenceType>()->getPointeeType();
100+
Sema::ReferenceConversions RefConv;
101+
Sema::ReferenceCompareResult RefRelationship =
102+
S.CompareReferenceRelationship(SourceLocation(), RT, ArgumentType,
103+
&RefConv);
104+
switch (RefRelationship) {
105+
case Sema::Ref_Compatible:
106+
case Sema::Ref_Related:
107+
Results.push_back(VD->getName().str());
108+
break;
109+
case Sema::Ref_Incompatible:
110+
break;
111+
}
112+
} else if (S.Context.hasSameType(ArgumentType, PreferredType)) {
113+
Results.push_back(VD->getName().str());
114+
}
115+
}
116+
}
117+
118+
/// Converts a Keyword completion result to a completion string, and then
119+
/// stores it in Results.
120+
virtual void handleKeyword(const CodeCompletionResult &Result) {
121+
auto Prefix = S.getPreprocessor().getCodeCompletionFilter();
122+
// Add keyword to the completion results only if we are in a type-aware
123+
// situation.
124+
if (!CCC.getBaseType().isNull() || !CCC.getPreferredType().isNull())
125+
return;
126+
if (StringRef(Result.Keyword).startswith(Prefix))
127+
Results.push_back(Result.Keyword);
128+
}
129+
130+
/// Converts a Pattern completion result to a completion string, and then
131+
/// stores it in Results.
132+
virtual void handlePattern(const CodeCompletionResult &Result) {}
133+
134+
/// Converts a Macro completion result to a completion string, and then stores
135+
/// it in Results.
136+
virtual void handleMacro(const CodeCompletionResult &Result) {}
137+
};
138+
139+
class DotMemberAccessHandler : public CompletionContextHandler {
140+
public:
141+
DotMemberAccessHandler(Sema &S, CodeCompletionContext CCC,
142+
std::vector<std::string> &Results)
143+
: CompletionContextHandler(S, CCC, Results) {}
144+
void handleDeclaration(const CodeCompletionResult &Result) override {
145+
auto *ID = Result.Declaration->getIdentifier();
146+
if (!ID)
147+
return;
148+
if (!isa<CXXMethodDecl>(Result.Declaration))
149+
return;
150+
const auto *Fun = cast<CXXMethodDecl>(Result.Declaration);
151+
if (Fun->getParent()->getCanonicalDecl() ==
152+
CCC.getBaseType()->getAsCXXRecordDecl()->getCanonicalDecl()) {
153+
LLVM_DEBUG(llvm::dbgs() << "[In HandleCodeCompleteDOT] Name : "
154+
<< ID->getName() << "\n");
155+
Results.push_back(ID->getName().str());
156+
}
157+
}
158+
159+
void handleKeyword(const CodeCompletionResult &Result) override {}
59160
};
60161

61162
void ReplCompletionConsumer::ProcessCodeCompleteResults(
62163
class Sema &S, CodeCompletionContext Context,
63164
CodeCompletionResult *InResults, unsigned NumResults) {
64-
for (unsigned I = 0; I < NumResults; ++I) {
165+
166+
auto Prefix = S.getPreprocessor().getCodeCompletionFilter();
167+
CC.Prefix = Prefix;
168+
169+
std::unique_ptr<CompletionContextHandler> CCH;
170+
171+
// initialize fine-grained code completion handler based on the code
172+
// completion context.
173+
switch (Context.getKind()) {
174+
case CodeCompletionContext::CCC_DotMemberAccess:
175+
CCH.reset(new DotMemberAccessHandler(S, Context, this->Results));
176+
break;
177+
default:
178+
CCH.reset(new CompletionContextHandler(S, Context, this->Results));
179+
};
180+
181+
for (unsigned I = 0; I < NumResults; I++) {
65182
auto &Result = InResults[I];
66183
switch (Result.Kind) {
67184
case CodeCompletionResult::RK_Declaration:
68-
if (auto *ID = Result.Declaration->getIdentifier()) {
69-
Results.push_back(ID->getName().str());
185+
if (Result.Hidden) {
186+
break;
187+
}
188+
if (!Result.Declaration->getDeclName().isIdentifier() ||
189+
!Result.Declaration->getName().startswith(Prefix)) {
190+
break;
70191
}
192+
CCH->handleDeclaration(Result);
71193
break;
72194
case CodeCompletionResult::RK_Keyword:
73-
Results.push_back(Result.Keyword);
195+
CCH->handleKeyword(Result);
74196
break;
75-
default:
197+
case CodeCompletionResult::RK_Macro:
198+
CCH->handleMacro(Result);
199+
break;
200+
case CodeCompletionResult::RK_Pattern:
201+
CCH->handlePattern(Result);
76202
break;
77203
}
78204
}
205+
206+
std::sort(Results.begin(), Results.end());
79207
}
80208

81209
class IncrementalSyntaxOnlyAction : public SyntaxOnlyAction {
@@ -118,6 +246,16 @@ void IncrementalSyntaxOnlyAction::ExecuteAction() {
118246
CI.getASTContext().getTranslationUnitDecl()->setHasExternalVisibleStorage(
119247
true);
120248

249+
// Load all external decls into current context. Under the hood, it calls
250+
// ExternalSource::completeVisibleDeclsMap, which make all decls on the redecl
251+
// chain visible.
252+
//
253+
// This is crucial to code completion on dot members, since a bound variable
254+
// before "." would be otherwise treated out-of-scope.
255+
//
256+
// clang-repl> Foo f1;
257+
// clang-repl> f1.<tab>
258+
CI.getASTContext().getTranslationUnitDecl()->lookups();
121259
SyntaxOnlyAction::ExecuteAction();
122260
}
123261

@@ -134,6 +272,7 @@ ExternalSource::ExternalSource(ASTContext &ChildASTCtxt, FileManager &ChildFM,
134272

135273
bool ExternalSource::FindExternalVisibleDeclsByName(const DeclContext *DC,
136274
DeclarationName Name) {
275+
137276
IdentifierTable &ParentIdTable = ParentASTCtxt.Idents;
138277

139278
auto ParentDeclName =
@@ -159,29 +298,67 @@ void ExternalSource::completeVisibleDeclsMap(
159298
for (auto *DeclCtxt = ParentTUDeclCtxt; DeclCtxt != nullptr;
160299
DeclCtxt = DeclCtxt->getPreviousDecl()) {
161300
for (auto &IDeclContext : DeclCtxt->decls()) {
162-
if (NamedDecl *Decl = llvm::dyn_cast<NamedDecl>(IDeclContext)) {
163-
if (auto DeclOrErr = Importer->Import(Decl)) {
164-
if (NamedDecl *importedNamedDecl =
165-
llvm::dyn_cast<NamedDecl>(*DeclOrErr)) {
166-
SetExternalVisibleDeclsForName(ChildDeclContext,
167-
importedNamedDecl->getDeclName(),
168-
importedNamedDecl);
169-
}
170-
171-
} else {
172-
llvm::consumeError(DeclOrErr.takeError());
173-
}
301+
if (!llvm::isa<NamedDecl>(IDeclContext))
302+
continue;
303+
304+
NamedDecl *Decl = llvm::cast<NamedDecl>(IDeclContext);
305+
306+
auto DeclOrErr = Importer->Import(Decl);
307+
if (!DeclOrErr) {
308+
// if an error happens, it usually means the decl has already been
309+
// imported or the decl is a result of a failed import. But in our
310+
// case, every import is fresh each time code completion is
311+
// triggered. So Import usually doesn't fail. If it does, it just means
312+
// the related decl can't be used in code completion and we can safely
313+
// drop it.
314+
llvm::consumeError(DeclOrErr.takeError());
315+
continue;
174316
}
317+
318+
if (!llvm::isa<NamedDecl>(*DeclOrErr))
319+
continue;
320+
321+
NamedDecl *importedNamedDecl = llvm::cast<NamedDecl>(*DeclOrErr);
322+
323+
SetExternalVisibleDeclsForName(ChildDeclContext,
324+
importedNamedDecl->getDeclName(),
325+
importedNamedDecl);
326+
327+
if (!llvm::isa<CXXRecordDecl>(importedNamedDecl))
328+
continue;
329+
330+
auto *Record = llvm::cast<CXXRecordDecl>(importedNamedDecl);
331+
332+
if (auto Err = Importer->ImportDefinition(Decl)) {
333+
// the same as above
334+
consumeError(std::move(Err));
335+
continue;
336+
}
337+
338+
Record->setHasLoadedFieldsFromExternalStorage(true);
339+
LLVM_DEBUG(llvm::dbgs()
340+
<< "\nCXXRecrod : " << Record->getName() << " size(methods): "
341+
<< std::distance(Record->method_begin(), Record->method_end())
342+
<< " has def?: " << Record->hasDefinition()
343+
<< " # (methods): "
344+
<< std::distance(Record->getDefinition()->method_begin(),
345+
Record->getDefinition()->method_end())
346+
<< "\n");
347+
for (auto *Meth : Record->methods())
348+
SetExternalVisibleDeclsForName(ChildDeclContext, Meth->getDeclName(),
349+
Meth);
175350
}
176351
ChildDeclContext->setHasExternalLexicalStorage(false);
177352
}
178353
}
179354

180-
void codeComplete(CompilerInstance *InterpCI, llvm::StringRef Content,
181-
unsigned Line, unsigned Col, const CompilerInstance *ParentCI,
182-
std::vector<std::string> &CCResults) {
355+
void ReplCodeCompleter::codeComplete(CompilerInstance *InterpCI,
356+
llvm::StringRef Content, unsigned Line,
357+
unsigned Col,
358+
const CompilerInstance *ParentCI,
359+
std::vector<std::string> &CCResults) {
183360
auto DiagOpts = DiagnosticOptions();
184-
auto consumer = ReplCompletionConsumer(CCResults);
361+
auto consumer = ReplCompletionConsumer(CCResults, *this);
185362

186363
auto diag = InterpCI->getDiagnosticsPtr();
187364
std::unique_ptr<ASTUnit> AU(ASTUnit::LoadFromCompilerInvocationAction(

clang/lib/Interpreter/Interpreter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,10 @@ const CompilerInstance *Interpreter::getCompilerInstance() const {
319319
return IncrParser->getCI();
320320
}
321321

322+
CompilerInstance *Interpreter::getCompilerInstance() {
323+
return IncrParser->getCI();
324+
}
325+
322326
llvm::Expected<llvm::orc::LLJIT &> Interpreter::getExecutionEngine() {
323327
if (!IncrExecutor) {
324328
if (auto Err = CreateExecutor())

clang/tools/clang-repl/ClangRepl.cpp

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include "clang/Frontend/FrontendDiagnostic.h"
1616
#include "clang/Interpreter/CodeCompletion.h"
1717
#include "clang/Interpreter/Interpreter.h"
18+
#include "clang/Lex/Preprocessor.h"
19+
#include "clang/Sema/Sema.h"
1820

1921
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
2022
#include "llvm/LineEditor/LineEditor.h"
@@ -123,22 +125,14 @@ ReplListCompleter::operator()(llvm::StringRef Buffer, size_t Pos,
123125

124126
return {};
125127
}
126-
127-
codeComplete(
128-
const_cast<clang::CompilerInstance *>((*Interp)->getCompilerInstance()),
129-
Buffer, Lines, Pos + 1, MainInterp.getCompilerInstance(), Results);
130-
131-
size_t space_pos = Buffer.rfind(" ");
132-
llvm::StringRef Prefix;
133-
if (space_pos == llvm::StringRef::npos) {
134-
Prefix = Buffer;
135-
} else {
136-
Prefix = Buffer.substr(space_pos + 1);
137-
}
138-
128+
auto *MainCI = (*Interp)->getCompilerInstance();
129+
auto CC = clang::ReplCodeCompleter();
130+
CC.codeComplete(MainCI, Buffer, Lines, Pos + 1,
131+
MainInterp.getCompilerInstance(), Results);
139132
for (auto c : Results) {
140-
if (c.find(Prefix) == 0)
141-
Comps.push_back(llvm::LineEditor::Completion(c.substr(Prefix.size()), c));
133+
if (c.find(CC.Prefix) == 0)
134+
Comps.push_back(
135+
llvm::LineEditor::Completion(c.substr(CC.Prefix.size()), c));
142136
}
143137
return Comps;
144138
}

0 commit comments

Comments
 (0)