diff --git a/clang/include/clang/Frontend/ASTUnit.h b/clang/include/clang/Frontend/ASTUnit.h --- a/clang/include/clang/Frontend/ASTUnit.h +++ b/clang/include/clang/Frontend/ASTUnit.h @@ -897,7 +897,8 @@ DiagnosticsEngine &Diag, LangOptions &LangOpts, SourceManager &SourceMgr, FileManager &FileMgr, SmallVectorImpl &StoredDiagnostics, - SmallVectorImpl &OwnedBuffers); + SmallVectorImpl &OwnedBuffers, + std::function AfterBeginSourceFile = [](CompilerInstance& CI) -> void {}); /// Save this translation unit to a file with the given name. /// diff --git a/clang/include/clang/Interpreter/CodeCompletion.h b/clang/include/clang/Interpreter/CodeCompletion.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/Interpreter/CodeCompletion.h @@ -0,0 +1,38 @@ +//===------ CodeCompletion.h - Code Completion for ClangRepl -------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines the classes which performs code completion at the REPL. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_INTERPRETER_CODE_COMPLETION_H +#define LLVM_CLANG_INTERPRETER_CODE_COMPLETION_H +#include "clang/Sema/CodeCompleteConsumer.h" + +namespace clang { +class ReplCompletionConsumer : public CodeCompleteConsumer { +public: + ReplCompletionConsumer(std::vector &Results); + void ProcessCodeCompleteResults(class Sema &S, CodeCompletionContext Context, + CodeCompletionResult *InResults, + unsigned NumResults) final; + + CodeCompletionAllocator &getAllocator() override { return *CCAllocator; } + + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } + +private: + std::shared_ptr CCAllocator; + CodeCompletionTUInfo CCTUInfo; + std::vector &Results; +}; + +std::vector ConvertToCodeCompleteStrings( + const std::vector &Results); +} // namespace clang +#endif diff --git a/clang/include/clang/Interpreter/ExternalSource.h b/clang/include/clang/Interpreter/ExternalSource.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/Interpreter/ExternalSource.h @@ -0,0 +1,38 @@ +//==----- ExternalSource.h - External AST Source for Code Completion ------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines components that make declarations parsed and executed by +// the interpreter visible to the context where code completion is being +// triggered. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ExternalASTSource.h" + +namespace clang { +class ASTContext; +class FileManager; +class ASTImporter; + +class ExternalSource : public clang::ExternalASTSource { + ASTContext &ChildASTCtxt; + TranslationUnitDecl *ChildTUDeclCtxt; + ASTContext &ParentASTCtxt; + TranslationUnitDecl *ParentTUDeclCtxt; + + std::unique_ptr Importer; + +public: + ExternalSource(ASTContext &ChildASTCtxt, FileManager &ChildFM, + ASTContext &ParentASTCtxt, FileManager &ParentFM); + bool FindExternalVisibleDeclsByName(const DeclContext *DC, + DeclarationName Name) override; + void + completeVisibleDeclsMap(const clang::DeclContext *childDeclContext) override; +}; +} // namespace clang diff --git a/clang/include/clang/Interpreter/Interpreter.h b/clang/include/clang/Interpreter/Interpreter.h --- a/clang/include/clang/Interpreter/Interpreter.h +++ b/clang/include/clang/Interpreter/Interpreter.h @@ -35,6 +35,8 @@ namespace clang { +class CodeCompleteConsumer; +class CodeCompletionResult; class CompilerInstance; class IncrementalExecutor; class IncrementalParser; @@ -72,6 +74,8 @@ llvm::StringRef CudaSDKPath; }; +const std::string CodeCompletionFileName = "input_line_[Completion]"; + /// Provides top-level interfaces for incremental compilation and execution. class Interpreter { std::unique_ptr TSCtx; @@ -93,11 +97,14 @@ public: ~Interpreter(); + static llvm::Expected> create(std::unique_ptr CI); + static llvm::Expected> createWithCUDA(std::unique_ptr CI, std::unique_ptr DCI); + const ASTContext &getASTContext() const; ASTContext &getASTContext(); const CompilerInstance *getCompilerInstance() const; @@ -111,6 +118,10 @@ /// Undo N previous incremental inputs. llvm::Error Undo(unsigned N = 1); + void codeComplete(llvm::StringRef Content, unsigned Col, + const clang::CompilerInstance *ParentCI, + std::vector &CCResults); + /// Link a dynamic library llvm::Error LoadDynamicLibrary(const char *name); diff --git a/clang/include/clang/Sema/CodeCompleteConsumer.h b/clang/include/clang/Sema/CodeCompleteConsumer.h --- a/clang/include/clang/Sema/CodeCompleteConsumer.h +++ b/clang/include/clang/Sema/CodeCompleteConsumer.h @@ -336,7 +336,10 @@ CCC_Recovery, /// Code completion in a @class forward declaration. - CCC_ObjCClassForwardDecl + CCC_ObjCClassForwardDecl, + + /// Code completion at a top level in a REPL session. + CCC_ReplTopLevel, }; using VisitedContextSet = llvm::SmallPtrSet; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -13319,7 +13319,9 @@ PCC_ParenthesizedExpression, /// Code completion occurs within a sequence of declaration /// specifiers within a function, method, or block. - PCC_LocalDeclarationSpecifiers + PCC_LocalDeclarationSpecifiers, + /// Code completion occurs at top-level in a REPL session + PCC_TopLevelStmtDecl, }; void CodeCompleteModuleImport(SourceLocation ImportLoc, ModuleIdPath Path); diff --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp --- a/clang/lib/Frontend/ASTUnit.cpp +++ b/clang/lib/Frontend/ASTUnit.cpp @@ -2005,6 +2005,7 @@ case CodeCompletionContext::CCC_SymbolOrNewName: case CodeCompletionContext::CCC_ParenthesizedExpression: case CodeCompletionContext::CCC_ObjCInterfaceName: + case CodeCompletionContext::CCC_ReplTopLevel: break; case CodeCompletionContext::CCC_EnumTag: @@ -2164,7 +2165,7 @@ std::shared_ptr PCHContainerOps, DiagnosticsEngine &Diag, LangOptions &LangOpts, SourceManager &SourceMgr, FileManager &FileMgr, SmallVectorImpl &StoredDiagnostics, - SmallVectorImpl &OwnedBuffers) { + SmallVectorImpl &OwnedBuffers, std::function AfterBeginSourceFile) { if (!Invocation) return; @@ -2304,6 +2305,7 @@ std::unique_ptr Act; Act.reset(new SyntaxOnlyAction); if (Act->BeginSourceFile(*Clang.get(), Clang->getFrontendOpts().Inputs[0])) { + AfterBeginSourceFile(*Clang.get()); if (llvm::Error Err = Act->Execute()) { consumeError(std::move(Err)); // FIXME this drops errors on the floor. } diff --git a/clang/lib/Interpreter/CMakeLists.txt b/clang/lib/Interpreter/CMakeLists.txt --- a/clang/lib/Interpreter/CMakeLists.txt +++ b/clang/lib/Interpreter/CMakeLists.txt @@ -12,7 +12,9 @@ ) add_clang_library(clangInterpreter + CodeCompletion.cpp DeviceOffload.cpp + ExternalSource.cpp IncrementalExecutor.cpp IncrementalParser.cpp Interpreter.cpp diff --git a/clang/lib/Interpreter/CodeCompletion.cpp b/clang/lib/Interpreter/CodeCompletion.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Interpreter/CodeCompletion.cpp @@ -0,0 +1,77 @@ +//===------ CodeCompletion.cpp - Code Completion for ClangRepl -------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements the classes which performs code completion at the REPL. +// +//===----------------------------------------------------------------------===// + +#include "clang/Interpreter/CodeCompletion.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Interpreter/Interpreter.h" +#include "clang/Lex/PreprocessorOptions.h" +#include "clang/Sema/CodeCompleteConsumer.h" +#include "clang/Sema/CodeCompleteOptions.h" +#include "clang/Sema/Sema.h" + +namespace clang { + +clang::CodeCompleteOptions getClangCompleteOpts() { + clang::CodeCompleteOptions Opts; + Opts.IncludeCodePatterns = true; + Opts.IncludeMacros = true; + Opts.IncludeGlobals = true; + Opts.IncludeBriefComments = true; + return Opts; +} + +ReplCompletionConsumer::ReplCompletionConsumer(std::vector &Results) + : CodeCompleteConsumer(getClangCompleteOpts()), + CCAllocator(std::make_shared()), + CCTUInfo(CCAllocator), Results(Results){}; + +void ReplCompletionConsumer::ProcessCodeCompleteResults( + class Sema &S, CodeCompletionContext Context, + CodeCompletionResult *InResults, unsigned NumResults) { + for (unsigned I = 0; I < NumResults; ++I) { + auto &Result = InResults[I]; + switch (Result.Kind) { + case CodeCompletionResult::RK_Declaration: + if (Result.Declaration->getIdentifier()) { + Results.push_back(Result); + } + break; + default: + break; + case CodeCompletionResult::RK_Keyword: + Results.push_back(Result); + break; + } + } +} + +std::vector ConvertToCodeCompleteStrings(const std::vector &Results) { + std::vector CompletionStrings; + for (auto Res : Results) { + switch (Res.Kind) { + case clang::CodeCompletionResult::RK_Declaration: + if (auto *ID = Res.Declaration->getIdentifier()) { + CompletionStrings.push_back(ID->getName()); + } + break; + case clang::CodeCompletionResult::RK_Keyword: + CompletionStrings.push_back(Res.Keyword); + break; + default: + break; + } + } + return CompletionStrings; +} + + +} // namespace clang diff --git a/clang/lib/Interpreter/DeviceOffload.cpp b/clang/lib/Interpreter/DeviceOffload.cpp --- a/clang/lib/Interpreter/DeviceOffload.cpp +++ b/clang/lib/Interpreter/DeviceOffload.cpp @@ -15,6 +15,7 @@ #include "clang/Basic/TargetOptions.h" #include "clang/CodeGen/ModuleBuilder.h" #include "clang/Frontend/CompilerInstance.h" +#include "clang/Sema/CodeCompleteConsumer.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/MC/TargetRegistry.h" diff --git a/clang/lib/Interpreter/ExternalSource.h b/clang/lib/Interpreter/ExternalSource.h new file mode 100644 --- /dev/null +++ b/clang/lib/Interpreter/ExternalSource.h @@ -0,0 +1,38 @@ +//==----- ExternalSource.h - External AST Source for Code Completion ------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines components that make declarations parsed and executed by +// the interpreter visible to the context where code completion is being +// triggered. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ExternalASTSource.h" + +namespace clang { +class ASTContext; +class FileManager; +class ASTImporter; + +class ExternalSource : public clang::ExternalASTSource { + ASTContext &ChildASTCtxt; + TranslationUnitDecl *ChildTUDeclCtxt; + ASTContext &ParentASTCtxt; + TranslationUnitDecl *ParentTUDeclCtxt; + + std::unique_ptr Importer; + +public: + ExternalSource(ASTContext &ChildASTCtxt, FileManager &ChildFM, + ASTContext &ParentASTCtxt, FileManager &ParentFM); + bool FindExternalVisibleDeclsByName(const DeclContext *DC, + DeclarationName Name) override; + void + completeVisibleDeclsMap(const clang::DeclContext *childDeclContext) override; +}; +} // namespace clang diff --git a/clang/lib/Interpreter/ExternalSource.cpp b/clang/lib/Interpreter/ExternalSource.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Interpreter/ExternalSource.cpp @@ -0,0 +1,77 @@ +//===--- ExternalSource.cpp - External AST Source for Code Completion ----===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// The file implements classes that make declarations parsed and executed by the +// interpreter visible to the context where code completion is being triggered. +// +//===----------------------------------------------------------------------===// + +#include "ExternalSource.h" +#include "clang/AST/ASTImporter.h" +#include "clang/AST/DeclarationName.h" +#include "clang/Basic/IdentifierTable.h" + +namespace clang { +ExternalSource::ExternalSource(ASTContext &ChildASTCtxt, FileManager &ChildFM, + ASTContext &ParentASTCtxt, FileManager &ParentFM) + : ChildASTCtxt(ChildASTCtxt), + ChildTUDeclCtxt(ChildASTCtxt.getTranslationUnitDecl()), + ParentASTCtxt(ParentASTCtxt), + ParentTUDeclCtxt(ParentASTCtxt.getTranslationUnitDecl()) { + ASTImporter *importer = + new ASTImporter(ChildASTCtxt, ChildFM, ParentASTCtxt, ParentFM, + /*MinimalImport : ON*/ true); + Importer.reset(importer); +} + +bool ExternalSource::FindExternalVisibleDeclsByName(const DeclContext *DC, + DeclarationName Name) { + IdentifierTable &ParentIdTable = ParentASTCtxt.Idents; + + auto ParentDeclName = + DeclarationName(&(ParentIdTable.get(Name.getAsString()))); + + DeclContext::lookup_result lookup_result = + ParentTUDeclCtxt->lookup(ParentDeclName); + + if (!lookup_result.empty()) { + return true; + } + return false; +} + +void ExternalSource::completeVisibleDeclsMap( + const DeclContext *ChildDeclContext) { + assert(ChildDeclContext && ChildDeclContext == ChildTUDeclCtxt && + "No child decl context!"); + + if (!ChildDeclContext->hasExternalVisibleStorage()) + return; + + for (auto *DeclCtxt = ParentTUDeclCtxt; DeclCtxt != nullptr; + DeclCtxt = DeclCtxt->getPreviousDecl()) { + for (auto &IDeclContext : DeclCtxt->decls()) { + if (NamedDecl *Decl = llvm::dyn_cast(IDeclContext)) { + if (auto DeclOrErr = Importer->Import(Decl)) { + if (NamedDecl *importedNamedDecl = + llvm::dyn_cast(*DeclOrErr)) { + SetExternalVisibleDeclsForName(ChildDeclContext, + importedNamedDecl->getDeclName(), + importedNamedDecl); + } + + } else { + llvm::consumeError(DeclOrErr.takeError()); + } + } + } + ChildDeclContext->setHasExternalLexicalStorage(false); + } +} + +} // namespace clang diff --git a/clang/lib/Interpreter/IncrementalParser.h b/clang/lib/Interpreter/IncrementalParser.h --- a/clang/lib/Interpreter/IncrementalParser.h +++ b/clang/lib/Interpreter/IncrementalParser.h @@ -24,7 +24,7 @@ #include namespace llvm { class LLVMContext; -} +} // namespace llvm namespace clang { class ASTConsumer; diff --git a/clang/lib/Interpreter/IncrementalParser.cpp b/clang/lib/Interpreter/IncrementalParser.cpp --- a/clang/lib/Interpreter/IncrementalParser.cpp +++ b/clang/lib/Interpreter/IncrementalParser.cpp @@ -157,16 +157,11 @@ TranslationUnitKind getTranslationUnitKind() override { return TU_Incremental; } + void ExecuteAction() override { CompilerInstance &CI = getCompilerInstance(); assert(CI.hasPreprocessor() && "No PP!"); - // FIXME: Move the truncation aspect of this into Sema, we delayed this till - // here so the source manager would be initialized. - if (hasCodeCompletionSupport() && - !CI.getFrontendOpts().CodeCompletionAt.FileName.empty()) - CI.createCodeCompletionConsumer(); - // Use a code completion consumer? CodeCompleteConsumer *CompletionConsumer = nullptr; if (CI.hasCodeCompletionConsumer()) diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp --- a/clang/lib/Interpreter/Interpreter.cpp +++ b/clang/lib/Interpreter/Interpreter.cpp @@ -14,6 +14,7 @@ #include "clang/Interpreter/Interpreter.h" #include "DeviceOffload.h" +#include "ExternalSource.h" #include "IncrementalExecutor.h" #include "IncrementalParser.h" @@ -31,10 +32,13 @@ #include "clang/Driver/Job.h" #include "clang/Driver/Options.h" #include "clang/Driver/Tool.h" +#include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/TextDiagnosticBuffer.h" +#include "clang/Interpreter/CodeCompletion.h" #include "clang/Interpreter/Value.h" #include "clang/Lex/PreprocessorOptions.h" +#include "clang/Sema/CodeCompleteConsumer.h" #include "clang/Sema/Lookup.h" #include "llvm/ExecutionEngine/JITSymbol.h" #include "llvm/ExecutionEngine/Orc/LLJIT.h" @@ -127,7 +131,6 @@ Clang->getFrontendOpts().DisableFree = false; Clang->getCodeGenOpts().DisableFree = false; - return std::move(Clang); } @@ -272,10 +275,12 @@ llvm::Expected> Interpreter::create(std::unique_ptr CI) { llvm::Error Err = llvm::Error::success(); - auto Interp = + std::unique_ptr Interp = std::unique_ptr(new Interpreter(std::move(CI), Err)); + if (Err) return std::move(Err); + auto PTU = Interp->Parse(Runtimes); if (!PTU) return PTU.takeError(); @@ -456,6 +461,45 @@ return llvm::Error::success(); } +void Interpreter::codeComplete(llvm::StringRef Content, unsigned Col, + const clang::CompilerInstance *ParentCI, + std::vector &CCResults) { + std::unique_ptr MB = + llvm::MemoryBuffer::getMemBufferCopy(Content, CodeCompletionFileName); + llvm::SmallVector RemappedFiles; + + RemappedFiles.push_back(std::make_pair(CodeCompletionFileName, MB.release())); + + auto DiagOpts = DiagnosticOptions(); + auto consumer = ReplCompletionConsumer(CCResults); + + auto *InterpCI = const_cast(this->getCompilerInstance()); + auto diag = InterpCI->getDiagnosticsPtr(); + ASTUnit *AU = ASTUnit::LoadFromCompilerInvocationAction( + InterpCI->getInvocationPtr(), std::make_shared(), + diag); + llvm::SmallVector sd = {}; + llvm::SmallVector tb = {}; + InterpCI->getFrontendOpts().Inputs[0] = FrontendInputFile( + CodeCompletionFileName, Language::CXX, InputKind::Source); + AU->CodeComplete( + CodeCompletionFileName, 1, Col, RemappedFiles, false, false, false, + consumer, std::make_shared(), *diag, + InterpCI->getLangOpts(), InterpCI->getSourceManager(), + InterpCI->getFileManager(), sd, tb, + [ParentCI](clang::CompilerInstance &CI) -> void { + clang::ExternalSource *myExternalSource = new clang::ExternalSource( + CI.getASTContext(), CI.getFileManager(), ParentCI->getASTContext(), + ParentCI->getFileManager()); + llvm::IntrusiveRefCntPtr + astContextExternalSource(myExternalSource); + CI.getASTContext().setExternalSource(astContextExternalSource); + CI.getASTContext() + .getTranslationUnitDecl() + ->setHasExternalVisibleStorage(true); + }); +} + llvm::Error Interpreter::LoadDynamicLibrary(const char *name) { auto EE = getExecutionEngine(); if (!EE) diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -18,6 +18,7 @@ #include "clang/Basic/Attributes.h" #include "clang/Basic/CharInfo.h" #include "clang/Basic/TargetInfo.h" +#include "clang/Basic/TokenKinds.h" #include "clang/Parse/ParseDiagnostic.h" #include "clang/Parse/Parser.h" #include "clang/Parse/RAIIObjectsForParser.h" @@ -6640,6 +6641,16 @@ while (true) { if (Tok.is(tok::l_paren)) { + if (PP.isIncrementalProcessingEnabled() && NextToken().is(tok::code_completion)) { + // In clang-repl, code completion for input like `void foo(` should not trigger a parsing error. + // So we make the declarator malformed and exits the loop. + ConsumeParen(); + cutOffParsing(); + D.SetIdentifier(nullptr, Tok.getLocation()); + D.setInvalidType(true); + break; + } + bool IsFunctionDeclaration = D.isFunctionDeclaratorAFunctionDeclaration(); // Enter function-declaration scope, limiting any declarators to the // function prototype scope, including parameter declarators. diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -923,9 +923,18 @@ /*IsInstanceMethod=*/std::nullopt, /*ReturnType=*/nullptr); } + + Sema::ParserCompletionContext PCC; + if (CurParsedObjCImpl) { + PCC = Sema::PCC_ObjCImplementation; + } else if (PP.isIncrementalProcessingEnabled()) { + PCC = Sema::PCC_TopLevelStmtDecl; + } else { + PCC = Sema::PCC_Namespace; + }; Actions.CodeCompleteOrdinaryName( getCurScope(), - CurParsedObjCImpl ? Sema::PCC_ObjCImplementation : Sema::PCC_Namespace); + PCC); return nullptr; case tok::kw_import: { Sema::ModuleImportState IS = Sema::ModuleImportState::NotACXX20Module; diff --git a/clang/lib/Sema/CodeCompleteConsumer.cpp b/clang/lib/Sema/CodeCompleteConsumer.cpp --- a/clang/lib/Sema/CodeCompleteConsumer.cpp +++ b/clang/lib/Sema/CodeCompleteConsumer.cpp @@ -51,6 +51,7 @@ case CCC_ParenthesizedExpression: case CCC_Symbol: case CCC_SymbolOrNewName: + case CCC_ReplTopLevel: return true; case CCC_TopLevel: @@ -169,6 +170,8 @@ return "Recovery"; case CCKind::CCC_ObjCClassForwardDecl: return "ObjCClassForwardDecl"; + case CCKind::CCC_ReplTopLevel: + return "ReplTopLevel"; } llvm_unreachable("Invalid CodeCompletionContext::Kind!"); } diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -225,6 +225,7 @@ case CodeCompletionContext::CCC_ObjCMessageReceiver: case CodeCompletionContext::CCC_ParenthesizedExpression: case CodeCompletionContext::CCC_Statement: + case CodeCompletionContext::CCC_ReplTopLevel: case CodeCompletionContext::CCC_Recovery: if (ObjCMethodDecl *Method = SemaRef.getCurMethodDecl()) if (Method->isInstanceMethod()) @@ -1850,6 +1851,7 @@ case Sema::PCC_ObjCInstanceVariableList: case Sema::PCC_Expression: case Sema::PCC_Statement: + case Sema::PCC_TopLevelStmtDecl: case Sema::PCC_ForInit: case Sema::PCC_Condition: case Sema::PCC_RecoveryInFunction: @@ -1907,6 +1909,7 @@ case Sema::PCC_Type: case Sema::PCC_ParenthesizedExpression: case Sema::PCC_LocalDeclarationSpecifiers: + case Sema::PCC_TopLevelStmtDecl: return true; case Sema::PCC_Expression: @@ -2219,6 +2222,7 @@ break; case Sema::PCC_RecoveryInFunction: + case Sema::PCC_TopLevelStmtDecl: case Sema::PCC_Statement: { if (SemaRef.getLangOpts().CPlusPlus11) AddUsingAliasResult(Builder, Results); @@ -4208,6 +4212,8 @@ case Sema::PCC_LocalDeclarationSpecifiers: return CodeCompletionContext::CCC_Type; + case Sema::PCC_TopLevelStmtDecl: + return CodeCompletionContext::CCC_ReplTopLevel; } llvm_unreachable("Invalid ParserCompletionContext!"); @@ -4348,6 +4354,7 @@ break; case PCC_Statement: + case PCC_TopLevelStmtDecl: case PCC_ParenthesizedExpression: case PCC_Expression: case PCC_ForInit: @@ -4385,6 +4392,7 @@ case PCC_ParenthesizedExpression: case PCC_Expression: case PCC_Statement: + case PCC_TopLevelStmtDecl: case PCC_RecoveryInFunction: if (S->getFnParent()) AddPrettyFunctionResults(getLangOpts(), Results); diff --git a/clang/test/CodeCompletion/incrememal-mode-completion-no-error.cpp b/clang/test/CodeCompletion/incrememal-mode-completion-no-error.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeCompletion/incrememal-mode-completion-no-error.cpp @@ -0,0 +1,3 @@ +void foo( +// RUN: %clang_cc1 -fincremental-extensions -fsyntax-only -code-completion-at=%s:%(line-1):9 %s | wc -c | FileCheck %s +// CHECK: 0 diff --git a/clang/test/CodeCompletion/incremental-top-level.cpp b/clang/test/CodeCompletion/incremental-top-level.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeCompletion/incremental-top-level.cpp @@ -0,0 +1,4 @@ +int foo = 10; +f +// RUN: %clang_cc1 -fincremental-extensions -fsyntax-only -code-completion-at=%s:%(line-1):1 %s | FileCheck %s +// CHECK: COMPLETION: foo : [#int#]foo diff --git a/clang/tools/clang-repl/ClangRepl.cpp b/clang/tools/clang-repl/ClangRepl.cpp --- a/clang/tools/clang-repl/ClangRepl.cpp +++ b/clang/tools/clang-repl/ClangRepl.cpp @@ -13,7 +13,9 @@ #include "clang/Basic/Diagnostic.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Interpreter/CodeCompletion.h" #include "clang/Interpreter/Interpreter.h" +#include "clang/Sema/CodeCompleteConsumer.h" #include "llvm/ExecutionEngine/Orc/LLJIT.h" #include "llvm/LineEditor/LineEditor.h" @@ -70,6 +72,62 @@ return (Errs || HasError) ? EXIT_FAILURE : EXIT_SUCCESS; } +struct ReplListCompleter { + clang::IncrementalCompilerBuilder &CB; + clang::Interpreter &MainInterp; + ReplListCompleter(clang::IncrementalCompilerBuilder &CB, + clang::Interpreter &Interp) + : CB(CB), MainInterp(Interp){}; + + std::vector operator()(llvm::StringRef Buffer, + size_t Pos) const { + auto Err = llvm::Error::success(); + auto res = (*this)(Buffer, Pos, Err); + if (Err) + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: "); + return res; + } + + std::vector + operator()(llvm::StringRef Buffer, size_t Pos, llvm::Error &ErrRes) const { + std::vector Comps; + std::vector Results; + + auto CI = CB.CreateCpp(); + if (auto Err = CI.takeError()) { + ErrRes = std::move(Err); + return {}; + } + + size_t Lines = std::count(Buffer.begin(), Buffer.end(), '\n') + 1; + auto Interp = clang::Interpreter::create(std::move(*CI)); + + if (auto Err = Interp.takeError()) { + // log the error and returns an empty vector; + ErrRes = std::move(Err); + + return {}; + } + + (*Interp)->codeComplete(Buffer, Pos + 1, MainInterp.getCompilerInstance(), Results); + + size_t space_pos = Buffer.rfind(" "); + llvm::StringRef s; + if (space_pos == llvm::StringRef::npos) { + s = Buffer; + } else { + s = Buffer.substr(space_pos + 1); + } + + for (auto c : ConvertToCodeCompleteStrings(Results)) { + if (c.startswith(s)) + Comps.push_back( + llvm::LineEditor::Completion(c.substr(s.size()).str(), c.str())); + } + return Comps; + } +}; + llvm::ExitOnError ExitOnErr; int main(int argc, const char **argv) { ExitOnErr.setBanner("clang-repl: "); @@ -133,6 +191,7 @@ DeviceCI->LoadRequestedPlugins(); std::unique_ptr Interp; + if (CudaEnabled) { Interp = ExitOnErr( clang::Interpreter::createWithCUDA(std::move(CI), std::move(DeviceCI))); @@ -155,8 +214,8 @@ if (OptInputs.empty()) { llvm::LineEditor LE("clang-repl"); - // FIXME: Add LE.setListCompleter std::string Input; + LE.setListCompleter(ReplListCompleter(CB, *Interp)); while (std::optional Line = LE.readLine()) { llvm::StringRef L = *Line; L = L.trim(); @@ -168,10 +227,10 @@ } Input += L; - if (Input == R"(%quit)") { break; - } else if (Input == R"(%undo)") { + } + if (Input == R"(%undo)") { if (auto Err = Interp->Undo()) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: "); HasError = true; diff --git a/clang/tools/libclang/CIndexCodeCompletion.cpp b/clang/tools/libclang/CIndexCodeCompletion.cpp --- a/clang/tools/libclang/CIndexCodeCompletion.cpp +++ b/clang/tools/libclang/CIndexCodeCompletion.cpp @@ -543,6 +543,7 @@ case CodeCompletionContext::CCC_PreprocessorExpression: case CodeCompletionContext::CCC_PreprocessorDirective: case CodeCompletionContext::CCC_Attribute: + case CodeCompletionContext::CCC_ReplTopLevel: case CodeCompletionContext::CCC_TypeQualifiers: { //Only Clang results should be accepted, so we'll set all of the other //context bits to 0 (i.e. the empty set) diff --git a/clang/unittests/Interpreter/CMakeLists.txt b/clang/unittests/Interpreter/CMakeLists.txt --- a/clang/unittests/Interpreter/CMakeLists.txt +++ b/clang/unittests/Interpreter/CMakeLists.txt @@ -9,6 +9,7 @@ add_clang_unittest(ClangReplInterpreterTests IncrementalProcessingTest.cpp InterpreterTest.cpp + CodeCompletionTest.cpp ) target_link_libraries(ClangReplInterpreterTests PUBLIC clangAST diff --git a/clang/unittests/Interpreter/CodeCompletionTest.cpp b/clang/unittests/Interpreter/CodeCompletionTest.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Interpreter/CodeCompletionTest.cpp @@ -0,0 +1,100 @@ +#include "clang/Interpreter/CodeCompletion.h" +#include "clang/Interpreter/Interpreter.h" + +#include "clang/Frontend/CompilerInstance.h" +#include "llvm/LineEditor/LineEditor.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace clang; +namespace { +auto CB = clang::IncrementalCompilerBuilder(); + +static std::unique_ptr createInterpreter() { + auto CI = cantFail(CB.CreateCpp()); + return cantFail(clang::Interpreter::create(std::move(CI))); +} + +static std::vector runComp(clang::Interpreter &MainInterp, + llvm::StringRef Prefix, + llvm::Error &ErrR) { + auto CI = CB.CreateCpp(); + if (auto Err = CI.takeError()) { + ErrR = std::move(Err); + return {}; + } + + + auto Interp = clang::Interpreter::create(std::move(*CI)); + if (auto Err = Interp.takeError()) { + // log the error and returns an empty vector; + ErrR = std::move(Err); + + return {}; + } + + std::vector Results; + + (*Interp)->codeComplete(Prefix, Prefix.size(), MainInterp.getCompilerInstance(), Results); + + std::vector Comps; + for (auto c : ConvertToCodeCompleteStrings(Results)) { + if (c.startswith(Prefix)) + Comps.push_back(c.substr(Prefix.size()).str()); + } + + return Comps; +} + +TEST(CodeCompletionTest, Sanity) { + auto Interp = createInterpreter(); + if (auto R = Interp->ParseAndExecute("int foo = 12;")) { + consumeError(std::move(R)); + return; + } + auto Err = llvm::Error::success(); + auto comps = runComp(*Interp, "f", Err); + EXPECT_EQ((size_t)2, comps.size()); // foo and float + EXPECT_EQ(comps[0], std::string("oo")); + EXPECT_EQ((bool)Err, false); +} + +TEST(CodeCompletionTest, SanityNoneValid) { + auto Interp = createInterpreter(); + if (auto R = Interp->ParseAndExecute("int foo = 12;")) { + consumeError(std::move(R)); + return; + } + auto Err = llvm::Error::success(); + auto comps = runComp(*Interp, "babanana", Err); + EXPECT_EQ((size_t)0, comps.size()); // foo and float + EXPECT_EQ((bool)Err, false); +} + +TEST(CodeCompletionTest, TwoDecls) { + auto Interp = createInterpreter(); + if (auto R = Interp->ParseAndExecute("int application = 12;")) { + consumeError(std::move(R)); + return; + } + if (auto R = Interp->ParseAndExecute("int apple = 12;")) { + consumeError(std::move(R)); + return; + } + auto Err = llvm::Error::success(); + auto comps = runComp(*Interp, "app", Err); + EXPECT_EQ((size_t)2, comps.size()); + EXPECT_EQ((bool)Err, false); +} + +TEST(CodeCompletionTest, CompFunDeclsNoError) { + auto Interp = createInterpreter(); + auto Err = llvm::Error::success(); + auto comps = runComp(*Interp, "void app(", Err); + EXPECT_EQ((bool)Err, false); +} + +} // anonymous namespace