Skip to content

[cling] Use clang-repl-style code completion #19375

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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion interpreter/cling/include/cling/Interpreter/CIFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ namespace cling {
clang::CompilerInstance*
createCI(llvm::StringRef Code, const InvocationOptions& Opts,
const char* LLVMDir, std::unique_ptr<clang::ASTConsumer> consumer,
const ModuleFileExtensions& moduleExtensions);
const ModuleFileExtensions& moduleExtensions,
bool AutoComplete = false);

clang::CompilerInstance*
createCI(MemBufPtr_t Buffer, int Argc, const char* const* Argv,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

using namespace clang;

namespace clang {
class CompilerInstance;
}

namespace cling {
/// \brief Create a new printing code-completion consumer that prints its
/// results to the given raw output stream.
Expand Down Expand Up @@ -50,6 +54,28 @@ namespace cling {
completions = m_Completions;
}
};

struct ClingCodeCompleter {
ClingCodeCompleter() = default;
std::string Prefix;

/// \param InterpCI [in] The compiler instance that is used to trigger code
/// completion

/// \param Content [in] The string where code completion is triggered.

/// \param Line [in] The line number of the code completion point.

/// \param Col [in] The column number of the code completion point.

/// \param ParentCI [in] The running interpreter compiler instance that
/// provides ASTContexts.

/// \param CCResults [out] The completion results.
void codeComplete(CompilerInstance* InterpCI, llvm::StringRef Content,
unsigned Line, unsigned Col, CompilerInstance* ParentCI,
std::vector<std::string>& CCResults);
};
}

#endif
155 changes: 95 additions & 60 deletions interpreter/cling/lib/Interpreter/CIFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "llvm/Config/llvm-config.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
Expand Down Expand Up @@ -1252,13 +1253,61 @@ namespace {
}
}

// Goal is to make this as close to the function in
// clang/lib/Interpreter/Interpreter.cpp taking only clang arguments
static llvm::Expected<std::unique_ptr<CompilerInstance>>
CreateCI(const std::vector<const char*>& ClangArgv, std::string ExeName,
std::unique_ptr<clang::driver::Compilation>& Compilation) {
auto InvocationPtr = std::make_shared<clang::CompilerInvocation>();

// The compiler invocation is the owner of the diagnostic options.
// Everything else points to them.
DiagnosticOptions& DiagOpts = InvocationPtr->getDiagnosticOpts();
llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
SetupDiagnostics(DiagOpts, ExeName);
if (!Diags) {
cling::errs() << "Could not setup diagnostic engine.\n";
return nullptr;
}

llvm::Triple TheTriple(llvm::sys::getProcessTriple());
clang::driver::Driver Drvr(ClangArgv[0], TheTriple.getTriple(), *Diags);
// Drvr.setWarnMissingInput(false);
Drvr.setCheckInputsExist(false); // think foo.C(12)
llvm::ArrayRef<const char*> RF(&(ClangArgv[0]), ClangArgv.size());
Compilation.reset(Drvr.BuildCompilation(RF));
if (!Compilation) {
cling::errs() << "Couldn't create clang::driver::Compilation.\n";
return nullptr;
}

const llvm::opt::ArgStringList* CC1Args =
GetCC1Arguments(Compilation.get());
if (!CC1Args) {
cling::errs() << "Could not get cc1 arguments.\n";
return nullptr;
}

clang::CompilerInvocation::CreateFromArgs(*InvocationPtr, *CC1Args, *Diags);
// We appreciate the error message about an unknown flag (or do we? if not
// we should switch to a different DiagEngine for parsing the flags).
// But in general we'll happily go on.
Diags->Reset();

// Create and setup a compiler instance.
std::unique_ptr<CompilerInstance> CI(new CompilerInstance());
CI->setInvocation(InvocationPtr);
CI->setDiagnostics(Diags.get()); // Diags is ref-counted

return CI;
}

static CompilerInstance*
createCIImpl(std::unique_ptr<llvm::MemoryBuffer> Buffer,
const CompilerOptions& COpts,
const char* LLVMDir,
const CompilerOptions& COpts, const char* LLVMDir,
std::unique_ptr<clang::ASTConsumer> customConsumer,
const CIFactory::ModuleFileExtensions& moduleExtensions,
bool OnlyLex, bool HasInput = false) {
bool OnlyLex, bool HasInput = false, bool AutoComplete = false) {
// Follow clang -v convention of printing version on first line
if (COpts.Verbose)
cling::log() << "cling version " << ClingStringify(CLING_VERSION) << '\n';
Expand Down Expand Up @@ -1405,58 +1454,37 @@ namespace {
#endif
}
#endif

if (!COpts.HasOutput || !HasInput) {
if ((!COpts.HasOutput || !HasInput) && !AutoComplete) {
argvCompile.push_back("-");
}

auto InvocationPtr = std::make_shared<clang::CompilerInvocation>();
if (AutoComplete) {
// Put a dummy C++ file on to ensure there's at least one compile job for
// the driver to construct.
argvCompile.push_back("<<< cling interactive line includer >>>");
}

// The compiler invocation is the owner of the diagnostic options.
// Everything else points to them.
DiagnosticOptions& DiagOpts = InvocationPtr->getDiagnosticOpts();
// add prefix to diagnostic messages if second compiler instance is existing
// e.g. in CUDA mode
std::string ExeName = "";
if (COpts.CUDAHost)
ExeName = "cling";
if (COpts.CUDADevice)
ExeName = "cling-ptx";
llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
SetupDiagnostics(DiagOpts, ExeName);
if (!Diags) {
cling::errs() << "Could not setup diagnostic engine.\n";
return nullptr;
}

llvm::Triple TheTriple(llvm::sys::getProcessTriple());
clang::driver::Driver Drvr(argv[0], TheTriple.getTriple(), *Diags);
//Drvr.setWarnMissingInput(false);
Drvr.setCheckInputsExist(false); // think foo.C(12)
llvm::ArrayRef<const char*>RF(&(argvCompile[0]), argvCompile.size());
std::unique_ptr<clang::driver::Compilation>
Compilation(Drvr.BuildCompilation(RF));
if (!Compilation) {
cling::errs() << "Couldn't create clang::driver::Compilation.\n";
return nullptr;
}
std::unique_ptr<clang::driver::Compilation> Compilation;

const llvm::opt::ArgStringList* CC1Args = GetCC1Arguments(Compilation.get());
if (!CC1Args) {
cling::errs() << "Could not get cc1 arguments.\n";
auto CIOrErr = CreateCI(argvCompile, ExeName, Compilation);
if (!CIOrErr) {
cling::errs() << "Could not create CI: " << CIOrErr.takeError() << "\n";
return nullptr;
}
std::unique_ptr<clang::CompilerInstance> CI = std::move(*CIOrErr);

clang::CompilerInvocation::CreateFromArgs(*InvocationPtr, *CC1Args, *Diags);
// We appreciate the error message about an unknown flag (or do we? if not
// we should switch to a different DiagEngine for parsing the flags).
// But in general we'll happily go on.
Diags->Reset();
DiagnosticsEngine& Diags = CI->getDiagnostics();
CompilerInvocation& Invocation = CI->getInvocation();
DiagnosticOptions& DiagOpts = Invocation.getDiagnosticOpts();

// Create and setup a compiler instance.
std::unique_ptr<CompilerInstance> CI(new CompilerInstance());
CI->setInvocation(InvocationPtr);
CI->setDiagnostics(Diags.get()); // Diags is ref-counted
if (!OnlyLex)
CI->getDiagnosticOpts().ShowColors =
llvm::sys::Process::StandardOutIsDisplayed() ||
Expand All @@ -1469,9 +1497,9 @@ namespace {
// Copied from CompilerInstance::createDiagnostics:
// Chain in -verify checker, if requested.
if (DiagOpts.VerifyDiagnostics)
Diags->setClient(new clang::VerifyDiagnosticConsumer(*Diags));
Diags.setClient(new clang::VerifyDiagnosticConsumer(Diags));
// Configure our handling of diagnostics.
ProcessWarningOptions(*Diags, DiagOpts);
ProcessWarningOptions(Diags, DiagOpts);

if (COpts.HasOutput && !OnlyLex) {
ActionScan scan(clang::driver::Action::PrecompileJobClass,
Expand All @@ -1484,14 +1512,13 @@ namespace {
if (!SetupCompiler(CI.get(), COpts))
return nullptr;

ProcessWarningOptions(*Diags, DiagOpts);
ProcessWarningOptions(Diags, DiagOpts);
return CI.release();
}

IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> Overlay =
new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem());
CI->createFileManager(Overlay);
clang::CompilerInvocation& Invocation = CI->getInvocation();
std::string& PCHFile = Invocation.getPreprocessorOpts().ImplicitPCHInclude;
bool InitLang = true, InitTarget = true;
if (!PCHFile.empty()) {
Expand Down Expand Up @@ -1543,14 +1570,14 @@ namespace {
// When running interactively pass on the info that the PCH
// has failed so that IncrmentalParser::Initialize won't try again.
if (!HasInput && llvm::sys::Process::StandardInIsUserInput()) {
const unsigned ID = Diags->getCustomDiagID(
clang::DiagnosticsEngine::Level::Error,
"Problems loading PCH: '%0'.");
Diags->Report(ID) << PCHFile;
const unsigned ID =
Diags.getCustomDiagID(clang::DiagnosticsEngine::Level::Error,
"Problems loading PCH: '%0'.");

Diags.Report(ID) << PCHFile;
// If this was the only error, then don't let it stop anything
if (Diags->getClient()->getNumErrors() == 1)
Diags->Reset(true);
if (Diags.getClient()->getNumErrors() == 1)
Diags.Reset(true);
// Clear the include so no one else uses it.
std::string().swap(PCHFile);
}
Expand Down Expand Up @@ -1612,7 +1639,14 @@ namespace {
= const_cast<SrcMgr::ContentCache&>(MainFileFI.getContentCache());
if (!Buffer)
Buffer = llvm::MemoryBuffer::getMemBuffer("/*CLING DEFAULT MEMBUF*/;\n");
MainFileCC.setBuffer(std::move(Buffer));
if (AutoComplete) {
// Adapted from upstream clang/lib/Interpreter/Interpreter.cpp
// FIXME: Merge with CompilerInstance::ExecuteAction.
llvm::MemoryBuffer* MB = Buffer.release();
CI->getPreprocessorOpts().addRemappedFile(Filename, MB);
} else {
MainFileCC.setBuffer(std::move(Buffer));
}

// Create TargetInfo for the other side of CUDA and OpenMP compilation.
if ((CI->getLangOpts().CUDA || CI->getLangOpts().OpenMPIsTargetDevice) &&
Expand Down Expand Up @@ -1780,16 +1814,17 @@ namespace {

namespace cling {

CompilerInstance*
CIFactory::createCI(llvm::StringRef Code, const InvocationOptions& Opts,
const char* LLVMDir,
std::unique_ptr<clang::ASTConsumer> consumer,
const ModuleFileExtensions& moduleExtensions) {
return createCIImpl(llvm::MemoryBuffer::getMemBuffer(Code), Opts.CompilerOpts,
LLVMDir, std::move(consumer), moduleExtensions,
false /*OnlyLex*/,
!Opts.IsInteractive());
}
CompilerInstance*
CIFactory::createCI(llvm::StringRef Code, const InvocationOptions& Opts,
const char* LLVMDir,
std::unique_ptr<clang::ASTConsumer> consumer,
const ModuleFileExtensions& moduleExtensions,
bool AutoComplete /*false*/) {
return createCIImpl(llvm::MemoryBuffer::getMemBuffer(Code),
Opts.CompilerOpts, LLVMDir, std::move(consumer),
moduleExtensions, false /*OnlyLex*/,
!Opts.IsInteractive(), AutoComplete);
}

CompilerInstance* CIFactory::createCI(
MemBufPtr_t Buffer, int argc, const char* const* argv, const char* LLVMDir,
Expand Down
Loading
Loading