Skip to content

Commit 002d471

Browse files
authored
[ClangRepl] Type Directed Code Completion (#67349)
Differential Revision: https://reviews.llvm.org/D159128
1 parent b1fba56 commit 002d471

File tree

6 files changed

+444
-51
lines changed

6 files changed

+444
-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: 199 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,146 @@ 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+
/// Converts a Declaration completion result to a completion string, and then
87+
/// stores it in Results.
88+
virtual void handleDeclaration(const CodeCompletionResult &Result) {
89+
auto PreferredType = CCC.getPreferredType();
90+
if (PreferredType.isNull()) {
91+
Results.push_back(Result.Declaration->getName().str());
92+
return;
93+
}
94+
95+
if (auto *VD = dyn_cast<VarDecl>(Result.Declaration)) {
96+
auto ArgumentType = VD->getType();
97+
if (PreferredType->isReferenceType()) {
98+
QualType RT = PreferredType->castAs<ReferenceType>()->getPointeeType();
99+
Sema::ReferenceConversions RefConv;
100+
Sema::ReferenceCompareResult RefRelationship =
101+
S.CompareReferenceRelationship(SourceLocation(), RT, ArgumentType,
102+
&RefConv);
103+
switch (RefRelationship) {
104+
case Sema::Ref_Compatible:
105+
case Sema::Ref_Related:
106+
Results.push_back(VD->getName().str());
107+
break;
108+
case Sema::Ref_Incompatible:
109+
break;
110+
}
111+
} else if (S.Context.hasSameType(ArgumentType, PreferredType)) {
112+
Results.push_back(VD->getName().str());
113+
}
114+
}
115+
}
116+
117+
/// Converts a Keyword completion result to a completion string, and then
118+
/// stores it in Results.
119+
virtual void handleKeyword(const CodeCompletionResult &Result) {
120+
auto Prefix = S.getPreprocessor().getCodeCompletionFilter();
121+
// Add keyword to the completion results only if we are in a type-aware
122+
// situation.
123+
if (!CCC.getBaseType().isNull() || !CCC.getPreferredType().isNull())
124+
return;
125+
if (StringRef(Result.Keyword).startswith(Prefix))
126+
Results.push_back(Result.Keyword);
127+
}
128+
129+
/// Converts a Pattern completion result to a completion string, and then
130+
/// stores it in Results.
131+
virtual void handlePattern(const CodeCompletionResult &Result) {}
132+
133+
/// Converts a Macro completion result to a completion string, and then stores
134+
/// it in Results.
135+
virtual void handleMacro(const CodeCompletionResult &Result) {}
136+
};
137+
138+
class DotMemberAccessHandler : public CompletionContextHandler {
139+
public:
140+
DotMemberAccessHandler(Sema &S, CodeCompletionContext CCC,
141+
std::vector<std::string> &Results)
142+
: CompletionContextHandler(S, CCC, Results) {}
143+
void handleDeclaration(const CodeCompletionResult &Result) override {
144+
auto *ID = Result.Declaration->getIdentifier();
145+
if (!ID)
146+
return;
147+
if (!isa<CXXMethodDecl>(Result.Declaration))
148+
return;
149+
const auto *Fun = cast<CXXMethodDecl>(Result.Declaration);
150+
if (Fun->getParent()->getCanonicalDecl() ==
151+
CCC.getBaseType()->getAsCXXRecordDecl()->getCanonicalDecl()) {
152+
LLVM_DEBUG(llvm::dbgs() << "[In HandleCodeCompleteDOT] Name : "
153+
<< ID->getName() << "\n");
154+
Results.push_back(ID->getName().str());
155+
}
156+
}
157+
158+
void handleKeyword(const CodeCompletionResult &Result) override {}
59159
};
60160

61161
void ReplCompletionConsumer::ProcessCodeCompleteResults(
62162
class Sema &S, CodeCompletionContext Context,
63163
CodeCompletionResult *InResults, unsigned NumResults) {
64-
for (unsigned I = 0; I < NumResults; ++I) {
164+
165+
auto Prefix = S.getPreprocessor().getCodeCompletionFilter();
166+
CC.Prefix = Prefix;
167+
168+
std::unique_ptr<CompletionContextHandler> CCH;
169+
170+
// initialize fine-grained code completion handler based on the code
171+
// completion context.
172+
switch (Context.getKind()) {
173+
case CodeCompletionContext::CCC_DotMemberAccess:
174+
CCH.reset(new DotMemberAccessHandler(S, Context, this->Results));
175+
break;
176+
default:
177+
CCH.reset(new CompletionContextHandler(S, Context, this->Results));
178+
};
179+
180+
for (unsigned I = 0; I < NumResults; I++) {
65181
auto &Result = InResults[I];
66182
switch (Result.Kind) {
67183
case CodeCompletionResult::RK_Declaration:
68-
if (auto *ID = Result.Declaration->getIdentifier()) {
69-
Results.push_back(ID->getName().str());
184+
if (Result.Hidden) {
185+
break;
186+
}
187+
if (!Result.Declaration->getDeclName().isIdentifier() ||
188+
!Result.Declaration->getName().startswith(Prefix)) {
189+
break;
70190
}
191+
CCH->handleDeclaration(Result);
71192
break;
72193
case CodeCompletionResult::RK_Keyword:
73-
Results.push_back(Result.Keyword);
194+
CCH->handleKeyword(Result);
74195
break;
75-
default:
196+
case CodeCompletionResult::RK_Macro:
197+
CCH->handleMacro(Result);
198+
break;
199+
case CodeCompletionResult::RK_Pattern:
200+
CCH->handlePattern(Result);
76201
break;
77202
}
78203
}
204+
205+
std::sort(Results.begin(), Results.end());
79206
}
80207

81208
class IncrementalSyntaxOnlyAction : public SyntaxOnlyAction {
@@ -118,6 +245,16 @@ void IncrementalSyntaxOnlyAction::ExecuteAction() {
118245
CI.getASTContext().getTranslationUnitDecl()->setHasExternalVisibleStorage(
119246
true);
120247

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

@@ -134,6 +271,7 @@ ExternalSource::ExternalSource(ASTContext &ChildASTCtxt, FileManager &ChildFM,
134271

135272
bool ExternalSource::FindExternalVisibleDeclsByName(const DeclContext *DC,
136273
DeclarationName Name) {
274+
137275
IdentifierTable &ParentIdTable = ParentASTCtxt.Idents;
138276

139277
auto ParentDeclName =
@@ -159,29 +297,67 @@ void ExternalSource::completeVisibleDeclsMap(
159297
for (auto *DeclCtxt = ParentTUDeclCtxt; DeclCtxt != nullptr;
160298
DeclCtxt = DeclCtxt->getPreviousDecl()) {
161299
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-
}
300+
if (!llvm::isa<NamedDecl>(IDeclContext))
301+
continue;
302+
303+
NamedDecl *Decl = llvm::cast<NamedDecl>(IDeclContext);
304+
305+
auto DeclOrErr = Importer->Import(Decl);
306+
if (!DeclOrErr) {
307+
// if an error happens, it usually means the decl has already been
308+
// imported or the decl is a result of a failed import. But in our
309+
// case, every import is fresh each time code completion is
310+
// triggered. So Import usually doesn't fail. If it does, it just means
311+
// the related decl can't be used in code completion and we can safely
312+
// drop it.
313+
llvm::consumeError(DeclOrErr.takeError());
314+
continue;
174315
}
316+
317+
if (!llvm::isa<NamedDecl>(*DeclOrErr))
318+
continue;
319+
320+
NamedDecl *importedNamedDecl = llvm::cast<NamedDecl>(*DeclOrErr);
321+
322+
SetExternalVisibleDeclsForName(ChildDeclContext,
323+
importedNamedDecl->getDeclName(),
324+
importedNamedDecl);
325+
326+
if (!llvm::isa<CXXRecordDecl>(importedNamedDecl))
327+
continue;
328+
329+
auto *Record = llvm::cast<CXXRecordDecl>(importedNamedDecl);
330+
331+
if (auto Err = Importer->ImportDefinition(Decl)) {
332+
// the same as above
333+
consumeError(std::move(Err));
334+
continue;
335+
}
336+
337+
Record->setHasLoadedFieldsFromExternalStorage(true);
338+
LLVM_DEBUG(llvm::dbgs()
339+
<< "\nCXXRecrod : " << Record->getName() << " size(methods): "
340+
<< std::distance(Record->method_begin(), Record->method_end())
341+
<< " has def?: " << Record->hasDefinition()
342+
<< " # (methods): "
343+
<< std::distance(Record->getDefinition()->method_begin(),
344+
Record->getDefinition()->method_end())
345+
<< "\n");
346+
for (auto *Meth : Record->methods())
347+
SetExternalVisibleDeclsForName(ChildDeclContext, Meth->getDeclName(),
348+
Meth);
175349
}
176350
ChildDeclContext->setHasExternalLexicalStorage(false);
177351
}
178352
}
179353

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

186362
auto diag = InterpCI->getDiagnosticsPtr();
187363
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)