1 //===- PDLLServer.cpp - PDLL Language Server ------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "PDLLServer.h" 10 11 #include "../lsp-server-support/CompilationDatabase.h" 12 #include "../lsp-server-support/Logging.h" 13 #include "../lsp-server-support/SourceMgrUtils.h" 14 #include "Protocol.h" 15 #include "mlir/IR/BuiltinOps.h" 16 #include "mlir/Tools/PDLL/AST/Context.h" 17 #include "mlir/Tools/PDLL/AST/Nodes.h" 18 #include "mlir/Tools/PDLL/AST/Types.h" 19 #include "mlir/Tools/PDLL/CodeGen/CPPGen.h" 20 #include "mlir/Tools/PDLL/CodeGen/MLIRGen.h" 21 #include "mlir/Tools/PDLL/ODS/Constraint.h" 22 #include "mlir/Tools/PDLL/ODS/Context.h" 23 #include "mlir/Tools/PDLL/ODS/Dialect.h" 24 #include "mlir/Tools/PDLL/ODS/Operation.h" 25 #include "mlir/Tools/PDLL/Parser/CodeComplete.h" 26 #include "mlir/Tools/PDLL/Parser/Parser.h" 27 #include "llvm/ADT/IntervalMap.h" 28 #include "llvm/ADT/StringMap.h" 29 #include "llvm/ADT/StringSet.h" 30 #include "llvm/ADT/TypeSwitch.h" 31 #include "llvm/Support/FileSystem.h" 32 #include "llvm/Support/Path.h" 33 34 using namespace mlir; 35 using namespace mlir::pdll; 36 37 /// Returns a language server uri for the given source location. `mainFileURI` 38 /// corresponds to the uri for the main file of the source manager. 39 static lsp::URIForFile getURIFromLoc(llvm::SourceMgr &mgr, SMRange loc, 40 const lsp::URIForFile &mainFileURI) { 41 int bufferId = mgr.FindBufferContainingLoc(loc.Start); 42 if (bufferId == 0 || bufferId == static_cast<int>(mgr.getMainFileID())) 43 return mainFileURI; 44 llvm::Expected<lsp::URIForFile> fileForLoc = lsp::URIForFile::fromFile( 45 mgr.getBufferInfo(bufferId).Buffer->getBufferIdentifier()); 46 if (fileForLoc) 47 return *fileForLoc; 48 lsp::Logger::error("Failed to create URI for include file: {0}", 49 llvm::toString(fileForLoc.takeError())); 50 return mainFileURI; 51 } 52 53 /// Returns true if the given location is in the main file of the source 54 /// manager. 55 static bool isMainFileLoc(llvm::SourceMgr &mgr, SMRange loc) { 56 return mgr.FindBufferContainingLoc(loc.Start) == mgr.getMainFileID(); 57 } 58 59 /// Returns a language server location from the given source range. 60 static lsp::Location getLocationFromLoc(llvm::SourceMgr &mgr, SMRange range, 61 const lsp::URIForFile &uri) { 62 return lsp::Location(getURIFromLoc(mgr, range, uri), lsp::Range(mgr, range)); 63 } 64 65 /// Convert the given MLIR diagnostic to the LSP form. 66 static Optional<lsp::Diagnostic> 67 getLspDiagnoticFromDiag(llvm::SourceMgr &sourceMgr, const ast::Diagnostic &diag, 68 const lsp::URIForFile &uri) { 69 lsp::Diagnostic lspDiag; 70 lspDiag.source = "pdll"; 71 72 // FIXME: Right now all of the diagnostics are treated as parser issues, but 73 // some are parser and some are verifier. 74 lspDiag.category = "Parse Error"; 75 76 // Try to grab a file location for this diagnostic. 77 lsp::Location loc = getLocationFromLoc(sourceMgr, diag.getLocation(), uri); 78 lspDiag.range = loc.range; 79 80 // Skip diagnostics that weren't emitted within the main file. 81 if (loc.uri != uri) 82 return llvm::None; 83 84 // Convert the severity for the diagnostic. 85 switch (diag.getSeverity()) { 86 case ast::Diagnostic::Severity::DK_Note: 87 llvm_unreachable("expected notes to be handled separately"); 88 case ast::Diagnostic::Severity::DK_Warning: 89 lspDiag.severity = lsp::DiagnosticSeverity::Warning; 90 break; 91 case ast::Diagnostic::Severity::DK_Error: 92 lspDiag.severity = lsp::DiagnosticSeverity::Error; 93 break; 94 case ast::Diagnostic::Severity::DK_Remark: 95 lspDiag.severity = lsp::DiagnosticSeverity::Information; 96 break; 97 } 98 lspDiag.message = diag.getMessage().str(); 99 100 // Attach any notes to the main diagnostic as related information. 101 std::vector<lsp::DiagnosticRelatedInformation> relatedDiags; 102 for (const ast::Diagnostic ¬e : diag.getNotes()) { 103 relatedDiags.emplace_back( 104 getLocationFromLoc(sourceMgr, note.getLocation(), uri), 105 note.getMessage().str()); 106 } 107 if (!relatedDiags.empty()) 108 lspDiag.relatedInformation = std::move(relatedDiags); 109 110 return lspDiag; 111 } 112 113 /// Get or extract the documentation for the given decl. 114 static Optional<std::string> getDocumentationFor(llvm::SourceMgr &sourceMgr, 115 const ast::Decl *decl) { 116 // If the decl already had documentation set, use it. 117 if (Optional<StringRef> doc = decl->getDocComment()) 118 return doc->str(); 119 120 // If the decl doesn't yet have documentation, try to extract it from the 121 // source file. This is a heuristic, and isn't intended to cover every case, 122 // but should cover the most common. We essentially look for a comment 123 // preceding the decl, and if we find one, use that as the documentation. 124 SMLoc startLoc = decl->getLoc().Start; 125 if (!startLoc.isValid()) 126 return llvm::None; 127 int bufferId = sourceMgr.FindBufferContainingLoc(startLoc); 128 if (bufferId == 0) 129 return llvm::None; 130 const char *bufferStart = 131 sourceMgr.getMemoryBuffer(bufferId)->getBufferStart(); 132 StringRef buffer(bufferStart, startLoc.getPointer() - bufferStart); 133 134 // Pop the last line from the buffer string. 135 auto popLastLine = [&]() -> Optional<StringRef> { 136 size_t newlineOffset = buffer.find_last_of("\n"); 137 if (newlineOffset == StringRef::npos) 138 return llvm::None; 139 StringRef lastLine = buffer.drop_front(newlineOffset).trim(); 140 buffer = buffer.take_front(newlineOffset); 141 return lastLine; 142 }; 143 144 // Try to pop the current line, which contains the decl. 145 if (!popLastLine()) 146 return llvm::None; 147 148 // Try to parse a comment string from the source file. 149 SmallVector<StringRef> commentLines; 150 while (Optional<StringRef> line = popLastLine()) { 151 // Check for a comment at the beginning of the line. 152 if (!line->startswith("//")) 153 break; 154 155 // Extract the document string from the comment. 156 commentLines.push_back(line->drop_while([](char c) { return c == '/'; })); 157 } 158 159 if (commentLines.empty()) 160 return llvm::None; 161 return llvm::join(llvm::reverse(commentLines), "\n"); 162 } 163 164 //===----------------------------------------------------------------------===// 165 // PDLIndex 166 //===----------------------------------------------------------------------===// 167 168 namespace { 169 struct PDLIndexSymbol { 170 explicit PDLIndexSymbol(const ast::Decl *definition) 171 : definition(definition) {} 172 explicit PDLIndexSymbol(const ods::Operation *definition) 173 : definition(definition) {} 174 175 /// Return the location of the definition of this symbol. 176 SMRange getDefLoc() const { 177 if (const ast::Decl *decl = definition.dyn_cast<const ast::Decl *>()) { 178 const ast::Name *declName = decl->getName(); 179 return declName ? declName->getLoc() : decl->getLoc(); 180 } 181 return definition.get<const ods::Operation *>()->getLoc(); 182 } 183 184 /// The main definition of the symbol. 185 PointerUnion<const ast::Decl *, const ods::Operation *> definition; 186 /// The set of references to the symbol. 187 std::vector<SMRange> references; 188 }; 189 190 /// This class provides an index for definitions/uses within a PDL document. 191 /// It provides efficient lookup of a definition given an input source range. 192 class PDLIndex { 193 public: 194 PDLIndex() : intervalMap(allocator) {} 195 196 /// Initialize the index with the given ast::Module. 197 void initialize(const ast::Module &module, const ods::Context &odsContext); 198 199 /// Lookup a symbol for the given location. Returns nullptr if no symbol could 200 /// be found. If provided, `overlappedRange` is set to the range that the 201 /// provided `loc` overlapped with. 202 const PDLIndexSymbol *lookup(SMLoc loc, 203 SMRange *overlappedRange = nullptr) const; 204 205 private: 206 /// The type of interval map used to store source references. SMRange is 207 /// half-open, so we also need to use a half-open interval map. 208 using MapT = 209 llvm::IntervalMap<const char *, const PDLIndexSymbol *, 210 llvm::IntervalMapImpl::NodeSizer< 211 const char *, const PDLIndexSymbol *>::LeafSize, 212 llvm::IntervalMapHalfOpenInfo<const char *>>; 213 214 /// An allocator for the interval map. 215 MapT::Allocator allocator; 216 217 /// An interval map containing a corresponding definition mapped to a source 218 /// interval. 219 MapT intervalMap; 220 221 /// A mapping between definitions and their corresponding symbol. 222 DenseMap<const void *, std::unique_ptr<PDLIndexSymbol>> defToSymbol; 223 }; 224 } // namespace 225 226 void PDLIndex::initialize(const ast::Module &module, 227 const ods::Context &odsContext) { 228 auto getOrInsertDef = [&](const auto *def) -> PDLIndexSymbol * { 229 auto it = defToSymbol.try_emplace(def, nullptr); 230 if (it.second) 231 it.first->second = std::make_unique<PDLIndexSymbol>(def); 232 return &*it.first->second; 233 }; 234 auto insertDeclRef = [&](PDLIndexSymbol *sym, SMRange refLoc, 235 bool isDef = false) { 236 const char *startLoc = refLoc.Start.getPointer(); 237 const char *endLoc = refLoc.End.getPointer(); 238 if (!intervalMap.overlaps(startLoc, endLoc)) { 239 intervalMap.insert(startLoc, endLoc, sym); 240 if (!isDef) 241 sym->references.push_back(refLoc); 242 } 243 }; 244 auto insertODSOpRef = [&](StringRef opName, SMRange refLoc) { 245 const ods::Operation *odsOp = odsContext.lookupOperation(opName); 246 if (!odsOp) 247 return; 248 249 PDLIndexSymbol *symbol = getOrInsertDef(odsOp); 250 insertDeclRef(symbol, odsOp->getLoc(), /*isDef=*/true); 251 insertDeclRef(symbol, refLoc); 252 }; 253 254 module.walk([&](const ast::Node *node) { 255 // Handle references to PDL decls. 256 if (const auto *decl = dyn_cast<ast::OpNameDecl>(node)) { 257 if (Optional<StringRef> name = decl->getName()) 258 insertODSOpRef(*name, decl->getLoc()); 259 } else if (const ast::Decl *decl = dyn_cast<ast::Decl>(node)) { 260 const ast::Name *name = decl->getName(); 261 if (!name) 262 return; 263 PDLIndexSymbol *declSym = getOrInsertDef(decl); 264 insertDeclRef(declSym, name->getLoc(), /*isDef=*/true); 265 266 if (const auto *varDecl = dyn_cast<ast::VariableDecl>(decl)) { 267 // Record references to any constraints. 268 for (const auto &it : varDecl->getConstraints()) 269 insertDeclRef(getOrInsertDef(it.constraint), it.referenceLoc); 270 } 271 } else if (const auto *expr = dyn_cast<ast::DeclRefExpr>(node)) { 272 insertDeclRef(getOrInsertDef(expr->getDecl()), expr->getLoc()); 273 } 274 }); 275 } 276 277 const PDLIndexSymbol *PDLIndex::lookup(SMLoc loc, 278 SMRange *overlappedRange) const { 279 auto it = intervalMap.find(loc.getPointer()); 280 if (!it.valid() || loc.getPointer() < it.start()) 281 return nullptr; 282 283 if (overlappedRange) { 284 *overlappedRange = SMRange(SMLoc::getFromPointer(it.start()), 285 SMLoc::getFromPointer(it.stop())); 286 } 287 return it.value(); 288 } 289 290 //===----------------------------------------------------------------------===// 291 // PDLDocument 292 //===----------------------------------------------------------------------===// 293 294 namespace { 295 /// This class represents all of the information pertaining to a specific PDL 296 /// document. 297 struct PDLDocument { 298 PDLDocument(const lsp::URIForFile &uri, StringRef contents, 299 const std::vector<std::string> &extraDirs, 300 std::vector<lsp::Diagnostic> &diagnostics); 301 PDLDocument(const PDLDocument &) = delete; 302 PDLDocument &operator=(const PDLDocument &) = delete; 303 304 //===--------------------------------------------------------------------===// 305 // Definitions and References 306 //===--------------------------------------------------------------------===// 307 308 void getLocationsOf(const lsp::URIForFile &uri, const lsp::Position &defPos, 309 std::vector<lsp::Location> &locations); 310 void findReferencesOf(const lsp::URIForFile &uri, const lsp::Position &pos, 311 std::vector<lsp::Location> &references); 312 313 //===--------------------------------------------------------------------===// 314 // Document Links 315 //===--------------------------------------------------------------------===// 316 317 void getDocumentLinks(const lsp::URIForFile &uri, 318 std::vector<lsp::DocumentLink> &links); 319 320 //===--------------------------------------------------------------------===// 321 // Hover 322 //===--------------------------------------------------------------------===// 323 324 Optional<lsp::Hover> findHover(const lsp::URIForFile &uri, 325 const lsp::Position &hoverPos); 326 Optional<lsp::Hover> findHover(const ast::Decl *decl, 327 const SMRange &hoverRange); 328 lsp::Hover buildHoverForOpName(const ods::Operation *op, 329 const SMRange &hoverRange); 330 lsp::Hover buildHoverForVariable(const ast::VariableDecl *varDecl, 331 const SMRange &hoverRange); 332 lsp::Hover buildHoverForPattern(const ast::PatternDecl *decl, 333 const SMRange &hoverRange); 334 lsp::Hover buildHoverForCoreConstraint(const ast::CoreConstraintDecl *decl, 335 const SMRange &hoverRange); 336 template <typename T> 337 lsp::Hover buildHoverForUserConstraintOrRewrite(StringRef typeName, 338 const T *decl, 339 const SMRange &hoverRange); 340 341 //===--------------------------------------------------------------------===// 342 // Document Symbols 343 //===--------------------------------------------------------------------===// 344 345 void findDocumentSymbols(std::vector<lsp::DocumentSymbol> &symbols); 346 347 //===--------------------------------------------------------------------===// 348 // Code Completion 349 //===--------------------------------------------------------------------===// 350 351 lsp::CompletionList getCodeCompletion(const lsp::URIForFile &uri, 352 const lsp::Position &completePos); 353 354 //===--------------------------------------------------------------------===// 355 // Signature Help 356 //===--------------------------------------------------------------------===// 357 358 lsp::SignatureHelp getSignatureHelp(const lsp::URIForFile &uri, 359 const lsp::Position &helpPos); 360 361 //===--------------------------------------------------------------------===// 362 // PDLL ViewOutput 363 //===--------------------------------------------------------------------===// 364 365 void getPDLLViewOutput(raw_ostream &os, lsp::PDLLViewOutputKind kind); 366 367 //===--------------------------------------------------------------------===// 368 // Fields 369 //===--------------------------------------------------------------------===// 370 371 /// The include directories for this file. 372 std::vector<std::string> includeDirs; 373 374 /// The source manager containing the contents of the input file. 375 llvm::SourceMgr sourceMgr; 376 377 /// The ODS and AST contexts. 378 ods::Context odsContext; 379 ast::Context astContext; 380 381 /// The parsed AST module, or failure if the file wasn't valid. 382 FailureOr<ast::Module *> astModule; 383 384 /// The index of the parsed module. 385 PDLIndex index; 386 387 /// The set of includes of the parsed module. 388 SmallVector<lsp::SourceMgrInclude> parsedIncludes; 389 }; 390 } // namespace 391 392 PDLDocument::PDLDocument(const lsp::URIForFile &uri, StringRef contents, 393 const std::vector<std::string> &extraDirs, 394 std::vector<lsp::Diagnostic> &diagnostics) 395 : astContext(odsContext) { 396 auto memBuffer = llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file()); 397 if (!memBuffer) { 398 lsp::Logger::error("Failed to create memory buffer for file", uri.file()); 399 return; 400 } 401 402 // Build the set of include directories for this file. 403 llvm::SmallString<32> uriDirectory(uri.file()); 404 llvm::sys::path::remove_filename(uriDirectory); 405 includeDirs.push_back(uriDirectory.str().str()); 406 includeDirs.insert(includeDirs.end(), extraDirs.begin(), extraDirs.end()); 407 408 sourceMgr.setIncludeDirs(includeDirs); 409 sourceMgr.AddNewSourceBuffer(std::move(memBuffer), SMLoc()); 410 411 astContext.getDiagEngine().setHandlerFn([&](const ast::Diagnostic &diag) { 412 if (auto lspDiag = getLspDiagnoticFromDiag(sourceMgr, diag, uri)) 413 diagnostics.push_back(std::move(*lspDiag)); 414 }); 415 astModule = parsePDLLAST(astContext, sourceMgr, /*enableDocumentation=*/true); 416 417 // Initialize the set of parsed includes. 418 lsp::gatherIncludeFiles(sourceMgr, parsedIncludes); 419 420 // If we failed to parse the module, there is nothing left to initialize. 421 if (failed(astModule)) 422 return; 423 424 // Prepare the AST index with the parsed module. 425 index.initialize(**astModule, odsContext); 426 } 427 428 //===----------------------------------------------------------------------===// 429 // PDLDocument: Definitions and References 430 //===----------------------------------------------------------------------===// 431 432 void PDLDocument::getLocationsOf(const lsp::URIForFile &uri, 433 const lsp::Position &defPos, 434 std::vector<lsp::Location> &locations) { 435 SMLoc posLoc = defPos.getAsSMLoc(sourceMgr); 436 const PDLIndexSymbol *symbol = index.lookup(posLoc); 437 if (!symbol) 438 return; 439 440 locations.push_back(getLocationFromLoc(sourceMgr, symbol->getDefLoc(), uri)); 441 } 442 443 void PDLDocument::findReferencesOf(const lsp::URIForFile &uri, 444 const lsp::Position &pos, 445 std::vector<lsp::Location> &references) { 446 SMLoc posLoc = pos.getAsSMLoc(sourceMgr); 447 const PDLIndexSymbol *symbol = index.lookup(posLoc); 448 if (!symbol) 449 return; 450 451 references.push_back(getLocationFromLoc(sourceMgr, symbol->getDefLoc(), uri)); 452 for (SMRange refLoc : symbol->references) 453 references.push_back(getLocationFromLoc(sourceMgr, refLoc, uri)); 454 } 455 456 //===--------------------------------------------------------------------===// 457 // PDLDocument: Document Links 458 //===--------------------------------------------------------------------===// 459 460 void PDLDocument::getDocumentLinks(const lsp::URIForFile &uri, 461 std::vector<lsp::DocumentLink> &links) { 462 for (const lsp::SourceMgrInclude &include : parsedIncludes) 463 links.emplace_back(include.range, include.uri); 464 } 465 466 //===----------------------------------------------------------------------===// 467 // PDLDocument: Hover 468 //===----------------------------------------------------------------------===// 469 470 Optional<lsp::Hover> PDLDocument::findHover(const lsp::URIForFile &uri, 471 const lsp::Position &hoverPos) { 472 SMLoc posLoc = hoverPos.getAsSMLoc(sourceMgr); 473 474 // Check for a reference to an include. 475 for (const lsp::SourceMgrInclude &include : parsedIncludes) 476 if (include.range.contains(hoverPos)) 477 return include.buildHover(); 478 479 // Find the symbol at the given location. 480 SMRange hoverRange; 481 const PDLIndexSymbol *symbol = index.lookup(posLoc, &hoverRange); 482 if (!symbol) 483 return llvm::None; 484 485 // Add hover for operation names. 486 if (const auto *op = symbol->definition.dyn_cast<const ods::Operation *>()) 487 return buildHoverForOpName(op, hoverRange); 488 const auto *decl = symbol->definition.get<const ast::Decl *>(); 489 return findHover(decl, hoverRange); 490 } 491 492 Optional<lsp::Hover> PDLDocument::findHover(const ast::Decl *decl, 493 const SMRange &hoverRange) { 494 // Add hover for variables. 495 if (const auto *varDecl = dyn_cast<ast::VariableDecl>(decl)) 496 return buildHoverForVariable(varDecl, hoverRange); 497 498 // Add hover for patterns. 499 if (const auto *patternDecl = dyn_cast<ast::PatternDecl>(decl)) 500 return buildHoverForPattern(patternDecl, hoverRange); 501 502 // Add hover for core constraints. 503 if (const auto *cst = dyn_cast<ast::CoreConstraintDecl>(decl)) 504 return buildHoverForCoreConstraint(cst, hoverRange); 505 506 // Add hover for user constraints. 507 if (const auto *cst = dyn_cast<ast::UserConstraintDecl>(decl)) 508 return buildHoverForUserConstraintOrRewrite("Constraint", cst, hoverRange); 509 510 // Add hover for user rewrites. 511 if (const auto *rewrite = dyn_cast<ast::UserRewriteDecl>(decl)) 512 return buildHoverForUserConstraintOrRewrite("Rewrite", rewrite, hoverRange); 513 514 return llvm::None; 515 } 516 517 lsp::Hover PDLDocument::buildHoverForOpName(const ods::Operation *op, 518 const SMRange &hoverRange) { 519 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); 520 { 521 llvm::raw_string_ostream hoverOS(hover.contents.value); 522 hoverOS << "**OpName**: `" << op->getName() << "`\n***\n" 523 << op->getSummary() << "\n***\n" 524 << op->getDescription(); 525 } 526 return hover; 527 } 528 529 lsp::Hover PDLDocument::buildHoverForVariable(const ast::VariableDecl *varDecl, 530 const SMRange &hoverRange) { 531 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); 532 { 533 llvm::raw_string_ostream hoverOS(hover.contents.value); 534 hoverOS << "**Variable**: `" << varDecl->getName().getName() << "`\n***\n" 535 << "Type: `" << varDecl->getType() << "`\n"; 536 } 537 return hover; 538 } 539 540 lsp::Hover PDLDocument::buildHoverForPattern(const ast::PatternDecl *decl, 541 const SMRange &hoverRange) { 542 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); 543 { 544 llvm::raw_string_ostream hoverOS(hover.contents.value); 545 hoverOS << "**Pattern**"; 546 if (const ast::Name *name = decl->getName()) 547 hoverOS << ": `" << name->getName() << "`"; 548 hoverOS << "\n***\n"; 549 if (Optional<uint16_t> benefit = decl->getBenefit()) 550 hoverOS << "Benefit: " << *benefit << "\n"; 551 if (decl->hasBoundedRewriteRecursion()) 552 hoverOS << "HasBoundedRewriteRecursion\n"; 553 hoverOS << "RootOp: `" 554 << decl->getRootRewriteStmt()->getRootOpExpr()->getType() << "`\n"; 555 556 // Format the documentation for the decl. 557 if (Optional<std::string> doc = getDocumentationFor(sourceMgr, decl)) 558 hoverOS << "\n" << *doc << "\n"; 559 } 560 return hover; 561 } 562 563 lsp::Hover 564 PDLDocument::buildHoverForCoreConstraint(const ast::CoreConstraintDecl *decl, 565 const SMRange &hoverRange) { 566 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); 567 { 568 llvm::raw_string_ostream hoverOS(hover.contents.value); 569 hoverOS << "**Constraint**: `"; 570 TypeSwitch<const ast::Decl *>(decl) 571 .Case([&](const ast::AttrConstraintDecl *) { hoverOS << "Attr"; }) 572 .Case([&](const ast::OpConstraintDecl *opCst) { 573 hoverOS << "Op"; 574 if (Optional<StringRef> name = opCst->getName()) 575 hoverOS << "<" << name << ">"; 576 }) 577 .Case([&](const ast::TypeConstraintDecl *) { hoverOS << "Type"; }) 578 .Case([&](const ast::TypeRangeConstraintDecl *) { 579 hoverOS << "TypeRange"; 580 }) 581 .Case([&](const ast::ValueConstraintDecl *) { hoverOS << "Value"; }) 582 .Case([&](const ast::ValueRangeConstraintDecl *) { 583 hoverOS << "ValueRange"; 584 }); 585 hoverOS << "`\n"; 586 } 587 return hover; 588 } 589 590 template <typename T> 591 lsp::Hover PDLDocument::buildHoverForUserConstraintOrRewrite( 592 StringRef typeName, const T *decl, const SMRange &hoverRange) { 593 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); 594 { 595 llvm::raw_string_ostream hoverOS(hover.contents.value); 596 hoverOS << "**" << typeName << "**: `" << decl->getName().getName() 597 << "`\n***\n"; 598 ArrayRef<ast::VariableDecl *> inputs = decl->getInputs(); 599 if (!inputs.empty()) { 600 hoverOS << "Parameters:\n"; 601 for (const ast::VariableDecl *input : inputs) 602 hoverOS << "* " << input->getName().getName() << ": `" 603 << input->getType() << "`\n"; 604 hoverOS << "***\n"; 605 } 606 ast::Type resultType = decl->getResultType(); 607 if (auto resultTupleTy = resultType.dyn_cast<ast::TupleType>()) { 608 if (!resultTupleTy.empty()) { 609 hoverOS << "Results:\n"; 610 for (auto it : llvm::zip(resultTupleTy.getElementNames(), 611 resultTupleTy.getElementTypes())) { 612 StringRef name = std::get<0>(it); 613 hoverOS << "* " << (name.empty() ? "" : (name + ": ")) << "`" 614 << std::get<1>(it) << "`\n"; 615 } 616 hoverOS << "***\n"; 617 } 618 } else { 619 hoverOS << "Results:\n* `" << resultType << "`\n"; 620 hoverOS << "***\n"; 621 } 622 623 // Format the documentation for the decl. 624 if (Optional<std::string> doc = getDocumentationFor(sourceMgr, decl)) 625 hoverOS << "\n" << *doc << "\n"; 626 } 627 return hover; 628 } 629 630 //===----------------------------------------------------------------------===// 631 // PDLDocument: Document Symbols 632 //===----------------------------------------------------------------------===// 633 634 void PDLDocument::findDocumentSymbols( 635 std::vector<lsp::DocumentSymbol> &symbols) { 636 if (failed(astModule)) 637 return; 638 639 for (const ast::Decl *decl : (*astModule)->getChildren()) { 640 if (!isMainFileLoc(sourceMgr, decl->getLoc())) 641 continue; 642 643 if (const auto *patternDecl = dyn_cast<ast::PatternDecl>(decl)) { 644 const ast::Name *name = patternDecl->getName(); 645 646 SMRange nameLoc = name ? name->getLoc() : patternDecl->getLoc(); 647 SMRange bodyLoc(nameLoc.Start, patternDecl->getBody()->getLoc().End); 648 649 symbols.emplace_back( 650 name ? name->getName() : "<pattern>", lsp::SymbolKind::Class, 651 lsp::Range(sourceMgr, bodyLoc), lsp::Range(sourceMgr, nameLoc)); 652 } else if (const auto *cDecl = dyn_cast<ast::UserConstraintDecl>(decl)) { 653 // TODO: Add source information for the code block body. 654 SMRange nameLoc = cDecl->getName().getLoc(); 655 SMRange bodyLoc = nameLoc; 656 657 symbols.emplace_back( 658 cDecl->getName().getName(), lsp::SymbolKind::Function, 659 lsp::Range(sourceMgr, bodyLoc), lsp::Range(sourceMgr, nameLoc)); 660 } else if (const auto *cDecl = dyn_cast<ast::UserRewriteDecl>(decl)) { 661 // TODO: Add source information for the code block body. 662 SMRange nameLoc = cDecl->getName().getLoc(); 663 SMRange bodyLoc = nameLoc; 664 665 symbols.emplace_back( 666 cDecl->getName().getName(), lsp::SymbolKind::Function, 667 lsp::Range(sourceMgr, bodyLoc), lsp::Range(sourceMgr, nameLoc)); 668 } 669 } 670 } 671 672 //===----------------------------------------------------------------------===// 673 // PDLDocument: Code Completion 674 //===----------------------------------------------------------------------===// 675 676 namespace { 677 class LSPCodeCompleteContext : public CodeCompleteContext { 678 public: 679 LSPCodeCompleteContext(SMLoc completeLoc, llvm::SourceMgr &sourceMgr, 680 lsp::CompletionList &completionList, 681 ods::Context &odsContext, 682 ArrayRef<std::string> includeDirs) 683 : CodeCompleteContext(completeLoc), sourceMgr(sourceMgr), 684 completionList(completionList), odsContext(odsContext), 685 includeDirs(includeDirs) {} 686 687 void codeCompleteTupleMemberAccess(ast::TupleType tupleType) final { 688 ArrayRef<ast::Type> elementTypes = tupleType.getElementTypes(); 689 ArrayRef<StringRef> elementNames = tupleType.getElementNames(); 690 for (unsigned i = 0, e = tupleType.size(); i < e; ++i) { 691 // Push back a completion item that uses the result index. 692 lsp::CompletionItem item; 693 item.label = llvm::formatv("{0} (field #{0})", i).str(); 694 item.insertText = Twine(i).str(); 695 item.filterText = item.sortText = item.insertText; 696 item.kind = lsp::CompletionItemKind::Field; 697 item.detail = llvm::formatv("{0}: {1}", i, elementTypes[i]); 698 item.insertTextFormat = lsp::InsertTextFormat::PlainText; 699 completionList.items.emplace_back(item); 700 701 // If the element has a name, push back a completion item with that name. 702 if (!elementNames[i].empty()) { 703 item.label = 704 llvm::formatv("{1} (field #{0})", i, elementNames[i]).str(); 705 item.filterText = item.label; 706 item.insertText = elementNames[i].str(); 707 completionList.items.emplace_back(item); 708 } 709 } 710 } 711 712 void codeCompleteOperationMemberAccess(ast::OperationType opType) final { 713 const ods::Operation *odsOp = opType.getODSOperation(); 714 if (!odsOp) 715 return; 716 717 ArrayRef<ods::OperandOrResult> results = odsOp->getResults(); 718 for (const auto &it : llvm::enumerate(results)) { 719 const ods::OperandOrResult &result = it.value(); 720 const ods::TypeConstraint &constraint = result.getConstraint(); 721 722 // Push back a completion item that uses the result index. 723 lsp::CompletionItem item; 724 item.label = llvm::formatv("{0} (field #{0})", it.index()).str(); 725 item.insertText = Twine(it.index()).str(); 726 item.filterText = item.sortText = item.insertText; 727 item.kind = lsp::CompletionItemKind::Field; 728 switch (result.getVariableLengthKind()) { 729 case ods::VariableLengthKind::Single: 730 item.detail = llvm::formatv("{0}: Value", it.index()).str(); 731 break; 732 case ods::VariableLengthKind::Optional: 733 item.detail = llvm::formatv("{0}: Value?", it.index()).str(); 734 break; 735 case ods::VariableLengthKind::Variadic: 736 item.detail = llvm::formatv("{0}: ValueRange", it.index()).str(); 737 break; 738 } 739 item.documentation = lsp::MarkupContent{ 740 lsp::MarkupKind::Markdown, 741 llvm::formatv("{0}\n\n```c++\n{1}\n```\n", constraint.getSummary(), 742 constraint.getCppClass()) 743 .str()}; 744 item.insertTextFormat = lsp::InsertTextFormat::PlainText; 745 completionList.items.emplace_back(item); 746 747 // If the result has a name, push back a completion item with the result 748 // name. 749 if (!result.getName().empty()) { 750 item.label = 751 llvm::formatv("{1} (field #{0})", it.index(), result.getName()) 752 .str(); 753 item.filterText = item.label; 754 item.insertText = result.getName().str(); 755 completionList.items.emplace_back(item); 756 } 757 } 758 } 759 760 void codeCompleteOperationAttributeName(StringRef opName) final { 761 const ods::Operation *odsOp = odsContext.lookupOperation(opName); 762 if (!odsOp) 763 return; 764 765 for (const ods::Attribute &attr : odsOp->getAttributes()) { 766 const ods::AttributeConstraint &constraint = attr.getConstraint(); 767 768 lsp::CompletionItem item; 769 item.label = attr.getName().str(); 770 item.kind = lsp::CompletionItemKind::Field; 771 item.detail = attr.isOptional() ? "optional" : ""; 772 item.documentation = lsp::MarkupContent{ 773 lsp::MarkupKind::Markdown, 774 llvm::formatv("{0}\n\n```c++\n{1}\n```\n", constraint.getSummary(), 775 constraint.getCppClass()) 776 .str()}; 777 item.insertTextFormat = lsp::InsertTextFormat::PlainText; 778 completionList.items.emplace_back(item); 779 } 780 } 781 782 void codeCompleteConstraintName(ast::Type currentType, 783 bool allowNonCoreConstraints, 784 bool allowInlineTypeConstraints, 785 const ast::DeclScope *scope) final { 786 auto addCoreConstraint = [&](StringRef constraint, StringRef mlirType, 787 StringRef snippetText = "") { 788 lsp::CompletionItem item; 789 item.label = constraint.str(); 790 item.kind = lsp::CompletionItemKind::Class; 791 item.detail = (constraint + " constraint").str(); 792 item.documentation = lsp::MarkupContent{ 793 lsp::MarkupKind::Markdown, 794 ("A single entity core constraint of type `" + mlirType + "`").str()}; 795 item.sortText = "0"; 796 item.insertText = snippetText.str(); 797 item.insertTextFormat = snippetText.empty() 798 ? lsp::InsertTextFormat::PlainText 799 : lsp::InsertTextFormat::Snippet; 800 completionList.items.emplace_back(item); 801 }; 802 803 // Insert completions for the core constraints. Some core constraints have 804 // additional characteristics, so we may add then even if a type has been 805 // inferred. 806 if (!currentType) { 807 addCoreConstraint("Attr", "mlir::Attribute"); 808 addCoreConstraint("Op", "mlir::Operation *"); 809 addCoreConstraint("Value", "mlir::Value"); 810 addCoreConstraint("ValueRange", "mlir::ValueRange"); 811 addCoreConstraint("Type", "mlir::Type"); 812 addCoreConstraint("TypeRange", "mlir::TypeRange"); 813 } 814 if (allowInlineTypeConstraints) { 815 /// Attr<Type>. 816 if (!currentType || currentType.isa<ast::AttributeType>()) 817 addCoreConstraint("Attr<type>", "mlir::Attribute", "Attr<$1>"); 818 /// Value<Type>. 819 if (!currentType || currentType.isa<ast::ValueType>()) 820 addCoreConstraint("Value<type>", "mlir::Value", "Value<$1>"); 821 /// ValueRange<TypeRange>. 822 if (!currentType || currentType.isa<ast::ValueRangeType>()) 823 addCoreConstraint("ValueRange<type>", "mlir::ValueRange", 824 "ValueRange<$1>"); 825 } 826 827 // If a scope was provided, check it for potential constraints. 828 while (scope) { 829 for (const ast::Decl *decl : scope->getDecls()) { 830 if (const auto *cst = dyn_cast<ast::UserConstraintDecl>(decl)) { 831 if (!allowNonCoreConstraints) 832 continue; 833 834 lsp::CompletionItem item; 835 item.label = cst->getName().getName().str(); 836 item.kind = lsp::CompletionItemKind::Interface; 837 item.sortText = "2_" + item.label; 838 839 // Skip constraints that are not single-arg. We currently only 840 // complete variable constraints. 841 if (cst->getInputs().size() != 1) 842 continue; 843 844 // Ensure the input type matched the given type. 845 ast::Type constraintType = cst->getInputs()[0]->getType(); 846 if (currentType && !currentType.refineWith(constraintType)) 847 continue; 848 849 // Format the constraint signature. 850 { 851 llvm::raw_string_ostream strOS(item.detail); 852 strOS << "("; 853 llvm::interleaveComma( 854 cst->getInputs(), strOS, [&](const ast::VariableDecl *var) { 855 strOS << var->getName().getName() << ": " << var->getType(); 856 }); 857 strOS << ") -> " << cst->getResultType(); 858 } 859 860 // Format the documentation for the constraint. 861 if (Optional<std::string> doc = getDocumentationFor(sourceMgr, cst)) { 862 item.documentation = 863 lsp::MarkupContent{lsp::MarkupKind::Markdown, std::move(*doc)}; 864 } 865 866 completionList.items.emplace_back(item); 867 } 868 } 869 870 scope = scope->getParentScope(); 871 } 872 } 873 874 void codeCompleteDialectName() final { 875 // Code complete known dialects. 876 for (const ods::Dialect &dialect : odsContext.getDialects()) { 877 lsp::CompletionItem item; 878 item.label = dialect.getName().str(); 879 item.kind = lsp::CompletionItemKind::Class; 880 item.insertTextFormat = lsp::InsertTextFormat::PlainText; 881 completionList.items.emplace_back(item); 882 } 883 } 884 885 void codeCompleteOperationName(StringRef dialectName) final { 886 const ods::Dialect *dialect = odsContext.lookupDialect(dialectName); 887 if (!dialect) 888 return; 889 890 for (const auto &it : dialect->getOperations()) { 891 const ods::Operation &op = *it.second; 892 893 lsp::CompletionItem item; 894 item.label = op.getName().drop_front(dialectName.size() + 1).str(); 895 item.kind = lsp::CompletionItemKind::Field; 896 item.insertTextFormat = lsp::InsertTextFormat::PlainText; 897 completionList.items.emplace_back(item); 898 } 899 } 900 901 void codeCompletePatternMetadata() final { 902 auto addSimpleConstraint = [&](StringRef constraint, StringRef desc, 903 StringRef snippetText = "") { 904 lsp::CompletionItem item; 905 item.label = constraint.str(); 906 item.kind = lsp::CompletionItemKind::Class; 907 item.detail = "pattern metadata"; 908 item.documentation = 909 lsp::MarkupContent{lsp::MarkupKind::Markdown, desc.str()}; 910 item.insertText = snippetText.str(); 911 item.insertTextFormat = snippetText.empty() 912 ? lsp::InsertTextFormat::PlainText 913 : lsp::InsertTextFormat::Snippet; 914 completionList.items.emplace_back(item); 915 }; 916 917 addSimpleConstraint("benefit", "The `benefit` of matching the pattern.", 918 "benefit($1)"); 919 addSimpleConstraint("recursion", 920 "The pattern properly handles recursive application."); 921 } 922 923 void codeCompleteIncludeFilename(StringRef curPath) final { 924 // Normalize the path to allow for interacting with the file system 925 // utilities. 926 SmallString<128> nativeRelDir(llvm::sys::path::convert_to_slash(curPath)); 927 llvm::sys::path::native(nativeRelDir); 928 929 // Set of already included completion paths. 930 StringSet<> seenResults; 931 932 // Functor used to add a single include completion item. 933 auto addIncludeCompletion = [&](StringRef path, bool isDirectory) { 934 lsp::CompletionItem item; 935 item.label = path.str(); 936 item.kind = isDirectory ? lsp::CompletionItemKind::Folder 937 : lsp::CompletionItemKind::File; 938 if (seenResults.insert(item.label).second) 939 completionList.items.emplace_back(item); 940 }; 941 942 // Process the include directories for this file, adding any potential 943 // nested include files or directories. 944 for (StringRef includeDir : includeDirs) { 945 llvm::SmallString<128> dir = includeDir; 946 if (!nativeRelDir.empty()) 947 llvm::sys::path::append(dir, nativeRelDir); 948 949 std::error_code errorCode; 950 for (auto it = llvm::sys::fs::directory_iterator(dir, errorCode), 951 e = llvm::sys::fs::directory_iterator(); 952 !errorCode && it != e; it.increment(errorCode)) { 953 StringRef filename = llvm::sys::path::filename(it->path()); 954 955 // To know whether a symlink should be treated as file or a directory, 956 // we have to stat it. This should be cheap enough as there shouldn't be 957 // many symlinks. 958 llvm::sys::fs::file_type fileType = it->type(); 959 if (fileType == llvm::sys::fs::file_type::symlink_file) { 960 if (auto fileStatus = it->status()) 961 fileType = fileStatus->type(); 962 } 963 964 switch (fileType) { 965 case llvm::sys::fs::file_type::directory_file: 966 addIncludeCompletion(filename, /*isDirectory=*/true); 967 break; 968 case llvm::sys::fs::file_type::regular_file: { 969 // Only consider concrete files that can actually be included by PDLL. 970 if (filename.endswith(".pdll") || filename.endswith(".td")) 971 addIncludeCompletion(filename, /*isDirectory=*/false); 972 break; 973 } 974 default: 975 break; 976 } 977 } 978 } 979 980 // Sort the completion results to make sure the output is deterministic in 981 // the face of different iteration schemes for different platforms. 982 llvm::sort(completionList.items, [](const lsp::CompletionItem &lhs, 983 const lsp::CompletionItem &rhs) { 984 return lhs.label < rhs.label; 985 }); 986 } 987 988 private: 989 llvm::SourceMgr &sourceMgr; 990 lsp::CompletionList &completionList; 991 ods::Context &odsContext; 992 ArrayRef<std::string> includeDirs; 993 }; 994 } // namespace 995 996 lsp::CompletionList 997 PDLDocument::getCodeCompletion(const lsp::URIForFile &uri, 998 const lsp::Position &completePos) { 999 SMLoc posLoc = completePos.getAsSMLoc(sourceMgr); 1000 if (!posLoc.isValid()) 1001 return lsp::CompletionList(); 1002 1003 // To perform code completion, we run another parse of the module with the 1004 // code completion context provided. 1005 ods::Context tmpODSContext; 1006 lsp::CompletionList completionList; 1007 LSPCodeCompleteContext lspCompleteContext(posLoc, sourceMgr, completionList, 1008 tmpODSContext, 1009 sourceMgr.getIncludeDirs()); 1010 1011 ast::Context tmpContext(tmpODSContext); 1012 (void)parsePDLLAST(tmpContext, sourceMgr, /*enableDocumentation=*/true, 1013 &lspCompleteContext); 1014 1015 return completionList; 1016 } 1017 1018 //===----------------------------------------------------------------------===// 1019 // PDLDocument: Signature Help 1020 //===----------------------------------------------------------------------===// 1021 1022 namespace { 1023 class LSPSignatureHelpContext : public CodeCompleteContext { 1024 public: 1025 LSPSignatureHelpContext(SMLoc completeLoc, llvm::SourceMgr &sourceMgr, 1026 lsp::SignatureHelp &signatureHelp, 1027 ods::Context &odsContext) 1028 : CodeCompleteContext(completeLoc), sourceMgr(sourceMgr), 1029 signatureHelp(signatureHelp), odsContext(odsContext) {} 1030 1031 void codeCompleteCallSignature(const ast::CallableDecl *callable, 1032 unsigned currentNumArgs) final { 1033 signatureHelp.activeParameter = currentNumArgs; 1034 1035 lsp::SignatureInformation signatureInfo; 1036 { 1037 llvm::raw_string_ostream strOS(signatureInfo.label); 1038 strOS << callable->getName()->getName() << "("; 1039 auto formatParamFn = [&](const ast::VariableDecl *var) { 1040 unsigned paramStart = strOS.str().size(); 1041 strOS << var->getName().getName() << ": " << var->getType(); 1042 unsigned paramEnd = strOS.str().size(); 1043 signatureInfo.parameters.emplace_back(lsp::ParameterInformation{ 1044 StringRef(strOS.str()).slice(paramStart, paramEnd).str(), 1045 std::make_pair(paramStart, paramEnd), /*paramDoc*/ std::string()}); 1046 }; 1047 llvm::interleaveComma(callable->getInputs(), strOS, formatParamFn); 1048 strOS << ") -> " << callable->getResultType(); 1049 } 1050 1051 // Format the documentation for the callable. 1052 if (Optional<std::string> doc = getDocumentationFor(sourceMgr, callable)) 1053 signatureInfo.documentation = std::move(*doc); 1054 1055 signatureHelp.signatures.emplace_back(std::move(signatureInfo)); 1056 } 1057 1058 void 1059 codeCompleteOperationOperandsSignature(Optional<StringRef> opName, 1060 unsigned currentNumOperands) final { 1061 const ods::Operation *odsOp = 1062 opName ? odsContext.lookupOperation(*opName) : nullptr; 1063 codeCompleteOperationOperandOrResultSignature( 1064 opName, odsOp, odsOp ? odsOp->getOperands() : llvm::None, 1065 currentNumOperands, "operand", "Value"); 1066 } 1067 1068 void codeCompleteOperationResultsSignature(Optional<StringRef> opName, 1069 unsigned currentNumResults) final { 1070 const ods::Operation *odsOp = 1071 opName ? odsContext.lookupOperation(*opName) : nullptr; 1072 codeCompleteOperationOperandOrResultSignature( 1073 opName, odsOp, odsOp ? odsOp->getResults() : llvm::None, 1074 currentNumResults, "result", "Type"); 1075 } 1076 1077 void codeCompleteOperationOperandOrResultSignature( 1078 Optional<StringRef> opName, const ods::Operation *odsOp, 1079 ArrayRef<ods::OperandOrResult> values, unsigned currentValue, 1080 StringRef label, StringRef dataType) { 1081 signatureHelp.activeParameter = currentValue; 1082 1083 // If we have ODS information for the operation, add in the ODS signature 1084 // for the operation. We also verify that the current number of values is 1085 // not more than what is defined in ODS, as this will result in an error 1086 // anyways. 1087 if (odsOp && currentValue < values.size()) { 1088 lsp::SignatureInformation signatureInfo; 1089 1090 // Build the signature label. 1091 { 1092 llvm::raw_string_ostream strOS(signatureInfo.label); 1093 strOS << "("; 1094 auto formatFn = [&](const ods::OperandOrResult &value) { 1095 unsigned paramStart = strOS.str().size(); 1096 1097 strOS << value.getName() << ": "; 1098 1099 StringRef constraintDoc = value.getConstraint().getSummary(); 1100 std::string paramDoc; 1101 switch (value.getVariableLengthKind()) { 1102 case ods::VariableLengthKind::Single: 1103 strOS << dataType; 1104 paramDoc = constraintDoc.str(); 1105 break; 1106 case ods::VariableLengthKind::Optional: 1107 strOS << dataType << "?"; 1108 paramDoc = ("optional: " + constraintDoc).str(); 1109 break; 1110 case ods::VariableLengthKind::Variadic: 1111 strOS << dataType << "Range"; 1112 paramDoc = ("variadic: " + constraintDoc).str(); 1113 break; 1114 } 1115 1116 unsigned paramEnd = strOS.str().size(); 1117 signatureInfo.parameters.emplace_back(lsp::ParameterInformation{ 1118 StringRef(strOS.str()).slice(paramStart, paramEnd).str(), 1119 std::make_pair(paramStart, paramEnd), paramDoc}); 1120 }; 1121 llvm::interleaveComma(values, strOS, formatFn); 1122 strOS << ")"; 1123 } 1124 signatureInfo.documentation = 1125 llvm::formatv("`op<{0}>` ODS {1} specification", *opName, label) 1126 .str(); 1127 signatureHelp.signatures.emplace_back(std::move(signatureInfo)); 1128 } 1129 1130 // If there aren't any arguments yet, we also add the generic signature. 1131 if (currentValue == 0 && (!odsOp || !values.empty())) { 1132 lsp::SignatureInformation signatureInfo; 1133 signatureInfo.label = 1134 llvm::formatv("(<{0}s>: {1}Range)", label, dataType).str(); 1135 signatureInfo.documentation = 1136 ("Generic operation " + label + " specification").str(); 1137 signatureInfo.parameters.emplace_back(lsp::ParameterInformation{ 1138 StringRef(signatureInfo.label).drop_front().drop_back().str(), 1139 std::pair<unsigned, unsigned>(1, signatureInfo.label.size() - 1), 1140 ("All of the " + label + "s of the operation.").str()}); 1141 signatureHelp.signatures.emplace_back(std::move(signatureInfo)); 1142 } 1143 } 1144 1145 private: 1146 llvm::SourceMgr &sourceMgr; 1147 lsp::SignatureHelp &signatureHelp; 1148 ods::Context &odsContext; 1149 }; 1150 } // namespace 1151 1152 lsp::SignatureHelp PDLDocument::getSignatureHelp(const lsp::URIForFile &uri, 1153 const lsp::Position &helpPos) { 1154 SMLoc posLoc = helpPos.getAsSMLoc(sourceMgr); 1155 if (!posLoc.isValid()) 1156 return lsp::SignatureHelp(); 1157 1158 // To perform code completion, we run another parse of the module with the 1159 // code completion context provided. 1160 ods::Context tmpODSContext; 1161 lsp::SignatureHelp signatureHelp; 1162 LSPSignatureHelpContext completeContext(posLoc, sourceMgr, signatureHelp, 1163 tmpODSContext); 1164 1165 ast::Context tmpContext(tmpODSContext); 1166 (void)parsePDLLAST(tmpContext, sourceMgr, /*enableDocumentation=*/true, 1167 &completeContext); 1168 1169 return signatureHelp; 1170 } 1171 1172 //===----------------------------------------------------------------------===// 1173 // PDLL ViewOutput 1174 //===----------------------------------------------------------------------===// 1175 1176 void PDLDocument::getPDLLViewOutput(raw_ostream &os, 1177 lsp::PDLLViewOutputKind kind) { 1178 if (failed(astModule)) 1179 return; 1180 if (kind == lsp::PDLLViewOutputKind::AST) { 1181 (*astModule)->print(os); 1182 return; 1183 } 1184 1185 // Generate the MLIR for the ast module. We also capture diagnostics here to 1186 // show to the user, which may be useful if PDLL isn't capturing constraints 1187 // expected by PDL. 1188 MLIRContext mlirContext; 1189 SourceMgrDiagnosticHandler diagHandler(sourceMgr, &mlirContext, os); 1190 OwningOpRef<ModuleOp> pdlModule = 1191 codegenPDLLToMLIR(&mlirContext, astContext, sourceMgr, **astModule); 1192 if (!pdlModule) 1193 return; 1194 if (kind == lsp::PDLLViewOutputKind::MLIR) { 1195 pdlModule->print(os, OpPrintingFlags().enableDebugInfo()); 1196 return; 1197 } 1198 1199 // Otherwise, generate the output for C++. 1200 assert(kind == lsp::PDLLViewOutputKind::CPP && 1201 "unexpected PDLLViewOutputKind"); 1202 codegenPDLLToCPP(**astModule, *pdlModule, os); 1203 } 1204 1205 //===----------------------------------------------------------------------===// 1206 // PDLTextFileChunk 1207 //===----------------------------------------------------------------------===// 1208 1209 namespace { 1210 /// This class represents a single chunk of an PDL text file. 1211 struct PDLTextFileChunk { 1212 PDLTextFileChunk(uint64_t lineOffset, const lsp::URIForFile &uri, 1213 StringRef contents, 1214 const std::vector<std::string> &extraDirs, 1215 std::vector<lsp::Diagnostic> &diagnostics) 1216 : lineOffset(lineOffset), 1217 document(uri, contents, extraDirs, diagnostics) {} 1218 1219 /// Adjust the line number of the given range to anchor at the beginning of 1220 /// the file, instead of the beginning of this chunk. 1221 void adjustLocForChunkOffset(lsp::Range &range) { 1222 adjustLocForChunkOffset(range.start); 1223 adjustLocForChunkOffset(range.end); 1224 } 1225 /// Adjust the line number of the given position to anchor at the beginning of 1226 /// the file, instead of the beginning of this chunk. 1227 void adjustLocForChunkOffset(lsp::Position &pos) { pos.line += lineOffset; } 1228 1229 /// The line offset of this chunk from the beginning of the file. 1230 uint64_t lineOffset; 1231 /// The document referred to by this chunk. 1232 PDLDocument document; 1233 }; 1234 } // namespace 1235 1236 //===----------------------------------------------------------------------===// 1237 // PDLTextFile 1238 //===----------------------------------------------------------------------===// 1239 1240 namespace { 1241 /// This class represents a text file containing one or more PDL documents. 1242 class PDLTextFile { 1243 public: 1244 PDLTextFile(const lsp::URIForFile &uri, StringRef fileContents, 1245 int64_t version, const std::vector<std::string> &extraDirs, 1246 std::vector<lsp::Diagnostic> &diagnostics); 1247 1248 /// Return the current version of this text file. 1249 int64_t getVersion() const { return version; } 1250 1251 //===--------------------------------------------------------------------===// 1252 // LSP Queries 1253 //===--------------------------------------------------------------------===// 1254 1255 void getLocationsOf(const lsp::URIForFile &uri, lsp::Position defPos, 1256 std::vector<lsp::Location> &locations); 1257 void findReferencesOf(const lsp::URIForFile &uri, lsp::Position pos, 1258 std::vector<lsp::Location> &references); 1259 void getDocumentLinks(const lsp::URIForFile &uri, 1260 std::vector<lsp::DocumentLink> &links); 1261 Optional<lsp::Hover> findHover(const lsp::URIForFile &uri, 1262 lsp::Position hoverPos); 1263 void findDocumentSymbols(std::vector<lsp::DocumentSymbol> &symbols); 1264 lsp::CompletionList getCodeCompletion(const lsp::URIForFile &uri, 1265 lsp::Position completePos); 1266 lsp::SignatureHelp getSignatureHelp(const lsp::URIForFile &uri, 1267 lsp::Position helpPos); 1268 lsp::PDLLViewOutputResult getPDLLViewOutput(lsp::PDLLViewOutputKind kind); 1269 1270 private: 1271 /// Find the PDL document that contains the given position, and update the 1272 /// position to be anchored at the start of the found chunk instead of the 1273 /// beginning of the file. 1274 PDLTextFileChunk &getChunkFor(lsp::Position &pos); 1275 1276 /// The full string contents of the file. 1277 std::string contents; 1278 1279 /// The version of this file. 1280 int64_t version; 1281 1282 /// The number of lines in the file. 1283 int64_t totalNumLines = 0; 1284 1285 /// The chunks of this file. The order of these chunks is the order in which 1286 /// they appear in the text file. 1287 std::vector<std::unique_ptr<PDLTextFileChunk>> chunks; 1288 }; 1289 } // namespace 1290 1291 PDLTextFile::PDLTextFile(const lsp::URIForFile &uri, StringRef fileContents, 1292 int64_t version, 1293 const std::vector<std::string> &extraDirs, 1294 std::vector<lsp::Diagnostic> &diagnostics) 1295 : contents(fileContents.str()), version(version) { 1296 // Split the file into separate PDL documents. 1297 // TODO: Find a way to share the split file marker with other tools. We don't 1298 // want to use `splitAndProcessBuffer` here, but we do want to make sure this 1299 // marker doesn't go out of sync. 1300 SmallVector<StringRef, 8> subContents; 1301 StringRef(contents).split(subContents, "// -----"); 1302 chunks.emplace_back(std::make_unique<PDLTextFileChunk>( 1303 /*lineOffset=*/0, uri, subContents.front(), extraDirs, diagnostics)); 1304 1305 uint64_t lineOffset = subContents.front().count('\n'); 1306 for (StringRef docContents : llvm::drop_begin(subContents)) { 1307 unsigned currentNumDiags = diagnostics.size(); 1308 auto chunk = std::make_unique<PDLTextFileChunk>( 1309 lineOffset, uri, docContents, extraDirs, diagnostics); 1310 lineOffset += docContents.count('\n'); 1311 1312 // Adjust locations used in diagnostics to account for the offset from the 1313 // beginning of the file. 1314 for (lsp::Diagnostic &diag : 1315 llvm::drop_begin(diagnostics, currentNumDiags)) { 1316 chunk->adjustLocForChunkOffset(diag.range); 1317 1318 if (!diag.relatedInformation) 1319 continue; 1320 for (auto &it : *diag.relatedInformation) 1321 if (it.location.uri == uri) 1322 chunk->adjustLocForChunkOffset(it.location.range); 1323 } 1324 chunks.emplace_back(std::move(chunk)); 1325 } 1326 totalNumLines = lineOffset; 1327 } 1328 1329 void PDLTextFile::getLocationsOf(const lsp::URIForFile &uri, 1330 lsp::Position defPos, 1331 std::vector<lsp::Location> &locations) { 1332 PDLTextFileChunk &chunk = getChunkFor(defPos); 1333 chunk.document.getLocationsOf(uri, defPos, locations); 1334 1335 // Adjust any locations within this file for the offset of this chunk. 1336 if (chunk.lineOffset == 0) 1337 return; 1338 for (lsp::Location &loc : locations) 1339 if (loc.uri == uri) 1340 chunk.adjustLocForChunkOffset(loc.range); 1341 } 1342 1343 void PDLTextFile::findReferencesOf(const lsp::URIForFile &uri, 1344 lsp::Position pos, 1345 std::vector<lsp::Location> &references) { 1346 PDLTextFileChunk &chunk = getChunkFor(pos); 1347 chunk.document.findReferencesOf(uri, pos, references); 1348 1349 // Adjust any locations within this file for the offset of this chunk. 1350 if (chunk.lineOffset == 0) 1351 return; 1352 for (lsp::Location &loc : references) 1353 if (loc.uri == uri) 1354 chunk.adjustLocForChunkOffset(loc.range); 1355 } 1356 1357 void PDLTextFile::getDocumentLinks(const lsp::URIForFile &uri, 1358 std::vector<lsp::DocumentLink> &links) { 1359 chunks.front()->document.getDocumentLinks(uri, links); 1360 for (const auto &it : llvm::drop_begin(chunks)) { 1361 size_t currentNumLinks = links.size(); 1362 it->document.getDocumentLinks(uri, links); 1363 1364 // Adjust any links within this file to account for the offset of this 1365 // chunk. 1366 for (auto &link : llvm::drop_begin(links, currentNumLinks)) 1367 it->adjustLocForChunkOffset(link.range); 1368 } 1369 } 1370 1371 Optional<lsp::Hover> PDLTextFile::findHover(const lsp::URIForFile &uri, 1372 lsp::Position hoverPos) { 1373 PDLTextFileChunk &chunk = getChunkFor(hoverPos); 1374 Optional<lsp::Hover> hoverInfo = chunk.document.findHover(uri, hoverPos); 1375 1376 // Adjust any locations within this file for the offset of this chunk. 1377 if (chunk.lineOffset != 0 && hoverInfo && hoverInfo->range) 1378 chunk.adjustLocForChunkOffset(*hoverInfo->range); 1379 return hoverInfo; 1380 } 1381 1382 void PDLTextFile::findDocumentSymbols( 1383 std::vector<lsp::DocumentSymbol> &symbols) { 1384 if (chunks.size() == 1) 1385 return chunks.front()->document.findDocumentSymbols(symbols); 1386 1387 // If there are multiple chunks in this file, we create top-level symbols for 1388 // each chunk. 1389 for (unsigned i = 0, e = chunks.size(); i < e; ++i) { 1390 PDLTextFileChunk &chunk = *chunks[i]; 1391 lsp::Position startPos(chunk.lineOffset); 1392 lsp::Position endPos((i == e - 1) ? totalNumLines - 1 1393 : chunks[i + 1]->lineOffset); 1394 lsp::DocumentSymbol symbol("<file-split-" + Twine(i) + ">", 1395 lsp::SymbolKind::Namespace, 1396 /*range=*/lsp::Range(startPos, endPos), 1397 /*selectionRange=*/lsp::Range(startPos)); 1398 chunk.document.findDocumentSymbols(symbol.children); 1399 1400 // Fixup the locations of document symbols within this chunk. 1401 if (i != 0) { 1402 SmallVector<lsp::DocumentSymbol *> symbolsToFix; 1403 for (lsp::DocumentSymbol &childSymbol : symbol.children) 1404 symbolsToFix.push_back(&childSymbol); 1405 1406 while (!symbolsToFix.empty()) { 1407 lsp::DocumentSymbol *symbol = symbolsToFix.pop_back_val(); 1408 chunk.adjustLocForChunkOffset(symbol->range); 1409 chunk.adjustLocForChunkOffset(symbol->selectionRange); 1410 1411 for (lsp::DocumentSymbol &childSymbol : symbol->children) 1412 symbolsToFix.push_back(&childSymbol); 1413 } 1414 } 1415 1416 // Push the symbol for this chunk. 1417 symbols.emplace_back(std::move(symbol)); 1418 } 1419 } 1420 1421 lsp::CompletionList PDLTextFile::getCodeCompletion(const lsp::URIForFile &uri, 1422 lsp::Position completePos) { 1423 PDLTextFileChunk &chunk = getChunkFor(completePos); 1424 lsp::CompletionList completionList = 1425 chunk.document.getCodeCompletion(uri, completePos); 1426 1427 // Adjust any completion locations. 1428 for (lsp::CompletionItem &item : completionList.items) { 1429 if (item.textEdit) 1430 chunk.adjustLocForChunkOffset(item.textEdit->range); 1431 for (lsp::TextEdit &edit : item.additionalTextEdits) 1432 chunk.adjustLocForChunkOffset(edit.range); 1433 } 1434 return completionList; 1435 } 1436 1437 lsp::SignatureHelp PDLTextFile::getSignatureHelp(const lsp::URIForFile &uri, 1438 lsp::Position helpPos) { 1439 return getChunkFor(helpPos).document.getSignatureHelp(uri, helpPos); 1440 } 1441 1442 lsp::PDLLViewOutputResult 1443 PDLTextFile::getPDLLViewOutput(lsp::PDLLViewOutputKind kind) { 1444 lsp::PDLLViewOutputResult result; 1445 { 1446 llvm::raw_string_ostream outputOS(result.output); 1447 llvm::interleave( 1448 llvm::make_pointee_range(chunks), 1449 [&](PDLTextFileChunk &chunk) { 1450 chunk.document.getPDLLViewOutput(outputOS, kind); 1451 }, 1452 [&] { outputOS << "\n// -----\n\n"; }); 1453 } 1454 return result; 1455 } 1456 1457 PDLTextFileChunk &PDLTextFile::getChunkFor(lsp::Position &pos) { 1458 if (chunks.size() == 1) 1459 return *chunks.front(); 1460 1461 // Search for the first chunk with a greater line offset, the previous chunk 1462 // is the one that contains `pos`. 1463 auto it = llvm::upper_bound( 1464 chunks, pos, [](const lsp::Position &pos, const auto &chunk) { 1465 return static_cast<uint64_t>(pos.line) < chunk->lineOffset; 1466 }); 1467 PDLTextFileChunk &chunk = it == chunks.end() ? *chunks.back() : **(--it); 1468 pos.line -= chunk.lineOffset; 1469 return chunk; 1470 } 1471 1472 //===----------------------------------------------------------------------===// 1473 // PDLLServer::Impl 1474 //===----------------------------------------------------------------------===// 1475 1476 struct lsp::PDLLServer::Impl { 1477 explicit Impl(const Options &options) 1478 : options(options), compilationDatabase(options.compilationDatabases) {} 1479 1480 /// PDLL LSP options. 1481 const Options &options; 1482 1483 /// The compilation database containing additional information for files 1484 /// passed to the server. 1485 lsp::CompilationDatabase compilationDatabase; 1486 1487 /// The files held by the server, mapped by their URI file name. 1488 llvm::StringMap<std::unique_ptr<PDLTextFile>> files; 1489 }; 1490 1491 //===----------------------------------------------------------------------===// 1492 // PDLLServer 1493 //===----------------------------------------------------------------------===// 1494 1495 lsp::PDLLServer::PDLLServer(const Options &options) 1496 : impl(std::make_unique<Impl>(options)) {} 1497 lsp::PDLLServer::~PDLLServer() = default; 1498 1499 void lsp::PDLLServer::addOrUpdateDocument( 1500 const URIForFile &uri, StringRef contents, int64_t version, 1501 std::vector<Diagnostic> &diagnostics) { 1502 // Build the set of additional include directories. 1503 std::vector<std::string> additionalIncludeDirs = impl->options.extraDirs; 1504 const auto &fileInfo = impl->compilationDatabase.getFileInfo(uri.file()); 1505 llvm::append_range(additionalIncludeDirs, fileInfo.includeDirs); 1506 1507 impl->files[uri.file()] = std::make_unique<PDLTextFile>( 1508 uri, contents, version, additionalIncludeDirs, diagnostics); 1509 } 1510 1511 Optional<int64_t> lsp::PDLLServer::removeDocument(const URIForFile &uri) { 1512 auto it = impl->files.find(uri.file()); 1513 if (it == impl->files.end()) 1514 return llvm::None; 1515 1516 int64_t version = it->second->getVersion(); 1517 impl->files.erase(it); 1518 return version; 1519 } 1520 1521 void lsp::PDLLServer::getLocationsOf(const URIForFile &uri, 1522 const Position &defPos, 1523 std::vector<Location> &locations) { 1524 auto fileIt = impl->files.find(uri.file()); 1525 if (fileIt != impl->files.end()) 1526 fileIt->second->getLocationsOf(uri, defPos, locations); 1527 } 1528 1529 void lsp::PDLLServer::findReferencesOf(const URIForFile &uri, 1530 const Position &pos, 1531 std::vector<Location> &references) { 1532 auto fileIt = impl->files.find(uri.file()); 1533 if (fileIt != impl->files.end()) 1534 fileIt->second->findReferencesOf(uri, pos, references); 1535 } 1536 1537 void lsp::PDLLServer::getDocumentLinks( 1538 const URIForFile &uri, std::vector<DocumentLink> &documentLinks) { 1539 auto fileIt = impl->files.find(uri.file()); 1540 if (fileIt != impl->files.end()) 1541 return fileIt->second->getDocumentLinks(uri, documentLinks); 1542 } 1543 1544 Optional<lsp::Hover> lsp::PDLLServer::findHover(const URIForFile &uri, 1545 const Position &hoverPos) { 1546 auto fileIt = impl->files.find(uri.file()); 1547 if (fileIt != impl->files.end()) 1548 return fileIt->second->findHover(uri, hoverPos); 1549 return llvm::None; 1550 } 1551 1552 void lsp::PDLLServer::findDocumentSymbols( 1553 const URIForFile &uri, std::vector<DocumentSymbol> &symbols) { 1554 auto fileIt = impl->files.find(uri.file()); 1555 if (fileIt != impl->files.end()) 1556 fileIt->second->findDocumentSymbols(symbols); 1557 } 1558 1559 lsp::CompletionList 1560 lsp::PDLLServer::getCodeCompletion(const URIForFile &uri, 1561 const Position &completePos) { 1562 auto fileIt = impl->files.find(uri.file()); 1563 if (fileIt != impl->files.end()) 1564 return fileIt->second->getCodeCompletion(uri, completePos); 1565 return CompletionList(); 1566 } 1567 1568 lsp::SignatureHelp lsp::PDLLServer::getSignatureHelp(const URIForFile &uri, 1569 const Position &helpPos) { 1570 auto fileIt = impl->files.find(uri.file()); 1571 if (fileIt != impl->files.end()) 1572 return fileIt->second->getSignatureHelp(uri, helpPos); 1573 return SignatureHelp(); 1574 } 1575 1576 Optional<lsp::PDLLViewOutputResult> 1577 lsp::PDLLServer::getPDLLViewOutput(const URIForFile &uri, 1578 PDLLViewOutputKind kind) { 1579 auto fileIt = impl->files.find(uri.file()); 1580 if (fileIt != impl->files.end()) 1581 return fileIt->second->getPDLLViewOutput(kind); 1582 return llvm::None; 1583 } 1584