Skip to content

Commit

Permalink
Add Code Completion (#246)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-penev authored May 3, 2024
1 parent 244d1ab commit b6e6e3e
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 1 deletion.
12 changes: 12 additions & 0 deletions include/clang/Interpreter/CppInterOp.h
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,18 @@ namespace Cpp {
CPPINTEROP_API std::string EndStdStreamCapture();

///@}

/// Append all Code completion suggestions to Results.
///\param[out] Results - CC suggestions for code fragment. Suggestions are
/// appended.
///\param[in] code - code fragmet to complete
///\param[in] complete_line - position (line) in code for suggestion
///\param[in] complete_column - position (column) in code for suggestion
CPPINTEROP_API void CodeComplete(std::vector<std::string>& Results,
const char* code,
unsigned complete_line = 1U,
unsigned complete_column = 1U);

} // end namespace Cpp

#endif // CPPINTEROP_CPPINTEROP_H
9 changes: 8 additions & 1 deletion lib/Interpreter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ set(LLVM_LINK_COMPONENTS
BinaryFormat
Core
Object
OrcJIT
OrcJit
Support
)
# FIXME: Investigate why this needs to be conditionally included.
if ("LLVMFrontendDriver" IN_LIST LLVM_AVAILABLE_LIBS)
list(APPEND LLVM_LINK_COMPONENTS FrontendDriver)
endif()
if ("LLVMOrcDebugging" IN_LIST LLVM_AVAILABLE_LIBS)
list(APPEND LLVM_LINK_COMPONENTS OrcDebugging)
endif()

set(DLM
DynamicLibraryManager.cpp
Expand Down
64 changes: 64 additions & 0 deletions lib/Interpreter/Compatibility.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
#include "clang/Basic/Version.h"
#include "clang/Config/config.h"

#if CLANG_VERSION_MAJOR >= 18
#include "clang/Interpreter/CodeCompletion.h"
#endif

#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
Expand All @@ -28,6 +32,8 @@

#include "cling/Utils/AST.h"

#include <regex>

namespace Cpp {
namespace Cpp_utils = cling::utils;
}
Expand Down Expand Up @@ -69,6 +75,30 @@ getSymbolAddress(const cling::Interpreter& I, llvm::StringRef IRName) {
Names.push_back(Jit.getExecutionSession().intern(IRName));
return llvm::make_error<llvm::orc::SymbolsNotFound>(Names);
}

inline void codeComplete(std::vector<std::string>& Results,
const cling::Interpreter& I, const char* code,
unsigned complete_line = 1U,
unsigned complete_column = 1U) {
std::vector<std::string> results;
size_t column = complete_column;
I.codeComplete(code, column, results);

// append cleaned results
for (auto& r : results) {
// remove the definition at the beginning (for example [#int#])
r = std::regex_replace(r, std::regex("\\[\\#.*\\#\\]"), "");
// remove the variable name in <#type name#>
r = std::regex_replace(r, std::regex("(\\ |\\*)+(\\w+)(\\#\\>)"), "$1$3");
// remove unnecessary space at the end of <#type #>
r = std::regex_replace(r, std::regex("\\ *(\\#\\>)"), "$1");
// remove <# #> to keep only the type
r = std::regex_replace(r, std::regex("\\<\\#([^#>]*)\\#\\>"), "$1");
if (r.find(code) == 0)
Results.push_back(r);
}
}

} // namespace compat

#endif // USE_CLING
Expand Down Expand Up @@ -184,6 +214,7 @@ inline void maybeMangleDeclName(const clang::GlobalDecl& GD,
// Clang 14 - Add new Interpreter methods: getExecutionEngine,
// getSymbolAddress, getSymbolAddressFromLinkerName
// Clang 15 - Add new Interpreter methods: Undo
// Clang 18 - Add new Interpreter methods: CodeComplete

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

inline void codeComplete(std::vector<std::string>& Results,
clang::Interpreter& I, const char* code,
unsigned complete_line = 1U,
unsigned complete_column = 1U) {
#if CLANG_VERSION_MAJOR >= 18
// FIXME: We should match the invocation arguments of the main interpreter.
// That can affect the returned completion results.
auto CB = clang::IncrementalCompilerBuilder();
auto CI = CB.CreateCpp();
if (auto Err = CI.takeError()) {
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: ");
return;
}
auto Interp = clang::Interpreter::create(std::move(*CI));
if (auto Err = Interp.takeError()) {
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: ");
return;
}

std::vector<std::string> results;
std::vector<std::string> Comps;
clang::CompilerInstance* MainCI = (*Interp)->getCompilerInstance();
auto CC = clang::ReplCodeCompleter();
CC.codeComplete(MainCI, code, complete_line, complete_column,
I.getCompilerInstance(), results);
for (llvm::StringRef r : results)
if (r.find(CC.Prefix) == 0)
Results.push_back(r.str());
#else
assert(false && "CodeCompletion API only available in Clang >= 18.");
#endif
}

} // namespace compat

#include "CppInterOpInterpreter.h"
Expand Down
7 changes: 7 additions & 0 deletions lib/Interpreter/CppInterOp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3257,4 +3257,11 @@ namespace Cpp {
return result;
}

void CodeComplete(std::vector<std::string>& Results, const char* code,
unsigned complete_line /* = 1U */,
unsigned complete_column /* = 1U */) {
compat::codeComplete(Results, getInterp(), code, complete_line,
complete_column);
}

} // end namespace Cpp
24 changes: 24 additions & 0 deletions unittests/CppInterOp/InterpreterTest.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#include "clang/Interpreter/CppInterOp.h"

#include "clang/Basic/Version.h"

#include "llvm/ADT/SmallString.h"
#include "llvm/Support/Path.h"

#include <gmock/gmock.h>
#include "gtest/gtest.h"

#include <algorithm>

using ::testing::StartsWith;

TEST(InterpreterTest, Version) {
Expand Down Expand Up @@ -103,3 +107,23 @@ TEST(InterpreterTest, DetectSystemCompilerIncludePaths) {
Cpp::DetectSystemCompilerIncludePaths(includes);
EXPECT_FALSE(includes.empty());
}

TEST(InterpreterTest, CodeCompletion) {
#if CLANG_VERSION_MAJOR >= 18 || defined(USE_CLING)
Cpp::CreateInterpreter();
std::vector<std::string> cc;
Cpp::Declare("int foo = 12;");
Cpp::CodeComplete(cc, "f", 1, 2);
// We check only for 'float' and 'foo', because they
// must be present in the result. Other hints may appear
// there, depending on the implementation, but these two
// are required to say that the test is working.
size_t cnt = 0;
for (auto& r : cc)
if (r == "float" || r == "foo")
cnt++;
EXPECT_EQ(2U, cnt); // float and foo
#else
GTEST_SKIP();
#endif
}

0 comments on commit b6e6e3e

Please sign in to comment.