Skip to content

Commit b6e6e3e

Browse files
Add Code Completion (#246)
1 parent 244d1ab commit b6e6e3e

File tree

5 files changed

+115
-1
lines changed

5 files changed

+115
-1
lines changed

include/clang/Interpreter/CppInterOp.h

+12
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,18 @@ namespace Cpp {
648648
CPPINTEROP_API std::string EndStdStreamCapture();
649649

650650
///@}
651+
652+
/// Append all Code completion suggestions to Results.
653+
///\param[out] Results - CC suggestions for code fragment. Suggestions are
654+
/// appended.
655+
///\param[in] code - code fragmet to complete
656+
///\param[in] complete_line - position (line) in code for suggestion
657+
///\param[in] complete_column - position (column) in code for suggestion
658+
CPPINTEROP_API void CodeComplete(std::vector<std::string>& Results,
659+
const char* code,
660+
unsigned complete_line = 1U,
661+
unsigned complete_column = 1U);
662+
651663
} // end namespace Cpp
652664

653665
#endif // CPPINTEROP_CPPINTEROP_H

lib/Interpreter/CMakeLists.txt

+8-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@ set(LLVM_LINK_COMPONENTS
33
BinaryFormat
44
Core
55
Object
6-
OrcJIT
6+
OrcJit
77
Support
88
)
9+
# FIXME: Investigate why this needs to be conditionally included.
10+
if ("LLVMFrontendDriver" IN_LIST LLVM_AVAILABLE_LIBS)
11+
list(APPEND LLVM_LINK_COMPONENTS FrontendDriver)
12+
endif()
13+
if ("LLVMOrcDebugging" IN_LIST LLVM_AVAILABLE_LIBS)
14+
list(APPEND LLVM_LINK_COMPONENTS OrcDebugging)
15+
endif()
916

1017
set(DLM
1118
DynamicLibraryManager.cpp

lib/Interpreter/Compatibility.h

+64
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
#include "clang/Basic/Version.h"
1111
#include "clang/Config/config.h"
1212

13+
#if CLANG_VERSION_MAJOR >= 18
14+
#include "clang/Interpreter/CodeCompletion.h"
15+
#endif
16+
1317
#include "llvm/ADT/SmallString.h"
1418
#include "llvm/ADT/StringRef.h"
1519
#include "llvm/ADT/Twine.h"
@@ -28,6 +32,8 @@
2832

2933
#include "cling/Utils/AST.h"
3034

35+
#include <regex>
36+
3137
namespace Cpp {
3238
namespace Cpp_utils = cling::utils;
3339
}
@@ -69,6 +75,30 @@ getSymbolAddress(const cling::Interpreter& I, llvm::StringRef IRName) {
6975
Names.push_back(Jit.getExecutionSession().intern(IRName));
7076
return llvm::make_error<llvm::orc::SymbolsNotFound>(Names);
7177
}
78+
79+
inline void codeComplete(std::vector<std::string>& Results,
80+
const cling::Interpreter& I, const char* code,
81+
unsigned complete_line = 1U,
82+
unsigned complete_column = 1U) {
83+
std::vector<std::string> results;
84+
size_t column = complete_column;
85+
I.codeComplete(code, column, results);
86+
87+
// append cleaned results
88+
for (auto& r : results) {
89+
// remove the definition at the beginning (for example [#int#])
90+
r = std::regex_replace(r, std::regex("\\[\\#.*\\#\\]"), "");
91+
// remove the variable name in <#type name#>
92+
r = std::regex_replace(r, std::regex("(\\ |\\*)+(\\w+)(\\#\\>)"), "$1$3");
93+
// remove unnecessary space at the end of <#type #>
94+
r = std::regex_replace(r, std::regex("\\ *(\\#\\>)"), "$1");
95+
// remove <# #> to keep only the type
96+
r = std::regex_replace(r, std::regex("\\<\\#([^#>]*)\\#\\>"), "$1");
97+
if (r.find(code) == 0)
98+
Results.push_back(r);
99+
}
100+
}
101+
72102
} // namespace compat
73103

74104
#endif // USE_CLING
@@ -184,6 +214,7 @@ inline void maybeMangleDeclName(const clang::GlobalDecl& GD,
184214
// Clang 14 - Add new Interpreter methods: getExecutionEngine,
185215
// getSymbolAddress, getSymbolAddressFromLinkerName
186216
// Clang 15 - Add new Interpreter methods: Undo
217+
// Clang 18 - Add new Interpreter methods: CodeComplete
187218

188219
inline llvm::orc::LLJIT* getExecutionEngine(clang::Interpreter& I) {
189220
#if CLANG_VERSION_MAJOR >= 14
@@ -247,6 +278,39 @@ inline llvm::Error Undo(clang::Interpreter& I, unsigned N = 1) {
247278
#endif
248279
}
249280

281+
inline void codeComplete(std::vector<std::string>& Results,
282+
clang::Interpreter& I, const char* code,
283+
unsigned complete_line = 1U,
284+
unsigned complete_column = 1U) {
285+
#if CLANG_VERSION_MAJOR >= 18
286+
// FIXME: We should match the invocation arguments of the main interpreter.
287+
// That can affect the returned completion results.
288+
auto CB = clang::IncrementalCompilerBuilder();
289+
auto CI = CB.CreateCpp();
290+
if (auto Err = CI.takeError()) {
291+
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: ");
292+
return;
293+
}
294+
auto Interp = clang::Interpreter::create(std::move(*CI));
295+
if (auto Err = Interp.takeError()) {
296+
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: ");
297+
return;
298+
}
299+
300+
std::vector<std::string> results;
301+
std::vector<std::string> Comps;
302+
clang::CompilerInstance* MainCI = (*Interp)->getCompilerInstance();
303+
auto CC = clang::ReplCodeCompleter();
304+
CC.codeComplete(MainCI, code, complete_line, complete_column,
305+
I.getCompilerInstance(), results);
306+
for (llvm::StringRef r : results)
307+
if (r.find(CC.Prefix) == 0)
308+
Results.push_back(r.str());
309+
#else
310+
assert(false && "CodeCompletion API only available in Clang >= 18.");
311+
#endif
312+
}
313+
250314
} // namespace compat
251315

252316
#include "CppInterOpInterpreter.h"

lib/Interpreter/CppInterOp.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -3257,4 +3257,11 @@ namespace Cpp {
32573257
return result;
32583258
}
32593259

3260+
void CodeComplete(std::vector<std::string>& Results, const char* code,
3261+
unsigned complete_line /* = 1U */,
3262+
unsigned complete_column /* = 1U */) {
3263+
compat::codeComplete(Results, getInterp(), code, complete_line,
3264+
complete_column);
3265+
}
3266+
32603267
} // end namespace Cpp

unittests/CppInterOp/InterpreterTest.cpp

+24
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
#include "clang/Interpreter/CppInterOp.h"
22

3+
#include "clang/Basic/Version.h"
4+
35
#include "llvm/ADT/SmallString.h"
46
#include "llvm/Support/Path.h"
57

68
#include <gmock/gmock.h>
79
#include "gtest/gtest.h"
810

11+
#include <algorithm>
12+
913
using ::testing::StartsWith;
1014

1115
TEST(InterpreterTest, Version) {
@@ -103,3 +107,23 @@ TEST(InterpreterTest, DetectSystemCompilerIncludePaths) {
103107
Cpp::DetectSystemCompilerIncludePaths(includes);
104108
EXPECT_FALSE(includes.empty());
105109
}
110+
111+
TEST(InterpreterTest, CodeCompletion) {
112+
#if CLANG_VERSION_MAJOR >= 18 || defined(USE_CLING)
113+
Cpp::CreateInterpreter();
114+
std::vector<std::string> cc;
115+
Cpp::Declare("int foo = 12;");
116+
Cpp::CodeComplete(cc, "f", 1, 2);
117+
// We check only for 'float' and 'foo', because they
118+
// must be present in the result. Other hints may appear
119+
// there, depending on the implementation, but these two
120+
// are required to say that the test is working.
121+
size_t cnt = 0;
122+
for (auto& r : cc)
123+
if (r == "float" || r == "foo")
124+
cnt++;
125+
EXPECT_EQ(2U, cnt); // float and foo
126+
#else
127+
GTEST_SKIP();
128+
#endif
129+
}

0 commit comments

Comments
 (0)