//===- PDLLServer.cpp - PDLL Language Server ------------------------------===// // // 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 // //===----------------------------------------------------------------------===// #include "PDLLServer.h" #include "Protocol.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/Support/ToolUtilities.h" #include "mlir/Tools/PDLL/AST/Context.h" #include "mlir/Tools/PDLL/AST/Nodes.h" #include "mlir/Tools/PDLL/AST/Types.h" #include "mlir/Tools/PDLL/CodeGen/CPPGen.h" #include "mlir/Tools/PDLL/CodeGen/MLIRGen.h" #include "mlir/Tools/PDLL/ODS/Constraint.h" #include "mlir/Tools/PDLL/ODS/Context.h" #include "mlir/Tools/PDLL/ODS/Dialect.h" #include "mlir/Tools/PDLL/ODS/Operation.h" #include "mlir/Tools/PDLL/Parser/CodeComplete.h" #include "mlir/Tools/PDLL/Parser/Parser.h" #include "mlir/Tools/lsp-server-support/CompilationDatabase.h" #include "mlir/Tools/lsp-server-support/Logging.h" #include "mlir/Tools/lsp-server-support/SourceMgrUtils.h" #include "llvm/ADT/IntervalMap.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringSet.h" #include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include using namespace mlir; using namespace mlir::pdll; /// Returns a language server uri for the given source location. `mainFileURI` /// corresponds to the uri for the main file of the source manager. static lsp::URIForFile getURIFromLoc(llvm::SourceMgr &mgr, SMRange loc, const lsp::URIForFile &mainFileURI) { int bufferId = mgr.FindBufferContainingLoc(loc.Start); if (bufferId == 0 || bufferId == static_cast(mgr.getMainFileID())) return mainFileURI; llvm::Expected fileForLoc = lsp::URIForFile::fromFile( mgr.getBufferInfo(bufferId).Buffer->getBufferIdentifier()); if (fileForLoc) return *fileForLoc; lsp::Logger::error("Failed to create URI for include file: {0}", llvm::toString(fileForLoc.takeError())); return mainFileURI; } /// Returns true if the given location is in the main file of the source /// manager. static bool isMainFileLoc(llvm::SourceMgr &mgr, SMRange loc) { return mgr.FindBufferContainingLoc(loc.Start) == mgr.getMainFileID(); } /// Returns a language server location from the given source range. static lsp::Location getLocationFromLoc(llvm::SourceMgr &mgr, SMRange range, const lsp::URIForFile &uri) { return lsp::Location(getURIFromLoc(mgr, range, uri), lsp::Range(mgr, range)); } /// Convert the given MLIR diagnostic to the LSP form. static std::optional getLspDiagnoticFromDiag(llvm::SourceMgr &sourceMgr, const ast::Diagnostic &diag, const lsp::URIForFile &uri) { lsp::Diagnostic lspDiag; lspDiag.source = "pdll"; // FIXME: Right now all of the diagnostics are treated as parser issues, but // some are parser and some are verifier. lspDiag.category = "Parse Error"; // Try to grab a file location for this diagnostic. lsp::Location loc = getLocationFromLoc(sourceMgr, diag.getLocation(), uri); lspDiag.range = loc.range; // Skip diagnostics that weren't emitted within the main file. if (loc.uri != uri) return std::nullopt; // Convert the severity for the diagnostic. switch (diag.getSeverity()) { case ast::Diagnostic::Severity::DK_Note: llvm_unreachable("expected notes to be handled separately"); case ast::Diagnostic::Severity::DK_Warning: lspDiag.severity = lsp::DiagnosticSeverity::Warning; break; case ast::Diagnostic::Severity::DK_Error: lspDiag.severity = lsp::DiagnosticSeverity::Error; break; case ast::Diagnostic::Severity::DK_Remark: lspDiag.severity = lsp::DiagnosticSeverity::Information; break; } lspDiag.message = diag.getMessage().str(); // Attach any notes to the main diagnostic as related information. std::vector relatedDiags; for (const ast::Diagnostic ¬e : diag.getNotes()) { relatedDiags.emplace_back( getLocationFromLoc(sourceMgr, note.getLocation(), uri), note.getMessage().str()); } if (!relatedDiags.empty()) lspDiag.relatedInformation = std::move(relatedDiags); return lspDiag; } /// Get or extract the documentation for the given decl. static std::optional getDocumentationFor(llvm::SourceMgr &sourceMgr, const ast::Decl *decl) { // If the decl already had documentation set, use it. if (std::optional doc = decl->getDocComment()) return doc->str(); // If the decl doesn't yet have documentation, try to extract it from the // source file. return lsp::extractSourceDocComment(sourceMgr, decl->getLoc().Start); } //===----------------------------------------------------------------------===// // PDLIndex //===----------------------------------------------------------------------===// namespace { struct PDLIndexSymbol { explicit PDLIndexSymbol(const ast::Decl *definition) : definition(definition) {} explicit PDLIndexSymbol(const ods::Operation *definition) : definition(definition) {} /// Return the location of the definition of this symbol. SMRange getDefLoc() const { if (const ast::Decl *decl = llvm::dyn_cast_if_present(definition)) { const ast::Name *declName = decl->getName(); return declName ? declName->getLoc() : decl->getLoc(); } return cast(definition)->getLoc(); } /// The main definition of the symbol. PointerUnion definition; /// The set of references to the symbol. std::vector references; }; /// This class provides an index for definitions/uses within a PDL document. /// It provides efficient lookup of a definition given an input source range. class PDLIndex { public: PDLIndex() : intervalMap(allocator) {} /// Initialize the index with the given ast::Module. void initialize(const ast::Module &module, const ods::Context &odsContext); /// Lookup a symbol for the given location. Returns nullptr if no symbol could /// be found. If provided, `overlappedRange` is set to the range that the /// provided `loc` overlapped with. const PDLIndexSymbol *lookup(SMLoc loc, SMRange *overlappedRange = nullptr) const; private: /// The type of interval map used to store source references. SMRange is /// half-open, so we also need to use a half-open interval map. using MapT = llvm::IntervalMap::LeafSize, llvm::IntervalMapHalfOpenInfo>; /// An allocator for the interval map. MapT::Allocator allocator; /// An interval map containing a corresponding definition mapped to a source /// interval. MapT intervalMap; /// A mapping between definitions and their corresponding symbol. DenseMap> defToSymbol; }; } // namespace void PDLIndex::initialize(const ast::Module &module, const ods::Context &odsContext) { auto getOrInsertDef = [&](const auto *def) -> PDLIndexSymbol * { auto it = defToSymbol.try_emplace(def, nullptr); if (it.second) it.first->second = std::make_unique(def); return &*it.first->second; }; auto insertDeclRef = [&](PDLIndexSymbol *sym, SMRange refLoc, bool isDef = false) { const char *startLoc = refLoc.Start.getPointer(); const char *endLoc = refLoc.End.getPointer(); if (!intervalMap.overlaps(startLoc, endLoc)) { intervalMap.insert(startLoc, endLoc, sym); if (!isDef) sym->references.push_back(refLoc); } }; auto insertODSOpRef = [&](StringRef opName, SMRange refLoc) { const ods::Operation *odsOp = odsContext.lookupOperation(opName); if (!odsOp) return; PDLIndexSymbol *symbol = getOrInsertDef(odsOp); insertDeclRef(symbol, odsOp->getLoc(), /*isDef=*/true); insertDeclRef(symbol, refLoc); }; module.walk([&](const ast::Node *node) { // Handle references to PDL decls. if (const auto *decl = dyn_cast(node)) { if (std::optional name = decl->getName()) insertODSOpRef(*name, decl->getLoc()); } else if (const ast::Decl *decl = dyn_cast(node)) { const ast::Name *name = decl->getName(); if (!name) return; PDLIndexSymbol *declSym = getOrInsertDef(decl); insertDeclRef(declSym, name->getLoc(), /*isDef=*/true); if (const auto *varDecl = dyn_cast(decl)) { // Record references to any constraints. for (const auto &it : varDecl->getConstraints()) insertDeclRef(getOrInsertDef(it.constraint), it.referenceLoc); } } else if (const auto *expr = dyn_cast(node)) { insertDeclRef(getOrInsertDef(expr->getDecl()), expr->getLoc()); } }); } const PDLIndexSymbol *PDLIndex::lookup(SMLoc loc, SMRange *overlappedRange) const { auto it = intervalMap.find(loc.getPointer()); if (!it.valid() || loc.getPointer() < it.start()) return nullptr; if (overlappedRange) { *overlappedRange = SMRange(SMLoc::getFromPointer(it.start()), SMLoc::getFromPointer(it.stop())); } return it.value(); } //===----------------------------------------------------------------------===// // PDLDocument //===----------------------------------------------------------------------===// namespace { /// This class represents all of the information pertaining to a specific PDL /// document. struct PDLDocument { PDLDocument(const lsp::URIForFile &uri, StringRef contents, const std::vector &extraDirs, std::vector &diagnostics); PDLDocument(const PDLDocument &) = delete; PDLDocument &operator=(const PDLDocument &) = delete; //===--------------------------------------------------------------------===// // Definitions and References //===--------------------------------------------------------------------===// void getLocationsOf(const lsp::URIForFile &uri, const lsp::Position &defPos, std::vector &locations); void findReferencesOf(const lsp::URIForFile &uri, const lsp::Position &pos, std::vector &references); //===--------------------------------------------------------------------===// // Document Links //===--------------------------------------------------------------------===// void getDocumentLinks(const lsp::URIForFile &uri, std::vector &links); //===--------------------------------------------------------------------===// // Hover //===--------------------------------------------------------------------===// std::optional findHover(const lsp::URIForFile &uri, const lsp::Position &hoverPos); std::optional findHover(const ast::Decl *decl, const SMRange &hoverRange); lsp::Hover buildHoverForOpName(const ods::Operation *op, const SMRange &hoverRange); lsp::Hover buildHoverForVariable(const ast::VariableDecl *varDecl, const SMRange &hoverRange); lsp::Hover buildHoverForPattern(const ast::PatternDecl *decl, const SMRange &hoverRange); lsp::Hover buildHoverForCoreConstraint(const ast::CoreConstraintDecl *decl, const SMRange &hoverRange); template lsp::Hover buildHoverForUserConstraintOrRewrite(StringRef typeName, const T *decl, const SMRange &hoverRange); //===--------------------------------------------------------------------===// // Document Symbols //===--------------------------------------------------------------------===// void findDocumentSymbols(std::vector &symbols); //===--------------------------------------------------------------------===// // Code Completion //===--------------------------------------------------------------------===// lsp::CompletionList getCodeCompletion(const lsp::URIForFile &uri, const lsp::Position &completePos); //===--------------------------------------------------------------------===// // Signature Help //===--------------------------------------------------------------------===// lsp::SignatureHelp getSignatureHelp(const lsp::URIForFile &uri, const lsp::Position &helpPos); //===--------------------------------------------------------------------===// // Inlay Hints //===--------------------------------------------------------------------===// void getInlayHints(const lsp::URIForFile &uri, const lsp::Range &range, std::vector &inlayHints); void getInlayHintsFor(const ast::VariableDecl *decl, const lsp::URIForFile &uri, std::vector &inlayHints); void getInlayHintsFor(const ast::CallExpr *expr, const lsp::URIForFile &uri, std::vector &inlayHints); void getInlayHintsFor(const ast::OperationExpr *expr, const lsp::URIForFile &uri, std::vector &inlayHints); /// Add a parameter hint for the given expression using `label`. void addParameterHintFor(std::vector &inlayHints, const ast::Expr *expr, StringRef label); //===--------------------------------------------------------------------===// // PDLL ViewOutput //===--------------------------------------------------------------------===// void getPDLLViewOutput(raw_ostream &os, lsp::PDLLViewOutputKind kind); //===--------------------------------------------------------------------===// // Fields //===--------------------------------------------------------------------===// /// The include directories for this file. std::vector includeDirs; /// The source manager containing the contents of the input file. llvm::SourceMgr sourceMgr; /// The ODS and AST contexts. ods::Context odsContext; ast::Context astContext; /// The parsed AST module, or failure if the file wasn't valid. FailureOr astModule; /// The index of the parsed module. PDLIndex index; /// The set of includes of the parsed module. SmallVector parsedIncludes; }; } // namespace PDLDocument::PDLDocument(const lsp::URIForFile &uri, StringRef contents, const std::vector &extraDirs, std::vector &diagnostics) : astContext(odsContext) { auto memBuffer = llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file()); if (!memBuffer) { lsp::Logger::error("Failed to create memory buffer for file", uri.file()); return; } // Build the set of include directories for this file. llvm::SmallString<32> uriDirectory(uri.file()); llvm::sys::path::remove_filename(uriDirectory); includeDirs.push_back(uriDirectory.str().str()); includeDirs.insert(includeDirs.end(), extraDirs.begin(), extraDirs.end()); sourceMgr.setIncludeDirs(includeDirs); sourceMgr.AddNewSourceBuffer(std::move(memBuffer), SMLoc()); astContext.getDiagEngine().setHandlerFn([&](const ast::Diagnostic &diag) { if (auto lspDiag = getLspDiagnoticFromDiag(sourceMgr, diag, uri)) diagnostics.push_back(std::move(*lspDiag)); }); astModule = parsePDLLAST(astContext, sourceMgr, /*enableDocumentation=*/true); // Initialize the set of parsed includes. lsp::gatherIncludeFiles(sourceMgr, parsedIncludes); // If we failed to parse the module, there is nothing left to initialize. if (failed(astModule)) return; // Prepare the AST index with the parsed module. index.initialize(**astModule, odsContext); } //===----------------------------------------------------------------------===// // PDLDocument: Definitions and References //===----------------------------------------------------------------------===// void PDLDocument::getLocationsOf(const lsp::URIForFile &uri, const lsp::Position &defPos, std::vector &locations) { SMLoc posLoc = defPos.getAsSMLoc(sourceMgr); const PDLIndexSymbol *symbol = index.lookup(posLoc); if (!symbol) return; locations.push_back(getLocationFromLoc(sourceMgr, symbol->getDefLoc(), uri)); } void PDLDocument::findReferencesOf(const lsp::URIForFile &uri, const lsp::Position &pos, std::vector &references) { SMLoc posLoc = pos.getAsSMLoc(sourceMgr); const PDLIndexSymbol *symbol = index.lookup(posLoc); if (!symbol) return; references.push_back(getLocationFromLoc(sourceMgr, symbol->getDefLoc(), uri)); for (SMRange refLoc : symbol->references) references.push_back(getLocationFromLoc(sourceMgr, refLoc, uri)); } //===--------------------------------------------------------------------===// // PDLDocument: Document Links //===--------------------------------------------------------------------===// void PDLDocument::getDocumentLinks(const lsp::URIForFile &uri, std::vector &links) { for (const lsp::SourceMgrInclude &include : parsedIncludes) links.emplace_back(include.range, include.uri); } //===----------------------------------------------------------------------===// // PDLDocument: Hover //===----------------------------------------------------------------------===// std::optional PDLDocument::findHover(const lsp::URIForFile &uri, const lsp::Position &hoverPos) { SMLoc posLoc = hoverPos.getAsSMLoc(sourceMgr); // Check for a reference to an include. for (const lsp::SourceMgrInclude &include : parsedIncludes) if (include.range.contains(hoverPos)) return include.buildHover(); // Find the symbol at the given location. SMRange hoverRange; const PDLIndexSymbol *symbol = index.lookup(posLoc, &hoverRange); if (!symbol) return std::nullopt; // Add hover for operation names. if (const auto *op = llvm::dyn_cast_if_present(symbol->definition)) return buildHoverForOpName(op, hoverRange); const auto *decl = cast(symbol->definition); return findHover(decl, hoverRange); } std::optional PDLDocument::findHover(const ast::Decl *decl, const SMRange &hoverRange) { // Add hover for variables. if (const auto *varDecl = dyn_cast(decl)) return buildHoverForVariable(varDecl, hoverRange); // Add hover for patterns. if (const auto *patternDecl = dyn_cast(decl)) return buildHoverForPattern(patternDecl, hoverRange); // Add hover for core constraints. if (const auto *cst = dyn_cast(decl)) return buildHoverForCoreConstraint(cst, hoverRange); // Add hover for user constraints. if (const auto *cst = dyn_cast(decl)) return buildHoverForUserConstraintOrRewrite("Constraint", cst, hoverRange); // Add hover for user rewrites. if (const auto *rewrite = dyn_cast(decl)) return buildHoverForUserConstraintOrRewrite("Rewrite", rewrite, hoverRange); return std::nullopt; } lsp::Hover PDLDocument::buildHoverForOpName(const ods::Operation *op, const SMRange &hoverRange) { lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); { llvm::raw_string_ostream hoverOS(hover.contents.value); hoverOS << "**OpName**: `" << op->getName() << "`\n***\n" << op->getSummary() << "\n***\n" << op->getDescription(); } return hover; } lsp::Hover PDLDocument::buildHoverForVariable(const ast::VariableDecl *varDecl, const SMRange &hoverRange) { lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); { llvm::raw_string_ostream hoverOS(hover.contents.value); hoverOS << "**Variable**: `" << varDecl->getName().getName() << "`\n***\n" << "Type: `" << varDecl->getType() << "`\n"; } return hover; } lsp::Hover PDLDocument::buildHoverForPattern(const ast::PatternDecl *decl, const SMRange &hoverRange) { lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); { llvm::raw_string_ostream hoverOS(hover.contents.value); hoverOS << "**Pattern**"; if (const ast::Name *name = decl->getName()) hoverOS << ": `" << name->getName() << "`"; hoverOS << "\n***\n"; if (std::optional benefit = decl->getBenefit()) hoverOS << "Benefit: " << *benefit << "\n"; if (decl->hasBoundedRewriteRecursion()) hoverOS << "HasBoundedRewriteRecursion\n"; hoverOS << "RootOp: `" << decl->getRootRewriteStmt()->getRootOpExpr()->getType() << "`\n"; // Format the documentation for the decl. if (std::optional doc = getDocumentationFor(sourceMgr, decl)) hoverOS << "\n" << *doc << "\n"; } return hover; } lsp::Hover PDLDocument::buildHoverForCoreConstraint(const ast::CoreConstraintDecl *decl, const SMRange &hoverRange) { lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); { llvm::raw_string_ostream hoverOS(hover.contents.value); hoverOS << "**Constraint**: `"; TypeSwitch(decl) .Case([&](const ast::AttrConstraintDecl *) { hoverOS << "Attr"; }) .Case([&](const ast::OpConstraintDecl *opCst) { hoverOS << "Op"; if (std::optional name = opCst->getName()) hoverOS << "<" << *name << ">"; }) .Case([&](const ast::TypeConstraintDecl *) { hoverOS << "Type"; }) .Case([&](const ast::TypeRangeConstraintDecl *) { hoverOS << "TypeRange"; }) .Case([&](const ast::ValueConstraintDecl *) { hoverOS << "Value"; }) .Case([&](const ast::ValueRangeConstraintDecl *) { hoverOS << "ValueRange"; }); hoverOS << "`\n"; } return hover; } template lsp::Hover PDLDocument::buildHoverForUserConstraintOrRewrite( StringRef typeName, const T *decl, const SMRange &hoverRange) { lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); { llvm::raw_string_ostream hoverOS(hover.contents.value); hoverOS << "**" << typeName << "**: `" << decl->getName().getName() << "`\n***\n"; ArrayRef inputs = decl->getInputs(); if (!inputs.empty()) { hoverOS << "Parameters:\n"; for (const ast::VariableDecl *input : inputs) hoverOS << "* " << input->getName().getName() << ": `" << input->getType() << "`\n"; hoverOS << "***\n"; } ast::Type resultType = decl->getResultType(); if (auto resultTupleTy = dyn_cast(resultType)) { if (!resultTupleTy.empty()) { hoverOS << "Results:\n"; for (auto it : llvm::zip(resultTupleTy.getElementNames(), resultTupleTy.getElementTypes())) { StringRef name = std::get<0>(it); hoverOS << "* " << (name.empty() ? "" : (name + ": ")) << "`" << std::get<1>(it) << "`\n"; } hoverOS << "***\n"; } } else { hoverOS << "Results:\n* `" << resultType << "`\n"; hoverOS << "***\n"; } // Format the documentation for the decl. if (std::optional doc = getDocumentationFor(sourceMgr, decl)) hoverOS << "\n" << *doc << "\n"; } return hover; } //===----------------------------------------------------------------------===// // PDLDocument: Document Symbols //===----------------------------------------------------------------------===// void PDLDocument::findDocumentSymbols( std::vector &symbols) { if (failed(astModule)) return; for (const ast::Decl *decl : (*astModule)->getChildren()) { if (!isMainFileLoc(sourceMgr, decl->getLoc())) continue; if (const auto *patternDecl = dyn_cast(decl)) { const ast::Name *name = patternDecl->getName(); SMRange nameLoc = name ? name->getLoc() : patternDecl->getLoc(); SMRange bodyLoc(nameLoc.Start, patternDecl->getBody()->getLoc().End); symbols.emplace_back( name ? name->getName() : "", lsp::SymbolKind::Class, lsp::Range(sourceMgr, bodyLoc), lsp::Range(sourceMgr, nameLoc)); } else if (const auto *cDecl = dyn_cast(decl)) { // TODO: Add source information for the code block body. SMRange nameLoc = cDecl->getName().getLoc(); SMRange bodyLoc = nameLoc; symbols.emplace_back( cDecl->getName().getName(), lsp::SymbolKind::Function, lsp::Range(sourceMgr, bodyLoc), lsp::Range(sourceMgr, nameLoc)); } else if (const auto *cDecl = dyn_cast(decl)) { // TODO: Add source information for the code block body. SMRange nameLoc = cDecl->getName().getLoc(); SMRange bodyLoc = nameLoc; symbols.emplace_back( cDecl->getName().getName(), lsp::SymbolKind::Function, lsp::Range(sourceMgr, bodyLoc), lsp::Range(sourceMgr, nameLoc)); } } } //===----------------------------------------------------------------------===// // PDLDocument: Code Completion //===----------------------------------------------------------------------===// namespace { class LSPCodeCompleteContext : public CodeCompleteContext { public: LSPCodeCompleteContext(SMLoc completeLoc, llvm::SourceMgr &sourceMgr, lsp::CompletionList &completionList, ods::Context &odsContext, ArrayRef includeDirs) : CodeCompleteContext(completeLoc), sourceMgr(sourceMgr), completionList(completionList), odsContext(odsContext), includeDirs(includeDirs) {} void codeCompleteTupleMemberAccess(ast::TupleType tupleType) final { ArrayRef elementTypes = tupleType.getElementTypes(); ArrayRef elementNames = tupleType.getElementNames(); for (unsigned i = 0, e = tupleType.size(); i < e; ++i) { // Push back a completion item that uses the result index. lsp::CompletionItem item; item.label = llvm::formatv("{0} (field #{0})", i).str(); item.insertText = Twine(i).str(); item.filterText = item.sortText = item.insertText; item.kind = lsp::CompletionItemKind::Field; item.detail = llvm::formatv("{0}: {1}", i, elementTypes[i]); item.insertTextFormat = lsp::InsertTextFormat::PlainText; completionList.items.emplace_back(item); // If the element has a name, push back a completion item with that name. if (!elementNames[i].empty()) { item.label = llvm::formatv("{1} (field #{0})", i, elementNames[i]).str(); item.filterText = item.label; item.insertText = elementNames[i].str(); completionList.items.emplace_back(item); } } } void codeCompleteOperationMemberAccess(ast::OperationType opType) final { const ods::Operation *odsOp = opType.getODSOperation(); if (!odsOp) return; ArrayRef results = odsOp->getResults(); for (const auto &it : llvm::enumerate(results)) { const ods::OperandOrResult &result = it.value(); const ods::TypeConstraint &constraint = result.getConstraint(); // Push back a completion item that uses the result index. lsp::CompletionItem item; item.label = llvm::formatv("{0} (field #{0})", it.index()).str(); item.insertText = Twine(it.index()).str(); item.filterText = item.sortText = item.insertText; item.kind = lsp::CompletionItemKind::Field; switch (result.getVariableLengthKind()) { case ods::VariableLengthKind::Single: item.detail = llvm::formatv("{0}: Value", it.index()).str(); break; case ods::VariableLengthKind::Optional: item.detail = llvm::formatv("{0}: Value?", it.index()).str(); break; case ods::VariableLengthKind::Variadic: item.detail = llvm::formatv("{0}: ValueRange", it.index()).str(); break; } item.documentation = lsp::MarkupContent{ lsp::MarkupKind::Markdown, llvm::formatv("{0}\n\n```c++\n{1}\n```\n", constraint.getSummary(), constraint.getCppClass()) .str()}; item.insertTextFormat = lsp::InsertTextFormat::PlainText; completionList.items.emplace_back(item); // If the result has a name, push back a completion item with the result // name. if (!result.getName().empty()) { item.label = llvm::formatv("{1} (field #{0})", it.index(), result.getName()) .str(); item.filterText = item.label; item.insertText = result.getName().str(); completionList.items.emplace_back(item); } } } void codeCompleteOperationAttributeName(StringRef opName) final { const ods::Operation *odsOp = odsContext.lookupOperation(opName); if (!odsOp) return; for (const ods::Attribute &attr : odsOp->getAttributes()) { const ods::AttributeConstraint &constraint = attr.getConstraint(); lsp::CompletionItem item; item.label = attr.getName().str(); item.kind = lsp::CompletionItemKind::Field; item.detail = attr.isOptional() ? "optional" : ""; item.documentation = lsp::MarkupContent{ lsp::MarkupKind::Markdown, llvm::formatv("{0}\n\n```c++\n{1}\n```\n", constraint.getSummary(), constraint.getCppClass()) .str()}; item.insertTextFormat = lsp::InsertTextFormat::PlainText; completionList.items.emplace_back(item); } } void codeCompleteConstraintName(ast::Type currentType, bool allowInlineTypeConstraints, const ast::DeclScope *scope) final { auto addCoreConstraint = [&](StringRef constraint, StringRef mlirType, StringRef snippetText = "") { lsp::CompletionItem item; item.label = constraint.str(); item.kind = lsp::CompletionItemKind::Class; item.detail = (constraint + " constraint").str(); item.documentation = lsp::MarkupContent{ lsp::MarkupKind::Markdown, ("A single entity core constraint of type `" + mlirType + "`").str()}; item.sortText = "0"; item.insertText = snippetText.str(); item.insertTextFormat = snippetText.empty() ? lsp::InsertTextFormat::PlainText : lsp::InsertTextFormat::Snippet; completionList.items.emplace_back(item); }; // Insert completions for the core constraints. Some core constraints have // additional characteristics, so we may add then even if a type has been // inferred. if (!currentType) { addCoreConstraint("Attr", "mlir::Attribute"); addCoreConstraint("Op", "mlir::Operation *"); addCoreConstraint("Value", "mlir::Value"); addCoreConstraint("ValueRange", "mlir::ValueRange"); addCoreConstraint("Type", "mlir::Type"); addCoreConstraint("TypeRange", "mlir::TypeRange"); } if (allowInlineTypeConstraints) { /// Attr. if (!currentType || isa(currentType)) addCoreConstraint("Attr", "mlir::Attribute", "Attr<$1>"); /// Value. if (!currentType || isa(currentType)) addCoreConstraint("Value", "mlir::Value", "Value<$1>"); /// ValueRange. if (!currentType || isa(currentType)) addCoreConstraint("ValueRange", "mlir::ValueRange", "ValueRange<$1>"); } // If a scope was provided, check it for potential constraints. while (scope) { for (const ast::Decl *decl : scope->getDecls()) { if (const auto *cst = dyn_cast(decl)) { lsp::CompletionItem item; item.label = cst->getName().getName().str(); item.kind = lsp::CompletionItemKind::Interface; item.sortText = "2_" + item.label; // Skip constraints that are not single-arg. We currently only // complete variable constraints. if (cst->getInputs().size() != 1) continue; // Ensure the input type matched the given type. ast::Type constraintType = cst->getInputs()[0]->getType(); if (currentType && !currentType.refineWith(constraintType)) continue; // Format the constraint signature. { llvm::raw_string_ostream strOS(item.detail); strOS << "("; llvm::interleaveComma( cst->getInputs(), strOS, [&](const ast::VariableDecl *var) { strOS << var->getName().getName() << ": " << var->getType(); }); strOS << ") -> " << cst->getResultType(); } // Format the documentation for the constraint. if (std::optional doc = getDocumentationFor(sourceMgr, cst)) { item.documentation = lsp::MarkupContent{lsp::MarkupKind::Markdown, std::move(*doc)}; } completionList.items.emplace_back(item); } } scope = scope->getParentScope(); } } void codeCompleteDialectName() final { // Code complete known dialects. for (const ods::Dialect &dialect : odsContext.getDialects()) { lsp::CompletionItem item; item.label = dialect.getName().str(); item.kind = lsp::CompletionItemKind::Class; item.insertTextFormat = lsp::InsertTextFormat::PlainText; completionList.items.emplace_back(item); } } void codeCompleteOperationName(StringRef dialectName) final { const ods::Dialect *dialect = odsContext.lookupDialect(dialectName); if (!dialect) return; for (const auto &it : dialect->getOperations()) { const ods::Operation &op = *it.second; lsp::CompletionItem item; item.label = op.getName().drop_front(dialectName.size() + 1).str(); item.kind = lsp::CompletionItemKind::Field; item.insertTextFormat = lsp::InsertTextFormat::PlainText; completionList.items.emplace_back(item); } } void codeCompletePatternMetadata() final { auto addSimpleConstraint = [&](StringRef constraint, StringRef desc, StringRef snippetText = "") { lsp::CompletionItem item; item.label = constraint.str(); item.kind = lsp::CompletionItemKind::Class; item.detail = "pattern metadata"; item.documentation = lsp::MarkupContent{lsp::MarkupKind::Markdown, desc.str()}; item.insertText = snippetText.str(); item.insertTextFormat = snippetText.empty() ? lsp::InsertTextFormat::PlainText : lsp::InsertTextFormat::Snippet; completionList.items.emplace_back(item); }; addSimpleConstraint("benefit", "The `benefit` of matching the pattern.", "benefit($1)"); addSimpleConstraint("recursion", "The pattern properly handles recursive application."); } void codeCompleteIncludeFilename(StringRef curPath) final { // Normalize the path to allow for interacting with the file system // utilities. SmallString<128> nativeRelDir(llvm::sys::path::convert_to_slash(curPath)); llvm::sys::path::native(nativeRelDir); // Set of already included completion paths. StringSet<> seenResults; // Functor used to add a single include completion item. auto addIncludeCompletion = [&](StringRef path, bool isDirectory) { lsp::CompletionItem item; item.label = path.str(); item.kind = isDirectory ? lsp::CompletionItemKind::Folder : lsp::CompletionItemKind::File; if (seenResults.insert(item.label).second) completionList.items.emplace_back(item); }; // Process the include directories for this file, adding any potential // nested include files or directories. for (StringRef includeDir : includeDirs) { llvm::SmallString<128> dir = includeDir; if (!nativeRelDir.empty()) llvm::sys::path::append(dir, nativeRelDir); std::error_code errorCode; for (auto it = llvm::sys::fs::directory_iterator(dir, errorCode), e = llvm::sys::fs::directory_iterator(); !errorCode && it != e; it.increment(errorCode)) { StringRef filename = llvm::sys::path::filename(it->path()); // To know whether a symlink should be treated as file or a directory, // we have to stat it. This should be cheap enough as there shouldn't be // many symlinks. llvm::sys::fs::file_type fileType = it->type(); if (fileType == llvm::sys::fs::file_type::symlink_file) { if (auto fileStatus = it->status()) fileType = fileStatus->type(); } switch (fileType) { case llvm::sys::fs::file_type::directory_file: addIncludeCompletion(filename, /*isDirectory=*/true); break; case llvm::sys::fs::file_type::regular_file: { // Only consider concrete files that can actually be included by PDLL. if (filename.ends_with(".pdll") || filename.ends_with(".td")) addIncludeCompletion(filename, /*isDirectory=*/false); break; } default: break; } } } // Sort the completion results to make sure the output is deterministic in // the face of different iteration schemes for different platforms. llvm::sort(completionList.items, [](const lsp::CompletionItem &lhs, const lsp::CompletionItem &rhs) { return lhs.label < rhs.label; }); } private: llvm::SourceMgr &sourceMgr; lsp::CompletionList &completionList; ods::Context &odsContext; ArrayRef includeDirs; }; } // namespace lsp::CompletionList PDLDocument::getCodeCompletion(const lsp::URIForFile &uri, const lsp::Position &completePos) { SMLoc posLoc = completePos.getAsSMLoc(sourceMgr); if (!posLoc.isValid()) return lsp::CompletionList(); // To perform code completion, we run another parse of the module with the // code completion context provided. ods::Context tmpODSContext; lsp::CompletionList completionList; LSPCodeCompleteContext lspCompleteContext(posLoc, sourceMgr, completionList, tmpODSContext, sourceMgr.getIncludeDirs()); ast::Context tmpContext(tmpODSContext); (void)parsePDLLAST(tmpContext, sourceMgr, /*enableDocumentation=*/true, &lspCompleteContext); return completionList; } //===----------------------------------------------------------------------===// // PDLDocument: Signature Help //===----------------------------------------------------------------------===// namespace { class LSPSignatureHelpContext : public CodeCompleteContext { public: LSPSignatureHelpContext(SMLoc completeLoc, llvm::SourceMgr &sourceMgr, lsp::SignatureHelp &signatureHelp, ods::Context &odsContext) : CodeCompleteContext(completeLoc), sourceMgr(sourceMgr), signatureHelp(signatureHelp), odsContext(odsContext) {} void codeCompleteCallSignature(const ast::CallableDecl *callable, unsigned currentNumArgs) final { signatureHelp.activeParameter = currentNumArgs; lsp::SignatureInformation signatureInfo; { llvm::raw_string_ostream strOS(signatureInfo.label); strOS << callable->getName()->getName() << "("; auto formatParamFn = [&](const ast::VariableDecl *var) { unsigned paramStart = strOS.str().size(); strOS << var->getName().getName() << ": " << var->getType(); unsigned paramEnd = strOS.str().size(); signatureInfo.parameters.emplace_back(lsp::ParameterInformation{ StringRef(strOS.str()).slice(paramStart, paramEnd).str(), std::make_pair(paramStart, paramEnd), /*paramDoc*/ std::string()}); }; llvm::interleaveComma(callable->getInputs(), strOS, formatParamFn); strOS << ") -> " << callable->getResultType(); } // Format the documentation for the callable. if (std::optional doc = getDocumentationFor(sourceMgr, callable)) signatureInfo.documentation = std::move(*doc); signatureHelp.signatures.emplace_back(std::move(signatureInfo)); } void codeCompleteOperationOperandsSignature(std::optional opName, unsigned currentNumOperands) final { const ods::Operation *odsOp = opName ? odsContext.lookupOperation(*opName) : nullptr; codeCompleteOperationOperandOrResultSignature( opName, odsOp, odsOp ? odsOp->getOperands() : std::nullopt, currentNumOperands, "operand", "Value"); } void codeCompleteOperationResultsSignature(std::optional opName, unsigned currentNumResults) final { const ods::Operation *odsOp = opName ? odsContext.lookupOperation(*opName) : nullptr; codeCompleteOperationOperandOrResultSignature( opName, odsOp, odsOp ? odsOp->getResults() : std::nullopt, currentNumResults, "result", "Type"); } void codeCompleteOperationOperandOrResultSignature( std::optional opName, const ods::Operation *odsOp, ArrayRef values, unsigned currentValue, StringRef label, StringRef dataType) { signatureHelp.activeParameter = currentValue; // If we have ODS information for the operation, add in the ODS signature // for the operation. We also verify that the current number of values is // not more than what is defined in ODS, as this will result in an error // anyways. if (odsOp && currentValue < values.size()) { lsp::SignatureInformation signatureInfo; // Build the signature label. { llvm::raw_string_ostream strOS(signatureInfo.label); strOS << "("; auto formatFn = [&](const ods::OperandOrResult &value) { unsigned paramStart = strOS.str().size(); strOS << value.getName() << ": "; StringRef constraintDoc = value.getConstraint().getSummary(); std::string paramDoc; switch (value.getVariableLengthKind()) { case ods::VariableLengthKind::Single: strOS << dataType; paramDoc = constraintDoc.str(); break; case ods::VariableLengthKind::Optional: strOS << dataType << "?"; paramDoc = ("optional: " + constraintDoc).str(); break; case ods::VariableLengthKind::Variadic: strOS << dataType << "Range"; paramDoc = ("variadic: " + constraintDoc).str(); break; } unsigned paramEnd = strOS.str().size(); signatureInfo.parameters.emplace_back(lsp::ParameterInformation{ StringRef(strOS.str()).slice(paramStart, paramEnd).str(), std::make_pair(paramStart, paramEnd), paramDoc}); }; llvm::interleaveComma(values, strOS, formatFn); strOS << ")"; } signatureInfo.documentation = llvm::formatv("`op<{0}>` ODS {1} specification", *opName, label) .str(); signatureHelp.signatures.emplace_back(std::move(signatureInfo)); } // If there aren't any arguments yet, we also add the generic signature. if (currentValue == 0 && (!odsOp || !values.empty())) { lsp::SignatureInformation signatureInfo; signatureInfo.label = llvm::formatv("(<{0}s>: {1}Range)", label, dataType).str(); signatureInfo.documentation = ("Generic operation " + label + " specification").str(); signatureInfo.parameters.emplace_back(lsp::ParameterInformation{ StringRef(signatureInfo.label).drop_front().drop_back().str(), std::pair(1, signatureInfo.label.size() - 1), ("All of the " + label + "s of the operation.").str()}); signatureHelp.signatures.emplace_back(std::move(signatureInfo)); } } private: llvm::SourceMgr &sourceMgr; lsp::SignatureHelp &signatureHelp; ods::Context &odsContext; }; } // namespace lsp::SignatureHelp PDLDocument::getSignatureHelp(const lsp::URIForFile &uri, const lsp::Position &helpPos) { SMLoc posLoc = helpPos.getAsSMLoc(sourceMgr); if (!posLoc.isValid()) return lsp::SignatureHelp(); // To perform code completion, we run another parse of the module with the // code completion context provided. ods::Context tmpODSContext; lsp::SignatureHelp signatureHelp; LSPSignatureHelpContext completeContext(posLoc, sourceMgr, signatureHelp, tmpODSContext); ast::Context tmpContext(tmpODSContext); (void)parsePDLLAST(tmpContext, sourceMgr, /*enableDocumentation=*/true, &completeContext); return signatureHelp; } //===----------------------------------------------------------------------===// // PDLDocument: Inlay Hints //===----------------------------------------------------------------------===// /// Returns true if the given name should be added as a hint for `expr`. static bool shouldAddHintFor(const ast::Expr *expr, StringRef name) { if (name.empty()) return false; // If the argument is a reference of the same name, don't add it as a hint. if (auto *ref = dyn_cast(expr)) { const ast::Name *declName = ref->getDecl()->getName(); if (declName && declName->getName() == name) return false; } return true; } void PDLDocument::getInlayHints(const lsp::URIForFile &uri, const lsp::Range &range, std::vector &inlayHints) { if (failed(astModule)) return; SMRange rangeLoc = range.getAsSMRange(sourceMgr); if (!rangeLoc.isValid()) return; (*astModule)->walk([&](const ast::Node *node) { SMRange loc = node->getLoc(); // Check that the location of this node is within the input range. if (!lsp::contains(rangeLoc, loc.Start) && !lsp::contains(rangeLoc, loc.End)) return; // Handle hints for various types of nodes. llvm::TypeSwitch(node) .Case( [&](const auto *node) { this->getInlayHintsFor(node, uri, inlayHints); }); }); } void PDLDocument::getInlayHintsFor(const ast::VariableDecl *decl, const lsp::URIForFile &uri, std::vector &inlayHints) { // Check to see if the variable has a constraint list, if it does we don't // provide initializer hints. if (!decl->getConstraints().empty()) return; // Check to see if the variable has an initializer. if (const ast::Expr *expr = decl->getInitExpr()) { // Don't add hints for operation expression initialized variables given that // the type of the variable is easily inferred by the expression operation // name. if (isa(expr)) return; } lsp::InlayHint hint(lsp::InlayHintKind::Type, lsp::Position(sourceMgr, decl->getLoc().End)); { llvm::raw_string_ostream labelOS(hint.label); labelOS << ": " << decl->getType(); } inlayHints.emplace_back(std::move(hint)); } void PDLDocument::getInlayHintsFor(const ast::CallExpr *expr, const lsp::URIForFile &uri, std::vector &inlayHints) { // Try to extract the callable of this call. const auto *callableRef = dyn_cast(expr->getCallableExpr()); const auto *callable = callableRef ? dyn_cast(callableRef->getDecl()) : nullptr; if (!callable) return; // Add hints for the arguments to the call. for (const auto &it : llvm::zip(expr->getArguments(), callable->getInputs())) addParameterHintFor(inlayHints, std::get<0>(it), std::get<1>(it)->getName().getName()); } void PDLDocument::getInlayHintsFor(const ast::OperationExpr *expr, const lsp::URIForFile &uri, std::vector &inlayHints) { // Check for ODS information. ast::OperationType opType = dyn_cast(expr->getType()); const auto *odsOp = opType ? opType.getODSOperation() : nullptr; auto addOpHint = [&](const ast::Expr *valueExpr, StringRef label) { // If the value expression used the same location as the operation, don't // add a hint. This expression was materialized during parsing. if (expr->getLoc().Start == valueExpr->getLoc().Start) return; addParameterHintFor(inlayHints, valueExpr, label); }; // Functor used to process hints for the operands and results of the // operation. They effectively have the same format, and thus can be processed // using the same logic. auto addOperandOrResultHints = [&](ArrayRef values, ArrayRef odsValues, StringRef allValuesName) { if (values.empty()) return; // The values should either map to a single range, or be equivalent to the // ODS values. if (values.size() != odsValues.size()) { // Handle the case of a single element that covers the full range. if (values.size() == 1) return addOpHint(values.front(), allValuesName); return; } for (const auto &it : llvm::zip(values, odsValues)) addOpHint(std::get<0>(it), std::get<1>(it).getName()); }; // Add hints for the operands and results of the operation. addOperandOrResultHints(expr->getOperands(), odsOp ? odsOp->getOperands() : ArrayRef(), "operands"); addOperandOrResultHints(expr->getResultTypes(), odsOp ? odsOp->getResults() : ArrayRef(), "results"); } void PDLDocument::addParameterHintFor(std::vector &inlayHints, const ast::Expr *expr, StringRef label) { if (!shouldAddHintFor(expr, label)) return; lsp::InlayHint hint(lsp::InlayHintKind::Parameter, lsp::Position(sourceMgr, expr->getLoc().Start)); hint.label = (label + ":").str(); hint.paddingRight = true; inlayHints.emplace_back(std::move(hint)); } //===----------------------------------------------------------------------===// // PDLL ViewOutput //===----------------------------------------------------------------------===// void PDLDocument::getPDLLViewOutput(raw_ostream &os, lsp::PDLLViewOutputKind kind) { if (failed(astModule)) return; if (kind == lsp::PDLLViewOutputKind::AST) { (*astModule)->print(os); return; } // Generate the MLIR for the ast module. We also capture diagnostics here to // show to the user, which may be useful if PDLL isn't capturing constraints // expected by PDL. MLIRContext mlirContext; SourceMgrDiagnosticHandler diagHandler(sourceMgr, &mlirContext, os); OwningOpRef pdlModule = codegenPDLLToMLIR(&mlirContext, astContext, sourceMgr, **astModule); if (!pdlModule) return; if (kind == lsp::PDLLViewOutputKind::MLIR) { pdlModule->print(os, OpPrintingFlags().enableDebugInfo()); return; } // Otherwise, generate the output for C++. assert(kind == lsp::PDLLViewOutputKind::CPP && "unexpected PDLLViewOutputKind"); codegenPDLLToCPP(**astModule, *pdlModule, os); } //===----------------------------------------------------------------------===// // PDLTextFileChunk //===----------------------------------------------------------------------===// namespace { /// This class represents a single chunk of an PDL text file. struct PDLTextFileChunk { PDLTextFileChunk(uint64_t lineOffset, const lsp::URIForFile &uri, StringRef contents, const std::vector &extraDirs, std::vector &diagnostics) : lineOffset(lineOffset), document(uri, contents, extraDirs, diagnostics) {} /// Adjust the line number of the given range to anchor at the beginning of /// the file, instead of the beginning of this chunk. void adjustLocForChunkOffset(lsp::Range &range) { adjustLocForChunkOffset(range.start); adjustLocForChunkOffset(range.end); } /// Adjust the line number of the given position to anchor at the beginning of /// the file, instead of the beginning of this chunk. void adjustLocForChunkOffset(lsp::Position &pos) { pos.line += lineOffset; } /// The line offset of this chunk from the beginning of the file. uint64_t lineOffset; /// The document referred to by this chunk. PDLDocument document; }; } // namespace //===----------------------------------------------------------------------===// // PDLTextFile //===----------------------------------------------------------------------===// namespace { /// This class represents a text file containing one or more PDL documents. class PDLTextFile { public: PDLTextFile(const lsp::URIForFile &uri, StringRef fileContents, int64_t version, const std::vector &extraDirs, std::vector &diagnostics); /// Return the current version of this text file. int64_t getVersion() const { return version; } /// Update the file to the new version using the provided set of content /// changes. Returns failure if the update was unsuccessful. LogicalResult update(const lsp::URIForFile &uri, int64_t newVersion, ArrayRef changes, std::vector &diagnostics); //===--------------------------------------------------------------------===// // LSP Queries //===--------------------------------------------------------------------===// void getLocationsOf(const lsp::URIForFile &uri, lsp::Position defPos, std::vector &locations); void findReferencesOf(const lsp::URIForFile &uri, lsp::Position pos, std::vector &references); void getDocumentLinks(const lsp::URIForFile &uri, std::vector &links); std::optional findHover(const lsp::URIForFile &uri, lsp::Position hoverPos); void findDocumentSymbols(std::vector &symbols); lsp::CompletionList getCodeCompletion(const lsp::URIForFile &uri, lsp::Position completePos); lsp::SignatureHelp getSignatureHelp(const lsp::URIForFile &uri, lsp::Position helpPos); void getInlayHints(const lsp::URIForFile &uri, lsp::Range range, std::vector &inlayHints); lsp::PDLLViewOutputResult getPDLLViewOutput(lsp::PDLLViewOutputKind kind); private: using ChunkIterator = llvm::pointee_iterator< std::vector>::iterator>; /// Initialize the text file from the given file contents. void initialize(const lsp::URIForFile &uri, int64_t newVersion, std::vector &diagnostics); /// Find the PDL document that contains the given position, and update the /// position to be anchored at the start of the found chunk instead of the /// beginning of the file. ChunkIterator getChunkItFor(lsp::Position &pos); PDLTextFileChunk &getChunkFor(lsp::Position &pos) { return *getChunkItFor(pos); } /// The full string contents of the file. std::string contents; /// The version of this file. int64_t version = 0; /// The number of lines in the file. int64_t totalNumLines = 0; /// The chunks of this file. The order of these chunks is the order in which /// they appear in the text file. std::vector> chunks; /// The extra set of include directories for this file. std::vector extraIncludeDirs; }; } // namespace PDLTextFile::PDLTextFile(const lsp::URIForFile &uri, StringRef fileContents, int64_t version, const std::vector &extraDirs, std::vector &diagnostics) : contents(fileContents.str()), extraIncludeDirs(extraDirs) { initialize(uri, version, diagnostics); } LogicalResult PDLTextFile::update(const lsp::URIForFile &uri, int64_t newVersion, ArrayRef changes, std::vector &diagnostics) { if (failed(lsp::TextDocumentContentChangeEvent::applyTo(changes, contents))) { lsp::Logger::error("Failed to update contents of {0}", uri.file()); return failure(); } // If the file contents were properly changed, reinitialize the text file. initialize(uri, newVersion, diagnostics); return success(); } void PDLTextFile::getLocationsOf(const lsp::URIForFile &uri, lsp::Position defPos, std::vector &locations) { PDLTextFileChunk &chunk = getChunkFor(defPos); chunk.document.getLocationsOf(uri, defPos, locations); // Adjust any locations within this file for the offset of this chunk. if (chunk.lineOffset == 0) return; for (lsp::Location &loc : locations) if (loc.uri == uri) chunk.adjustLocForChunkOffset(loc.range); } void PDLTextFile::findReferencesOf(const lsp::URIForFile &uri, lsp::Position pos, std::vector &references) { PDLTextFileChunk &chunk = getChunkFor(pos); chunk.document.findReferencesOf(uri, pos, references); // Adjust any locations within this file for the offset of this chunk. if (chunk.lineOffset == 0) return; for (lsp::Location &loc : references) if (loc.uri == uri) chunk.adjustLocForChunkOffset(loc.range); } void PDLTextFile::getDocumentLinks(const lsp::URIForFile &uri, std::vector &links) { chunks.front()->document.getDocumentLinks(uri, links); for (const auto &it : llvm::drop_begin(chunks)) { size_t currentNumLinks = links.size(); it->document.getDocumentLinks(uri, links); // Adjust any links within this file to account for the offset of this // chunk. for (auto &link : llvm::drop_begin(links, currentNumLinks)) it->adjustLocForChunkOffset(link.range); } } std::optional PDLTextFile::findHover(const lsp::URIForFile &uri, lsp::Position hoverPos) { PDLTextFileChunk &chunk = getChunkFor(hoverPos); std::optional hoverInfo = chunk.document.findHover(uri, hoverPos); // Adjust any locations within this file for the offset of this chunk. if (chunk.lineOffset != 0 && hoverInfo && hoverInfo->range) chunk.adjustLocForChunkOffset(*hoverInfo->range); return hoverInfo; } void PDLTextFile::findDocumentSymbols( std::vector &symbols) { if (chunks.size() == 1) return chunks.front()->document.findDocumentSymbols(symbols); // If there are multiple chunks in this file, we create top-level symbols for // each chunk. for (unsigned i = 0, e = chunks.size(); i < e; ++i) { PDLTextFileChunk &chunk = *chunks[i]; lsp::Position startPos(chunk.lineOffset); lsp::Position endPos((i == e - 1) ? totalNumLines - 1 : chunks[i + 1]->lineOffset); lsp::DocumentSymbol symbol("", lsp::SymbolKind::Namespace, /*range=*/lsp::Range(startPos, endPos), /*selectionRange=*/lsp::Range(startPos)); chunk.document.findDocumentSymbols(symbol.children); // Fixup the locations of document symbols within this chunk. if (i != 0) { SmallVector symbolsToFix; for (lsp::DocumentSymbol &childSymbol : symbol.children) symbolsToFix.push_back(&childSymbol); while (!symbolsToFix.empty()) { lsp::DocumentSymbol *symbol = symbolsToFix.pop_back_val(); chunk.adjustLocForChunkOffset(symbol->range); chunk.adjustLocForChunkOffset(symbol->selectionRange); for (lsp::DocumentSymbol &childSymbol : symbol->children) symbolsToFix.push_back(&childSymbol); } } // Push the symbol for this chunk. symbols.emplace_back(std::move(symbol)); } } lsp::CompletionList PDLTextFile::getCodeCompletion(const lsp::URIForFile &uri, lsp::Position completePos) { PDLTextFileChunk &chunk = getChunkFor(completePos); lsp::CompletionList completionList = chunk.document.getCodeCompletion(uri, completePos); // Adjust any completion locations. for (lsp::CompletionItem &item : completionList.items) { if (item.textEdit) chunk.adjustLocForChunkOffset(item.textEdit->range); for (lsp::TextEdit &edit : item.additionalTextEdits) chunk.adjustLocForChunkOffset(edit.range); } return completionList; } lsp::SignatureHelp PDLTextFile::getSignatureHelp(const lsp::URIForFile &uri, lsp::Position helpPos) { return getChunkFor(helpPos).document.getSignatureHelp(uri, helpPos); } void PDLTextFile::getInlayHints(const lsp::URIForFile &uri, lsp::Range range, std::vector &inlayHints) { auto startIt = getChunkItFor(range.start); auto endIt = getChunkItFor(range.end); // Functor used to get the chunks for a given file, and fixup any locations auto getHintsForChunk = [&](ChunkIterator chunkIt, lsp::Range range) { size_t currentNumHints = inlayHints.size(); chunkIt->document.getInlayHints(uri, range, inlayHints); // If this isn't the first chunk, update any positions to account for line // number differences. if (&*chunkIt != &*chunks.front()) { for (auto &hint : llvm::drop_begin(inlayHints, currentNumHints)) chunkIt->adjustLocForChunkOffset(hint.position); } }; // Returns the number of lines held by a given chunk. auto getNumLines = [](ChunkIterator chunkIt) { return (chunkIt + 1)->lineOffset - chunkIt->lineOffset; }; // Check if the range is fully within a single chunk. if (startIt == endIt) return getHintsForChunk(startIt, range); // Otherwise, the range is split between multiple chunks. The first chunk // has the correct range start, but covers the total document. getHintsForChunk(startIt, lsp::Range(range.start, getNumLines(startIt))); // Every chunk in between uses the full document. for (++startIt; startIt != endIt; ++startIt) getHintsForChunk(startIt, lsp::Range(0, getNumLines(startIt))); // The range for the last chunk starts at the beginning of the document, up // through the end of the input range. getHintsForChunk(startIt, lsp::Range(0, range.end)); } lsp::PDLLViewOutputResult PDLTextFile::getPDLLViewOutput(lsp::PDLLViewOutputKind kind) { lsp::PDLLViewOutputResult result; { llvm::raw_string_ostream outputOS(result.output); llvm::interleave( llvm::make_pointee_range(chunks), [&](PDLTextFileChunk &chunk) { chunk.document.getPDLLViewOutput(outputOS, kind); }, [&] { outputOS << "\n" << kDefaultSplitMarker << "\n\n"; }); } return result; } void PDLTextFile::initialize(const lsp::URIForFile &uri, int64_t newVersion, std::vector &diagnostics) { version = newVersion; chunks.clear(); // Split the file into separate PDL documents. SmallVector subContents; StringRef(contents).split(subContents, kDefaultSplitMarker); chunks.emplace_back(std::make_unique( /*lineOffset=*/0, uri, subContents.front(), extraIncludeDirs, diagnostics)); uint64_t lineOffset = subContents.front().count('\n'); for (StringRef docContents : llvm::drop_begin(subContents)) { unsigned currentNumDiags = diagnostics.size(); auto chunk = std::make_unique( lineOffset, uri, docContents, extraIncludeDirs, diagnostics); lineOffset += docContents.count('\n'); // Adjust locations used in diagnostics to account for the offset from the // beginning of the file. for (lsp::Diagnostic &diag : llvm::drop_begin(diagnostics, currentNumDiags)) { chunk->adjustLocForChunkOffset(diag.range); if (!diag.relatedInformation) continue; for (auto &it : *diag.relatedInformation) if (it.location.uri == uri) chunk->adjustLocForChunkOffset(it.location.range); } chunks.emplace_back(std::move(chunk)); } totalNumLines = lineOffset; } PDLTextFile::ChunkIterator PDLTextFile::getChunkItFor(lsp::Position &pos) { if (chunks.size() == 1) return chunks.begin(); // Search for the first chunk with a greater line offset, the previous chunk // is the one that contains `pos`. auto it = llvm::upper_bound( chunks, pos, [](const lsp::Position &pos, const auto &chunk) { return static_cast(pos.line) < chunk->lineOffset; }); ChunkIterator chunkIt(it == chunks.end() ? (chunks.end() - 1) : --it); pos.line -= chunkIt->lineOffset; return chunkIt; } //===----------------------------------------------------------------------===// // PDLLServer::Impl //===----------------------------------------------------------------------===// struct lsp::PDLLServer::Impl { explicit Impl(const Options &options) : options(options), compilationDatabase(options.compilationDatabases) {} /// PDLL LSP options. const Options &options; /// The compilation database containing additional information for files /// passed to the server. lsp::CompilationDatabase compilationDatabase; /// The files held by the server, mapped by their URI file name. llvm::StringMap> files; }; //===----------------------------------------------------------------------===// // PDLLServer //===----------------------------------------------------------------------===// lsp::PDLLServer::PDLLServer(const Options &options) : impl(std::make_unique(options)) {} lsp::PDLLServer::~PDLLServer() = default; void lsp::PDLLServer::addDocument(const URIForFile &uri, StringRef contents, int64_t version, std::vector &diagnostics) { // Build the set of additional include directories. std::vector additionalIncludeDirs = impl->options.extraDirs; const auto &fileInfo = impl->compilationDatabase.getFileInfo(uri.file()); llvm::append_range(additionalIncludeDirs, fileInfo.includeDirs); impl->files[uri.file()] = std::make_unique( uri, contents, version, additionalIncludeDirs, diagnostics); } void lsp::PDLLServer::updateDocument( const URIForFile &uri, ArrayRef changes, int64_t version, std::vector &diagnostics) { // Check that we actually have a document for this uri. auto it = impl->files.find(uri.file()); if (it == impl->files.end()) return; // Try to update the document. If we fail, erase the file from the server. A // failed updated generally means we've fallen out of sync somewhere. if (failed(it->second->update(uri, version, changes, diagnostics))) impl->files.erase(it); } std::optional lsp::PDLLServer::removeDocument(const URIForFile &uri) { auto it = impl->files.find(uri.file()); if (it == impl->files.end()) return std::nullopt; int64_t version = it->second->getVersion(); impl->files.erase(it); return version; } void lsp::PDLLServer::getLocationsOf(const URIForFile &uri, const Position &defPos, std::vector &locations) { auto fileIt = impl->files.find(uri.file()); if (fileIt != impl->files.end()) fileIt->second->getLocationsOf(uri, defPos, locations); } void lsp::PDLLServer::findReferencesOf(const URIForFile &uri, const Position &pos, std::vector &references) { auto fileIt = impl->files.find(uri.file()); if (fileIt != impl->files.end()) fileIt->second->findReferencesOf(uri, pos, references); } void lsp::PDLLServer::getDocumentLinks( const URIForFile &uri, std::vector &documentLinks) { auto fileIt = impl->files.find(uri.file()); if (fileIt != impl->files.end()) return fileIt->second->getDocumentLinks(uri, documentLinks); } std::optional lsp::PDLLServer::findHover(const URIForFile &uri, const Position &hoverPos) { auto fileIt = impl->files.find(uri.file()); if (fileIt != impl->files.end()) return fileIt->second->findHover(uri, hoverPos); return std::nullopt; } void lsp::PDLLServer::findDocumentSymbols( const URIForFile &uri, std::vector &symbols) { auto fileIt = impl->files.find(uri.file()); if (fileIt != impl->files.end()) fileIt->second->findDocumentSymbols(symbols); } lsp::CompletionList lsp::PDLLServer::getCodeCompletion(const URIForFile &uri, const Position &completePos) { auto fileIt = impl->files.find(uri.file()); if (fileIt != impl->files.end()) return fileIt->second->getCodeCompletion(uri, completePos); return CompletionList(); } lsp::SignatureHelp lsp::PDLLServer::getSignatureHelp(const URIForFile &uri, const Position &helpPos) { auto fileIt = impl->files.find(uri.file()); if (fileIt != impl->files.end()) return fileIt->second->getSignatureHelp(uri, helpPos); return SignatureHelp(); } void lsp::PDLLServer::getInlayHints(const URIForFile &uri, const Range &range, std::vector &inlayHints) { auto fileIt = impl->files.find(uri.file()); if (fileIt == impl->files.end()) return; fileIt->second->getInlayHints(uri, range, inlayHints); // Drop any duplicated hints that may have cropped up. llvm::sort(inlayHints); inlayHints.erase(llvm::unique(inlayHints), inlayHints.end()); } std::optional lsp::PDLLServer::getPDLLViewOutput(const URIForFile &uri, PDLLViewOutputKind kind) { auto fileIt = impl->files.find(uri.file()); if (fileIt != impl->files.end()) return fileIt->second->getPDLLViewOutput(kind); return std::nullopt; }