1 //===- MLIRServer.cpp - MLIR Generic 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 "MLIRServer.h" 10 #include "../lsp-server-support/Logging.h" 11 #include "../lsp-server-support/Protocol.h" 12 #include "../lsp-server-support/SourceMgrUtils.h" 13 #include "mlir/IR/FunctionInterfaces.h" 14 #include "mlir/IR/Operation.h" 15 #include "mlir/Parser/AsmParserState.h" 16 #include "mlir/Parser/CodeComplete.h" 17 #include "mlir/Parser/Parser.h" 18 #include "llvm/Support/SourceMgr.h" 19 20 using namespace mlir; 21 22 /// Returns a language server location from the given MLIR file location. 23 static Optional<lsp::Location> getLocationFromLoc(FileLineColLoc loc) { 24 llvm::Expected<lsp::URIForFile> sourceURI = 25 lsp::URIForFile::fromFile(loc.getFilename()); 26 if (!sourceURI) { 27 lsp::Logger::error("Failed to create URI for file `{0}`: {1}", 28 loc.getFilename(), 29 llvm::toString(sourceURI.takeError())); 30 return llvm::None; 31 } 32 33 lsp::Position position; 34 position.line = loc.getLine() - 1; 35 position.character = loc.getColumn() ? loc.getColumn() - 1 : 0; 36 return lsp::Location{*sourceURI, lsp::Range(position)}; 37 } 38 39 /// Returns a language server location from the given MLIR location, or None if 40 /// one couldn't be created. `uri` is an optional additional filter that, when 41 /// present, is used to filter sub locations that do not share the same uri. 42 static Optional<lsp::Location> 43 getLocationFromLoc(llvm::SourceMgr &sourceMgr, Location loc, 44 const lsp::URIForFile *uri = nullptr) { 45 Optional<lsp::Location> location; 46 loc->walk([&](Location nestedLoc) { 47 FileLineColLoc fileLoc = nestedLoc.dyn_cast<FileLineColLoc>(); 48 if (!fileLoc) 49 return WalkResult::advance(); 50 51 Optional<lsp::Location> sourceLoc = getLocationFromLoc(fileLoc); 52 if (sourceLoc && (!uri || sourceLoc->uri == *uri)) { 53 location = *sourceLoc; 54 SMLoc loc = sourceMgr.FindLocForLineAndColumn( 55 sourceMgr.getMainFileID(), fileLoc.getLine(), fileLoc.getColumn()); 56 57 // Use range of potential identifier starting at location, else length 1 58 // range. 59 location->range.end.character += 1; 60 if (Optional<SMRange> range = lsp::convertTokenLocToRange(loc)) { 61 auto lineCol = sourceMgr.getLineAndColumn(range->End); 62 location->range.end.character = 63 std::max(fileLoc.getColumn() + 1, lineCol.second - 1); 64 } 65 return WalkResult::interrupt(); 66 } 67 return WalkResult::advance(); 68 }); 69 return location; 70 } 71 72 /// Collect all of the locations from the given MLIR location that are not 73 /// contained within the given URI. 74 static void collectLocationsFromLoc(Location loc, 75 std::vector<lsp::Location> &locations, 76 const lsp::URIForFile &uri) { 77 SetVector<Location> visitedLocs; 78 loc->walk([&](Location nestedLoc) { 79 FileLineColLoc fileLoc = nestedLoc.dyn_cast<FileLineColLoc>(); 80 if (!fileLoc || !visitedLocs.insert(nestedLoc)) 81 return WalkResult::advance(); 82 83 Optional<lsp::Location> sourceLoc = getLocationFromLoc(fileLoc); 84 if (sourceLoc && sourceLoc->uri != uri) 85 locations.push_back(*sourceLoc); 86 return WalkResult::advance(); 87 }); 88 } 89 90 /// Returns true if the given range contains the given source location. Note 91 /// that this has slightly different behavior than SMRange because it is 92 /// inclusive of the end location. 93 static bool contains(SMRange range, SMLoc loc) { 94 return range.Start.getPointer() <= loc.getPointer() && 95 loc.getPointer() <= range.End.getPointer(); 96 } 97 98 /// Returns true if the given location is contained by the definition or one of 99 /// the uses of the given SMDefinition. If provided, `overlappedRange` is set to 100 /// the range within `def` that the provided `loc` overlapped with. 101 static bool isDefOrUse(const AsmParserState::SMDefinition &def, SMLoc loc, 102 SMRange *overlappedRange = nullptr) { 103 // Check the main definition. 104 if (contains(def.loc, loc)) { 105 if (overlappedRange) 106 *overlappedRange = def.loc; 107 return true; 108 } 109 110 // Check the uses. 111 const auto *useIt = llvm::find_if( 112 def.uses, [&](const SMRange &range) { return contains(range, loc); }); 113 if (useIt != def.uses.end()) { 114 if (overlappedRange) 115 *overlappedRange = *useIt; 116 return true; 117 } 118 return false; 119 } 120 121 /// Given a location pointing to a result, return the result number it refers 122 /// to or None if it refers to all of the results. 123 static Optional<unsigned> getResultNumberFromLoc(SMLoc loc) { 124 // Skip all of the identifier characters. 125 auto isIdentifierChar = [](char c) { 126 return isalnum(c) || c == '%' || c == '$' || c == '.' || c == '_' || 127 c == '-'; 128 }; 129 const char *curPtr = loc.getPointer(); 130 while (isIdentifierChar(*curPtr)) 131 ++curPtr; 132 133 // Check to see if this location indexes into the result group, via `#`. If it 134 // doesn't, we can't extract a sub result number. 135 if (*curPtr != '#') 136 return llvm::None; 137 138 // Compute the sub result number from the remaining portion of the string. 139 const char *numberStart = ++curPtr; 140 while (llvm::isDigit(*curPtr)) 141 ++curPtr; 142 StringRef numberStr(numberStart, curPtr - numberStart); 143 unsigned resultNumber = 0; 144 return numberStr.consumeInteger(10, resultNumber) ? Optional<unsigned>() 145 : resultNumber; 146 } 147 148 /// Given a source location range, return the text covered by the given range. 149 /// If the range is invalid, returns None. 150 static Optional<StringRef> getTextFromRange(SMRange range) { 151 if (!range.isValid()) 152 return None; 153 const char *startPtr = range.Start.getPointer(); 154 return StringRef(startPtr, range.End.getPointer() - startPtr); 155 } 156 157 /// Given a block, return its position in its parent region. 158 static unsigned getBlockNumber(Block *block) { 159 return std::distance(block->getParent()->begin(), block->getIterator()); 160 } 161 162 /// Given a block and source location, print the source name of the block to the 163 /// given output stream. 164 static void printDefBlockName(raw_ostream &os, Block *block, SMRange loc = {}) { 165 // Try to extract a name from the source location. 166 Optional<StringRef> text = getTextFromRange(loc); 167 if (text && text->startswith("^")) { 168 os << *text; 169 return; 170 } 171 172 // Otherwise, we don't have a name so print the block number. 173 os << "<Block #" << getBlockNumber(block) << ">"; 174 } 175 static void printDefBlockName(raw_ostream &os, 176 const AsmParserState::BlockDefinition &def) { 177 printDefBlockName(os, def.block, def.definition.loc); 178 } 179 180 /// Convert the given MLIR diagnostic to the LSP form. 181 static lsp::Diagnostic getLspDiagnoticFromDiag(llvm::SourceMgr &sourceMgr, 182 Diagnostic &diag, 183 const lsp::URIForFile &uri) { 184 lsp::Diagnostic lspDiag; 185 lspDiag.source = "mlir"; 186 187 // Note: Right now all of the diagnostics are treated as parser issues, but 188 // some are parser and some are verifier. 189 lspDiag.category = "Parse Error"; 190 191 // Try to grab a file location for this diagnostic. 192 // TODO: For simplicity, we just grab the first one. It may be likely that we 193 // will need a more interesting heuristic here.' 194 Optional<lsp::Location> lspLocation = 195 getLocationFromLoc(sourceMgr, diag.getLocation(), &uri); 196 if (lspLocation) 197 lspDiag.range = lspLocation->range; 198 199 // Convert the severity for the diagnostic. 200 switch (diag.getSeverity()) { 201 case DiagnosticSeverity::Note: 202 llvm_unreachable("expected notes to be handled separately"); 203 case DiagnosticSeverity::Warning: 204 lspDiag.severity = lsp::DiagnosticSeverity::Warning; 205 break; 206 case DiagnosticSeverity::Error: 207 lspDiag.severity = lsp::DiagnosticSeverity::Error; 208 break; 209 case DiagnosticSeverity::Remark: 210 lspDiag.severity = lsp::DiagnosticSeverity::Information; 211 break; 212 } 213 lspDiag.message = diag.str(); 214 215 // Attach any notes to the main diagnostic as related information. 216 std::vector<lsp::DiagnosticRelatedInformation> relatedDiags; 217 for (Diagnostic ¬e : diag.getNotes()) { 218 lsp::Location noteLoc; 219 if (Optional<lsp::Location> loc = 220 getLocationFromLoc(sourceMgr, note.getLocation())) 221 noteLoc = *loc; 222 else 223 noteLoc.uri = uri; 224 relatedDiags.emplace_back(noteLoc, note.str()); 225 } 226 if (!relatedDiags.empty()) 227 lspDiag.relatedInformation = std::move(relatedDiags); 228 229 return lspDiag; 230 } 231 232 //===----------------------------------------------------------------------===// 233 // MLIRDocument 234 //===----------------------------------------------------------------------===// 235 236 namespace { 237 /// This class represents all of the information pertaining to a specific MLIR 238 /// document. 239 struct MLIRDocument { 240 MLIRDocument(MLIRContext &context, const lsp::URIForFile &uri, 241 StringRef contents, std::vector<lsp::Diagnostic> &diagnostics); 242 MLIRDocument(const MLIRDocument &) = delete; 243 MLIRDocument &operator=(const MLIRDocument &) = delete; 244 245 //===--------------------------------------------------------------------===// 246 // Definitions and References 247 //===--------------------------------------------------------------------===// 248 249 void getLocationsOf(const lsp::URIForFile &uri, const lsp::Position &defPos, 250 std::vector<lsp::Location> &locations); 251 void findReferencesOf(const lsp::URIForFile &uri, const lsp::Position &pos, 252 std::vector<lsp::Location> &references); 253 254 //===--------------------------------------------------------------------===// 255 // Hover 256 //===--------------------------------------------------------------------===// 257 258 Optional<lsp::Hover> findHover(const lsp::URIForFile &uri, 259 const lsp::Position &hoverPos); 260 Optional<lsp::Hover> 261 buildHoverForOperation(SMRange hoverRange, 262 const AsmParserState::OperationDefinition &op); 263 lsp::Hover buildHoverForOperationResult(SMRange hoverRange, Operation *op, 264 unsigned resultStart, 265 unsigned resultEnd, SMLoc posLoc); 266 lsp::Hover buildHoverForBlock(SMRange hoverRange, 267 const AsmParserState::BlockDefinition &block); 268 lsp::Hover 269 buildHoverForBlockArgument(SMRange hoverRange, BlockArgument arg, 270 const AsmParserState::BlockDefinition &block); 271 272 //===--------------------------------------------------------------------===// 273 // Document Symbols 274 //===--------------------------------------------------------------------===// 275 276 void findDocumentSymbols(std::vector<lsp::DocumentSymbol> &symbols); 277 void findDocumentSymbols(Operation *op, 278 std::vector<lsp::DocumentSymbol> &symbols); 279 280 //===--------------------------------------------------------------------===// 281 // Code Completion 282 //===--------------------------------------------------------------------===// 283 284 lsp::CompletionList getCodeCompletion(const lsp::URIForFile &uri, 285 const lsp::Position &completePos, 286 const DialectRegistry ®istry); 287 288 //===--------------------------------------------------------------------===// 289 // Code Action 290 //===--------------------------------------------------------------------===// 291 292 void getCodeActionForDiagnostic(const lsp::URIForFile &uri, 293 lsp::Position &pos, StringRef severity, 294 StringRef message, 295 std::vector<lsp::TextEdit> &edits); 296 297 //===--------------------------------------------------------------------===// 298 // Fields 299 //===--------------------------------------------------------------------===// 300 301 /// The high level parser state used to find definitions and references within 302 /// the source file. 303 AsmParserState asmState; 304 305 /// The container for the IR parsed from the input file. 306 Block parsedIR; 307 308 /// The source manager containing the contents of the input file. 309 llvm::SourceMgr sourceMgr; 310 }; 311 } // namespace 312 313 MLIRDocument::MLIRDocument(MLIRContext &context, const lsp::URIForFile &uri, 314 StringRef contents, 315 std::vector<lsp::Diagnostic> &diagnostics) { 316 ScopedDiagnosticHandler handler(&context, [&](Diagnostic &diag) { 317 diagnostics.push_back(getLspDiagnoticFromDiag(sourceMgr, diag, uri)); 318 }); 319 320 // Try to parsed the given IR string. 321 auto memBuffer = llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file()); 322 if (!memBuffer) { 323 lsp::Logger::error("Failed to create memory buffer for file", uri.file()); 324 return; 325 } 326 327 sourceMgr.AddNewSourceBuffer(std::move(memBuffer), SMLoc()); 328 if (failed(parseSourceFile(sourceMgr, &parsedIR, &context, nullptr, 329 &asmState))) { 330 // If parsing failed, clear out any of the current state. 331 parsedIR.clear(); 332 asmState = AsmParserState(); 333 return; 334 } 335 } 336 337 //===----------------------------------------------------------------------===// 338 // MLIRDocument: Definitions and References 339 //===----------------------------------------------------------------------===// 340 341 void MLIRDocument::getLocationsOf(const lsp::URIForFile &uri, 342 const lsp::Position &defPos, 343 std::vector<lsp::Location> &locations) { 344 SMLoc posLoc = defPos.getAsSMLoc(sourceMgr); 345 346 // Functor used to check if an SM definition contains the position. 347 auto containsPosition = [&](const AsmParserState::SMDefinition &def) { 348 if (!isDefOrUse(def, posLoc)) 349 return false; 350 locations.emplace_back(uri, sourceMgr, def.loc); 351 return true; 352 }; 353 354 // Check all definitions related to operations. 355 for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) { 356 if (contains(op.loc, posLoc)) 357 return collectLocationsFromLoc(op.op->getLoc(), locations, uri); 358 for (const auto &result : op.resultGroups) 359 if (containsPosition(result.definition)) 360 return collectLocationsFromLoc(op.op->getLoc(), locations, uri); 361 for (const auto &symUse : op.symbolUses) { 362 if (contains(symUse, posLoc)) { 363 locations.emplace_back(uri, sourceMgr, op.loc); 364 return collectLocationsFromLoc(op.op->getLoc(), locations, uri); 365 } 366 } 367 } 368 369 // Check all definitions related to blocks. 370 for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) { 371 if (containsPosition(block.definition)) 372 return; 373 for (const AsmParserState::SMDefinition &arg : block.arguments) 374 if (containsPosition(arg)) 375 return; 376 } 377 } 378 379 void MLIRDocument::findReferencesOf(const lsp::URIForFile &uri, 380 const lsp::Position &pos, 381 std::vector<lsp::Location> &references) { 382 // Functor used to append all of the definitions/uses of the given SM 383 // definition to the reference list. 384 auto appendSMDef = [&](const AsmParserState::SMDefinition &def) { 385 references.emplace_back(uri, sourceMgr, def.loc); 386 for (const SMRange &use : def.uses) 387 references.emplace_back(uri, sourceMgr, use); 388 }; 389 390 SMLoc posLoc = pos.getAsSMLoc(sourceMgr); 391 392 // Check all definitions related to operations. 393 for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) { 394 if (contains(op.loc, posLoc)) { 395 for (const auto &result : op.resultGroups) 396 appendSMDef(result.definition); 397 for (const auto &symUse : op.symbolUses) 398 if (contains(symUse, posLoc)) 399 references.emplace_back(uri, sourceMgr, symUse); 400 return; 401 } 402 for (const auto &result : op.resultGroups) 403 if (isDefOrUse(result.definition, posLoc)) 404 return appendSMDef(result.definition); 405 for (const auto &symUse : op.symbolUses) { 406 if (!contains(symUse, posLoc)) 407 continue; 408 for (const auto &symUse : op.symbolUses) 409 references.emplace_back(uri, sourceMgr, symUse); 410 return; 411 } 412 } 413 414 // Check all definitions related to blocks. 415 for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) { 416 if (isDefOrUse(block.definition, posLoc)) 417 return appendSMDef(block.definition); 418 419 for (const AsmParserState::SMDefinition &arg : block.arguments) 420 if (isDefOrUse(arg, posLoc)) 421 return appendSMDef(arg); 422 } 423 } 424 425 //===----------------------------------------------------------------------===// 426 // MLIRDocument: Hover 427 //===----------------------------------------------------------------------===// 428 429 Optional<lsp::Hover> MLIRDocument::findHover(const lsp::URIForFile &uri, 430 const lsp::Position &hoverPos) { 431 SMLoc posLoc = hoverPos.getAsSMLoc(sourceMgr); 432 SMRange hoverRange; 433 434 // Check for Hovers on operations and results. 435 for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) { 436 // Check if the position points at this operation. 437 if (contains(op.loc, posLoc)) 438 return buildHoverForOperation(op.loc, op); 439 440 // Check if the position points at the symbol name. 441 for (auto &use : op.symbolUses) 442 if (contains(use, posLoc)) 443 return buildHoverForOperation(use, op); 444 445 // Check if the position points at a result group. 446 for (unsigned i = 0, e = op.resultGroups.size(); i < e; ++i) { 447 const auto &result = op.resultGroups[i]; 448 if (!isDefOrUse(result.definition, posLoc, &hoverRange)) 449 continue; 450 451 // Get the range of results covered by the over position. 452 unsigned resultStart = result.startIndex; 453 unsigned resultEnd = (i == e - 1) ? op.op->getNumResults() 454 : op.resultGroups[i + 1].startIndex; 455 return buildHoverForOperationResult(hoverRange, op.op, resultStart, 456 resultEnd, posLoc); 457 } 458 } 459 460 // Check to see if the hover is over a block argument. 461 for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) { 462 if (isDefOrUse(block.definition, posLoc, &hoverRange)) 463 return buildHoverForBlock(hoverRange, block); 464 465 for (const auto &arg : llvm::enumerate(block.arguments)) { 466 if (!isDefOrUse(arg.value(), posLoc, &hoverRange)) 467 continue; 468 469 return buildHoverForBlockArgument( 470 hoverRange, block.block->getArgument(arg.index()), block); 471 } 472 } 473 return llvm::None; 474 } 475 476 Optional<lsp::Hover> MLIRDocument::buildHoverForOperation( 477 SMRange hoverRange, const AsmParserState::OperationDefinition &op) { 478 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); 479 llvm::raw_string_ostream os(hover.contents.value); 480 481 // Add the operation name to the hover. 482 os << "\"" << op.op->getName() << "\""; 483 if (SymbolOpInterface symbol = dyn_cast<SymbolOpInterface>(op.op)) 484 os << " : " << symbol.getVisibility() << " @" << symbol.getName() << ""; 485 os << "\n\n"; 486 487 os << "Generic Form:\n\n```mlir\n"; 488 489 // Temporary drop the regions of this operation so that they don't get 490 // printed in the output. This helps keeps the size of the output hover 491 // small. 492 SmallVector<std::unique_ptr<Region>> regions; 493 for (Region ®ion : op.op->getRegions()) { 494 regions.emplace_back(std::make_unique<Region>()); 495 regions.back()->takeBody(region); 496 } 497 498 op.op->print( 499 os, OpPrintingFlags().printGenericOpForm().elideLargeElementsAttrs()); 500 os << "\n```\n"; 501 502 // Move the regions back to the current operation. 503 for (Region ®ion : op.op->getRegions()) 504 region.takeBody(*regions.back()); 505 506 return hover; 507 } 508 509 lsp::Hover MLIRDocument::buildHoverForOperationResult(SMRange hoverRange, 510 Operation *op, 511 unsigned resultStart, 512 unsigned resultEnd, 513 SMLoc posLoc) { 514 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); 515 llvm::raw_string_ostream os(hover.contents.value); 516 517 // Add the parent operation name to the hover. 518 os << "Operation: \"" << op->getName() << "\"\n\n"; 519 520 // Check to see if the location points to a specific result within the 521 // group. 522 if (Optional<unsigned> resultNumber = getResultNumberFromLoc(posLoc)) { 523 if ((resultStart + *resultNumber) < resultEnd) { 524 resultStart += *resultNumber; 525 resultEnd = resultStart + 1; 526 } 527 } 528 529 // Add the range of results and their types to the hover info. 530 if ((resultStart + 1) == resultEnd) { 531 os << "Result #" << resultStart << "\n\n" 532 << "Type: `" << op->getResult(resultStart).getType() << "`\n\n"; 533 } else { 534 os << "Result #[" << resultStart << ", " << (resultEnd - 1) << "]\n\n" 535 << "Types: "; 536 llvm::interleaveComma( 537 op->getResults().slice(resultStart, resultEnd), os, 538 [&](Value result) { os << "`" << result.getType() << "`"; }); 539 } 540 541 return hover; 542 } 543 544 lsp::Hover 545 MLIRDocument::buildHoverForBlock(SMRange hoverRange, 546 const AsmParserState::BlockDefinition &block) { 547 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); 548 llvm::raw_string_ostream os(hover.contents.value); 549 550 // Print the given block to the hover output stream. 551 auto printBlockToHover = [&](Block *newBlock) { 552 if (const auto *def = asmState.getBlockDef(newBlock)) 553 printDefBlockName(os, *def); 554 else 555 printDefBlockName(os, newBlock); 556 }; 557 558 // Display the parent operation, block number, predecessors, and successors. 559 os << "Operation: \"" << block.block->getParentOp()->getName() << "\"\n\n" 560 << "Block #" << getBlockNumber(block.block) << "\n\n"; 561 if (!block.block->hasNoPredecessors()) { 562 os << "Predecessors: "; 563 llvm::interleaveComma(block.block->getPredecessors(), os, 564 printBlockToHover); 565 os << "\n\n"; 566 } 567 if (!block.block->hasNoSuccessors()) { 568 os << "Successors: "; 569 llvm::interleaveComma(block.block->getSuccessors(), os, printBlockToHover); 570 os << "\n\n"; 571 } 572 573 return hover; 574 } 575 576 lsp::Hover MLIRDocument::buildHoverForBlockArgument( 577 SMRange hoverRange, BlockArgument arg, 578 const AsmParserState::BlockDefinition &block) { 579 lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); 580 llvm::raw_string_ostream os(hover.contents.value); 581 582 // Display the parent operation, block, the argument number, and the type. 583 os << "Operation: \"" << block.block->getParentOp()->getName() << "\"\n\n" 584 << "Block: "; 585 printDefBlockName(os, block); 586 os << "\n\nArgument #" << arg.getArgNumber() << "\n\n" 587 << "Type: `" << arg.getType() << "`\n\n"; 588 589 return hover; 590 } 591 592 //===----------------------------------------------------------------------===// 593 // MLIRDocument: Document Symbols 594 //===----------------------------------------------------------------------===// 595 596 void MLIRDocument::findDocumentSymbols( 597 std::vector<lsp::DocumentSymbol> &symbols) { 598 for (Operation &op : parsedIR) 599 findDocumentSymbols(&op, symbols); 600 } 601 602 void MLIRDocument::findDocumentSymbols( 603 Operation *op, std::vector<lsp::DocumentSymbol> &symbols) { 604 std::vector<lsp::DocumentSymbol> *childSymbols = &symbols; 605 606 // Check for the source information of this operation. 607 if (const AsmParserState::OperationDefinition *def = asmState.getOpDef(op)) { 608 // If this operation defines a symbol, record it. 609 if (SymbolOpInterface symbol = dyn_cast<SymbolOpInterface>(op)) { 610 symbols.emplace_back(symbol.getName(), 611 isa<FunctionOpInterface>(op) 612 ? lsp::SymbolKind::Function 613 : lsp::SymbolKind::Class, 614 lsp::Range(sourceMgr, def->scopeLoc), 615 lsp::Range(sourceMgr, def->loc)); 616 childSymbols = &symbols.back().children; 617 618 } else if (op->hasTrait<OpTrait::SymbolTable>()) { 619 // Otherwise, if this is a symbol table push an anonymous document symbol. 620 symbols.emplace_back("<" + op->getName().getStringRef() + ">", 621 lsp::SymbolKind::Namespace, 622 lsp::Range(sourceMgr, def->scopeLoc), 623 lsp::Range(sourceMgr, def->loc)); 624 childSymbols = &symbols.back().children; 625 } 626 } 627 628 // Recurse into the regions of this operation. 629 if (!op->getNumRegions()) 630 return; 631 for (Region ®ion : op->getRegions()) 632 for (Operation &childOp : region.getOps()) 633 findDocumentSymbols(&childOp, *childSymbols); 634 } 635 636 //===----------------------------------------------------------------------===// 637 // MLIRDocument: Code Completion 638 //===----------------------------------------------------------------------===// 639 640 namespace { 641 class LSPCodeCompleteContext : public AsmParserCodeCompleteContext { 642 public: 643 LSPCodeCompleteContext(SMLoc completeLoc, lsp::CompletionList &completionList, 644 MLIRContext *ctx) 645 : AsmParserCodeCompleteContext(completeLoc), 646 completionList(completionList), ctx(ctx) {} 647 648 /// Signal code completion for a dialect name, with an optional prefix. 649 void completeDialectName(StringRef prefix) final { 650 for (StringRef dialect : ctx->getAvailableDialects()) { 651 lsp::CompletionItem item(prefix + dialect, 652 lsp::CompletionItemKind::Module, 653 /*sortText=*/"3"); 654 item.detail = "dialect"; 655 completionList.items.emplace_back(item); 656 } 657 } 658 using AsmParserCodeCompleteContext::completeDialectName; 659 660 /// Signal code completion for an operation name within the given dialect. 661 void completeOperationName(StringRef dialectName) final { 662 Dialect *dialect = ctx->getOrLoadDialect(dialectName); 663 if (!dialect) 664 return; 665 666 for (const auto &op : ctx->getRegisteredOperations()) { 667 if (&op.getDialect() != dialect) 668 continue; 669 670 lsp::CompletionItem item( 671 op.getStringRef().drop_front(dialectName.size() + 1), 672 lsp::CompletionItemKind::Field, 673 /*sortText=*/"1"); 674 item.detail = "operation"; 675 completionList.items.emplace_back(item); 676 } 677 } 678 679 /// Append the given SSA value as a code completion result for SSA value 680 /// completions. 681 void appendSSAValueCompletion(StringRef name, std::string typeData) final { 682 // Check if we need to insert the `%` or not. 683 bool stripPrefix = getCodeCompleteLoc().getPointer()[-1] == '%'; 684 685 lsp::CompletionItem item(name, lsp::CompletionItemKind::Variable); 686 if (stripPrefix) 687 item.insertText = name.drop_front(1).str(); 688 item.detail = std::move(typeData); 689 completionList.items.emplace_back(item); 690 } 691 692 /// Append the given block as a code completion result for block name 693 /// completions. 694 void appendBlockCompletion(StringRef name) final { 695 // Check if we need to insert the `^` or not. 696 bool stripPrefix = getCodeCompleteLoc().getPointer()[-1] == '^'; 697 698 lsp::CompletionItem item(name, lsp::CompletionItemKind::Field); 699 if (stripPrefix) 700 item.insertText = name.drop_front(1).str(); 701 completionList.items.emplace_back(item); 702 } 703 704 /// Signal a completion for the given expected token. 705 void completeExpectedTokens(ArrayRef<StringRef> tokens, bool optional) final { 706 for (StringRef token : tokens) { 707 lsp::CompletionItem item(token, lsp::CompletionItemKind::Keyword, 708 /*sortText=*/"0"); 709 item.detail = optional ? "optional" : ""; 710 completionList.items.emplace_back(item); 711 } 712 } 713 714 /// Signal a completion for an attribute. 715 void completeAttribute(const llvm::StringMap<Attribute> &aliases) override { 716 appendSimpleCompletions({"affine_set", "affine_map", "dense", "false", 717 "loc", "opaque", "sparse", "true", "unit"}, 718 lsp::CompletionItemKind::Field, 719 /*sortText=*/"1"); 720 721 completeDialectName("#"); 722 completeAliases(aliases, "#"); 723 } 724 void completeDialectAttributeOrAlias( 725 const llvm::StringMap<Attribute> &aliases) override { 726 completeDialectName(); 727 completeAliases(aliases); 728 } 729 730 /// Signal a completion for a type. 731 void completeType(const llvm::StringMap<Type> &aliases) override { 732 // Handle the various builtin types. 733 appendSimpleCompletions({"memref", "tensor", "complex", "tuple", "vector", 734 "bf16", "f16", "f32", "f64", "f80", "f128", 735 "index", "none"}, 736 lsp::CompletionItemKind::Field, 737 /*sortText=*/"1"); 738 739 // Handle the builtin integer types. 740 for (StringRef type : {"i", "si", "ui"}) { 741 lsp::CompletionItem item(type + "<N>", lsp::CompletionItemKind::Field, 742 /*sortText=*/"1"); 743 item.insertText = type.str(); 744 completionList.items.emplace_back(item); 745 } 746 747 // Insert completions for dialect types and aliases. 748 completeDialectName("!"); 749 completeAliases(aliases, "!"); 750 } 751 void 752 completeDialectTypeOrAlias(const llvm::StringMap<Type> &aliases) override { 753 completeDialectName(); 754 completeAliases(aliases); 755 } 756 757 /// Add completion results for the given set of aliases. 758 template <typename T> 759 void completeAliases(const llvm::StringMap<T> &aliases, 760 StringRef prefix = "") { 761 for (const auto &alias : aliases) { 762 lsp::CompletionItem item(prefix + alias.getKey(), 763 lsp::CompletionItemKind::Field, 764 /*sortText=*/"2"); 765 llvm::raw_string_ostream(item.detail) << "alias: " << alias.getValue(); 766 completionList.items.emplace_back(item); 767 } 768 } 769 770 /// Add a set of simple completions that all have the same kind. 771 void appendSimpleCompletions(ArrayRef<StringRef> completions, 772 lsp::CompletionItemKind kind, 773 StringRef sortText = "") { 774 for (StringRef completion : completions) 775 completionList.items.emplace_back(completion, kind, sortText); 776 } 777 778 private: 779 lsp::CompletionList &completionList; 780 MLIRContext *ctx; 781 }; 782 } // namespace 783 784 lsp::CompletionList 785 MLIRDocument::getCodeCompletion(const lsp::URIForFile &uri, 786 const lsp::Position &completePos, 787 const DialectRegistry ®istry) { 788 SMLoc posLoc = completePos.getAsSMLoc(sourceMgr); 789 if (!posLoc.isValid()) 790 return lsp::CompletionList(); 791 792 // To perform code completion, we run another parse of the module with the 793 // code completion context provided. 794 MLIRContext tmpContext(registry, MLIRContext::Threading::DISABLED); 795 tmpContext.allowUnregisteredDialects(); 796 lsp::CompletionList completionList; 797 LSPCodeCompleteContext lspCompleteContext(posLoc, completionList, 798 &tmpContext); 799 800 Block tmpIR; 801 AsmParserState tmpState; 802 (void)parseSourceFile(sourceMgr, &tmpIR, &tmpContext, 803 /*sourceFileLoc=*/nullptr, &tmpState, 804 &lspCompleteContext); 805 return completionList; 806 } 807 808 //===----------------------------------------------------------------------===// 809 // MLIRDocument: Code Action 810 //===----------------------------------------------------------------------===// 811 812 void MLIRDocument::getCodeActionForDiagnostic( 813 const lsp::URIForFile &uri, lsp::Position &pos, StringRef severity, 814 StringRef message, std::vector<lsp::TextEdit> &edits) { 815 // Ignore diagnostics that print the current operation. These are always 816 // enabled for the language server, but not generally during normal 817 // parsing/verification. 818 if (message.startswith("see current operation: ")) 819 return; 820 821 // Get the start of the line containing the diagnostic. 822 const auto &buffer = sourceMgr.getBufferInfo(sourceMgr.getMainFileID()); 823 const char *lineStart = buffer.getPointerForLineNumber(pos.line + 1); 824 if (!lineStart) 825 return; 826 StringRef line(lineStart, pos.character); 827 828 // Add a text edit for adding an expected-* diagnostic check for this 829 // diagnostic. 830 lsp::TextEdit edit; 831 edit.range = lsp::Range(lsp::Position(pos.line, 0)); 832 833 // Use the indent of the current line for the expected-* diagnostic. 834 size_t indent = line.find_first_not_of(" "); 835 if (indent == StringRef::npos) 836 indent = line.size(); 837 838 edit.newText.append(indent, ' '); 839 llvm::raw_string_ostream(edit.newText) 840 << "// expected-" << severity << " @below {{" << message << "}}\n"; 841 edits.emplace_back(std::move(edit)); 842 } 843 844 //===----------------------------------------------------------------------===// 845 // MLIRTextFileChunk 846 //===----------------------------------------------------------------------===// 847 848 namespace { 849 /// This class represents a single chunk of an MLIR text file. 850 struct MLIRTextFileChunk { 851 MLIRTextFileChunk(MLIRContext &context, uint64_t lineOffset, 852 const lsp::URIForFile &uri, StringRef contents, 853 std::vector<lsp::Diagnostic> &diagnostics) 854 : lineOffset(lineOffset), document(context, uri, contents, diagnostics) {} 855 856 /// Adjust the line number of the given range to anchor at the beginning of 857 /// the file, instead of the beginning of this chunk. 858 void adjustLocForChunkOffset(lsp::Range &range) { 859 adjustLocForChunkOffset(range.start); 860 adjustLocForChunkOffset(range.end); 861 } 862 /// Adjust the line number of the given position to anchor at the beginning of 863 /// the file, instead of the beginning of this chunk. 864 void adjustLocForChunkOffset(lsp::Position &pos) { pos.line += lineOffset; } 865 866 /// The line offset of this chunk from the beginning of the file. 867 uint64_t lineOffset; 868 /// The document referred to by this chunk. 869 MLIRDocument document; 870 }; 871 } // namespace 872 873 //===----------------------------------------------------------------------===// 874 // MLIRTextFile 875 //===----------------------------------------------------------------------===// 876 877 namespace { 878 /// This class represents a text file containing one or more MLIR documents. 879 class MLIRTextFile { 880 public: 881 MLIRTextFile(const lsp::URIForFile &uri, StringRef fileContents, 882 int64_t version, DialectRegistry ®istry, 883 std::vector<lsp::Diagnostic> &diagnostics); 884 885 /// Return the current version of this text file. 886 int64_t getVersion() const { return version; } 887 888 //===--------------------------------------------------------------------===// 889 // LSP Queries 890 //===--------------------------------------------------------------------===// 891 892 void getLocationsOf(const lsp::URIForFile &uri, lsp::Position defPos, 893 std::vector<lsp::Location> &locations); 894 void findReferencesOf(const lsp::URIForFile &uri, lsp::Position pos, 895 std::vector<lsp::Location> &references); 896 Optional<lsp::Hover> findHover(const lsp::URIForFile &uri, 897 lsp::Position hoverPos); 898 void findDocumentSymbols(std::vector<lsp::DocumentSymbol> &symbols); 899 lsp::CompletionList getCodeCompletion(const lsp::URIForFile &uri, 900 lsp::Position completePos); 901 void getCodeActions(const lsp::URIForFile &uri, const lsp::Range &pos, 902 const lsp::CodeActionContext &context, 903 std::vector<lsp::CodeAction> &actions); 904 905 private: 906 /// Find the MLIR document that contains the given position, and update the 907 /// position to be anchored at the start of the found chunk instead of the 908 /// beginning of the file. 909 MLIRTextFileChunk &getChunkFor(lsp::Position &pos); 910 911 /// The context used to hold the state contained by the parsed document. 912 MLIRContext context; 913 914 /// The full string contents of the file. 915 std::string contents; 916 917 /// The version of this file. 918 int64_t version; 919 920 /// The number of lines in the file. 921 int64_t totalNumLines = 0; 922 923 /// The chunks of this file. The order of these chunks is the order in which 924 /// they appear in the text file. 925 std::vector<std::unique_ptr<MLIRTextFileChunk>> chunks; 926 }; 927 } // namespace 928 929 MLIRTextFile::MLIRTextFile(const lsp::URIForFile &uri, StringRef fileContents, 930 int64_t version, DialectRegistry ®istry, 931 std::vector<lsp::Diagnostic> &diagnostics) 932 : context(registry, MLIRContext::Threading::DISABLED), 933 contents(fileContents.str()), version(version) { 934 context.allowUnregisteredDialects(); 935 936 // Split the file into separate MLIR documents. 937 // TODO: Find a way to share the split file marker with other tools. We don't 938 // want to use `splitAndProcessBuffer` here, but we do want to make sure this 939 // marker doesn't go out of sync. 940 SmallVector<StringRef, 8> subContents; 941 StringRef(contents).split(subContents, "// -----"); 942 chunks.emplace_back(std::make_unique<MLIRTextFileChunk>( 943 context, /*lineOffset=*/0, uri, subContents.front(), diagnostics)); 944 945 uint64_t lineOffset = subContents.front().count('\n'); 946 for (StringRef docContents : llvm::drop_begin(subContents)) { 947 unsigned currentNumDiags = diagnostics.size(); 948 auto chunk = std::make_unique<MLIRTextFileChunk>(context, lineOffset, uri, 949 docContents, diagnostics); 950 lineOffset += docContents.count('\n'); 951 952 // Adjust locations used in diagnostics to account for the offset from the 953 // beginning of the file. 954 for (lsp::Diagnostic &diag : 955 llvm::drop_begin(diagnostics, currentNumDiags)) { 956 chunk->adjustLocForChunkOffset(diag.range); 957 958 if (!diag.relatedInformation) 959 continue; 960 for (auto &it : *diag.relatedInformation) 961 if (it.location.uri == uri) 962 chunk->adjustLocForChunkOffset(it.location.range); 963 } 964 chunks.emplace_back(std::move(chunk)); 965 } 966 totalNumLines = lineOffset; 967 } 968 969 void MLIRTextFile::getLocationsOf(const lsp::URIForFile &uri, 970 lsp::Position defPos, 971 std::vector<lsp::Location> &locations) { 972 MLIRTextFileChunk &chunk = getChunkFor(defPos); 973 chunk.document.getLocationsOf(uri, defPos, locations); 974 975 // Adjust any locations within this file for the offset of this chunk. 976 if (chunk.lineOffset == 0) 977 return; 978 for (lsp::Location &loc : locations) 979 if (loc.uri == uri) 980 chunk.adjustLocForChunkOffset(loc.range); 981 } 982 983 void MLIRTextFile::findReferencesOf(const lsp::URIForFile &uri, 984 lsp::Position pos, 985 std::vector<lsp::Location> &references) { 986 MLIRTextFileChunk &chunk = getChunkFor(pos); 987 chunk.document.findReferencesOf(uri, pos, references); 988 989 // Adjust any locations within this file for the offset of this chunk. 990 if (chunk.lineOffset == 0) 991 return; 992 for (lsp::Location &loc : references) 993 if (loc.uri == uri) 994 chunk.adjustLocForChunkOffset(loc.range); 995 } 996 997 Optional<lsp::Hover> MLIRTextFile::findHover(const lsp::URIForFile &uri, 998 lsp::Position hoverPos) { 999 MLIRTextFileChunk &chunk = getChunkFor(hoverPos); 1000 Optional<lsp::Hover> hoverInfo = chunk.document.findHover(uri, hoverPos); 1001 1002 // Adjust any locations within this file for the offset of this chunk. 1003 if (chunk.lineOffset != 0 && hoverInfo && hoverInfo->range) 1004 chunk.adjustLocForChunkOffset(*hoverInfo->range); 1005 return hoverInfo; 1006 } 1007 1008 void MLIRTextFile::findDocumentSymbols( 1009 std::vector<lsp::DocumentSymbol> &symbols) { 1010 if (chunks.size() == 1) 1011 return chunks.front()->document.findDocumentSymbols(symbols); 1012 1013 // If there are multiple chunks in this file, we create top-level symbols for 1014 // each chunk. 1015 for (unsigned i = 0, e = chunks.size(); i < e; ++i) { 1016 MLIRTextFileChunk &chunk = *chunks[i]; 1017 lsp::Position startPos(chunk.lineOffset); 1018 lsp::Position endPos((i == e - 1) ? totalNumLines - 1 1019 : chunks[i + 1]->lineOffset); 1020 lsp::DocumentSymbol symbol("<file-split-" + Twine(i) + ">", 1021 lsp::SymbolKind::Namespace, 1022 /*range=*/lsp::Range(startPos, endPos), 1023 /*selectionRange=*/lsp::Range(startPos)); 1024 chunk.document.findDocumentSymbols(symbol.children); 1025 1026 // Fixup the locations of document symbols within this chunk. 1027 if (i != 0) { 1028 SmallVector<lsp::DocumentSymbol *> symbolsToFix; 1029 for (lsp::DocumentSymbol &childSymbol : symbol.children) 1030 symbolsToFix.push_back(&childSymbol); 1031 1032 while (!symbolsToFix.empty()) { 1033 lsp::DocumentSymbol *symbol = symbolsToFix.pop_back_val(); 1034 chunk.adjustLocForChunkOffset(symbol->range); 1035 chunk.adjustLocForChunkOffset(symbol->selectionRange); 1036 1037 for (lsp::DocumentSymbol &childSymbol : symbol->children) 1038 symbolsToFix.push_back(&childSymbol); 1039 } 1040 } 1041 1042 // Push the symbol for this chunk. 1043 symbols.emplace_back(std::move(symbol)); 1044 } 1045 } 1046 1047 lsp::CompletionList MLIRTextFile::getCodeCompletion(const lsp::URIForFile &uri, 1048 lsp::Position completePos) { 1049 MLIRTextFileChunk &chunk = getChunkFor(completePos); 1050 lsp::CompletionList completionList = chunk.document.getCodeCompletion( 1051 uri, completePos, context.getDialectRegistry()); 1052 1053 // Adjust any completion locations. 1054 for (lsp::CompletionItem &item : completionList.items) { 1055 if (item.textEdit) 1056 chunk.adjustLocForChunkOffset(item.textEdit->range); 1057 for (lsp::TextEdit &edit : item.additionalTextEdits) 1058 chunk.adjustLocForChunkOffset(edit.range); 1059 } 1060 return completionList; 1061 } 1062 1063 void MLIRTextFile::getCodeActions(const lsp::URIForFile &uri, 1064 const lsp::Range &pos, 1065 const lsp::CodeActionContext &context, 1066 std::vector<lsp::CodeAction> &actions) { 1067 // Create actions for any diagnostics in this file. 1068 for (auto &diag : context.diagnostics) { 1069 if (diag.source != "mlir") 1070 continue; 1071 lsp::Position diagPos = diag.range.start; 1072 MLIRTextFileChunk &chunk = getChunkFor(diagPos); 1073 1074 // Add a new code action that inserts a "expected" diagnostic check. 1075 lsp::CodeAction action; 1076 action.title = "Add expected-* diagnostic checks"; 1077 action.kind = lsp::CodeAction::kQuickFix.str(); 1078 1079 StringRef severity; 1080 switch (diag.severity) { 1081 case lsp::DiagnosticSeverity::Error: 1082 severity = "error"; 1083 break; 1084 case lsp::DiagnosticSeverity::Warning: 1085 severity = "warning"; 1086 break; 1087 default: 1088 continue; 1089 } 1090 1091 // Get edits for the diagnostic. 1092 std::vector<lsp::TextEdit> edits; 1093 chunk.document.getCodeActionForDiagnostic(uri, diagPos, severity, 1094 diag.message, edits); 1095 1096 // Walk the related diagnostics, this is how we encode notes. 1097 if (diag.relatedInformation) { 1098 for (auto ¬eDiag : *diag.relatedInformation) { 1099 if (noteDiag.location.uri != uri) 1100 continue; 1101 diagPos = noteDiag.location.range.start; 1102 diagPos.line -= chunk.lineOffset; 1103 chunk.document.getCodeActionForDiagnostic(uri, diagPos, "note", 1104 noteDiag.message, edits); 1105 } 1106 } 1107 // Fixup the locations for any edits. 1108 for (lsp::TextEdit &edit : edits) 1109 chunk.adjustLocForChunkOffset(edit.range); 1110 1111 action.edit.emplace(); 1112 action.edit->changes[uri.uri().str()] = std::move(edits); 1113 action.diagnostics = {diag}; 1114 1115 actions.emplace_back(std::move(action)); 1116 } 1117 } 1118 1119 MLIRTextFileChunk &MLIRTextFile::getChunkFor(lsp::Position &pos) { 1120 if (chunks.size() == 1) 1121 return *chunks.front(); 1122 1123 // Search for the first chunk with a greater line offset, the previous chunk 1124 // is the one that contains `pos`. 1125 auto it = llvm::upper_bound( 1126 chunks, pos, [](const lsp::Position &pos, const auto &chunk) { 1127 return static_cast<uint64_t>(pos.line) < chunk->lineOffset; 1128 }); 1129 MLIRTextFileChunk &chunk = it == chunks.end() ? *chunks.back() : **(--it); 1130 pos.line -= chunk.lineOffset; 1131 return chunk; 1132 } 1133 1134 //===----------------------------------------------------------------------===// 1135 // MLIRServer::Impl 1136 //===----------------------------------------------------------------------===// 1137 1138 struct lsp::MLIRServer::Impl { 1139 Impl(DialectRegistry ®istry) : registry(registry) {} 1140 1141 /// The registry containing dialects that can be recognized in parsed .mlir 1142 /// files. 1143 DialectRegistry ®istry; 1144 1145 /// The files held by the server, mapped by their URI file name. 1146 llvm::StringMap<std::unique_ptr<MLIRTextFile>> files; 1147 }; 1148 1149 //===----------------------------------------------------------------------===// 1150 // MLIRServer 1151 //===----------------------------------------------------------------------===// 1152 1153 lsp::MLIRServer::MLIRServer(DialectRegistry ®istry) 1154 : impl(std::make_unique<Impl>(registry)) {} 1155 lsp::MLIRServer::~MLIRServer() = default; 1156 1157 void lsp::MLIRServer::addOrUpdateDocument( 1158 const URIForFile &uri, StringRef contents, int64_t version, 1159 std::vector<Diagnostic> &diagnostics) { 1160 impl->files[uri.file()] = std::make_unique<MLIRTextFile>( 1161 uri, contents, version, impl->registry, diagnostics); 1162 } 1163 1164 Optional<int64_t> lsp::MLIRServer::removeDocument(const URIForFile &uri) { 1165 auto it = impl->files.find(uri.file()); 1166 if (it == impl->files.end()) 1167 return llvm::None; 1168 1169 int64_t version = it->second->getVersion(); 1170 impl->files.erase(it); 1171 return version; 1172 } 1173 1174 void lsp::MLIRServer::getLocationsOf(const URIForFile &uri, 1175 const Position &defPos, 1176 std::vector<Location> &locations) { 1177 auto fileIt = impl->files.find(uri.file()); 1178 if (fileIt != impl->files.end()) 1179 fileIt->second->getLocationsOf(uri, defPos, locations); 1180 } 1181 1182 void lsp::MLIRServer::findReferencesOf(const URIForFile &uri, 1183 const Position &pos, 1184 std::vector<Location> &references) { 1185 auto fileIt = impl->files.find(uri.file()); 1186 if (fileIt != impl->files.end()) 1187 fileIt->second->findReferencesOf(uri, pos, references); 1188 } 1189 1190 Optional<lsp::Hover> lsp::MLIRServer::findHover(const URIForFile &uri, 1191 const Position &hoverPos) { 1192 auto fileIt = impl->files.find(uri.file()); 1193 if (fileIt != impl->files.end()) 1194 return fileIt->second->findHover(uri, hoverPos); 1195 return llvm::None; 1196 } 1197 1198 void lsp::MLIRServer::findDocumentSymbols( 1199 const URIForFile &uri, std::vector<DocumentSymbol> &symbols) { 1200 auto fileIt = impl->files.find(uri.file()); 1201 if (fileIt != impl->files.end()) 1202 fileIt->second->findDocumentSymbols(symbols); 1203 } 1204 1205 lsp::CompletionList 1206 lsp::MLIRServer::getCodeCompletion(const URIForFile &uri, 1207 const Position &completePos) { 1208 auto fileIt = impl->files.find(uri.file()); 1209 if (fileIt != impl->files.end()) 1210 return fileIt->second->getCodeCompletion(uri, completePos); 1211 return CompletionList(); 1212 } 1213 1214 void lsp::MLIRServer::getCodeActions(const URIForFile &uri, const Range &pos, 1215 const CodeActionContext &context, 1216 std::vector<CodeAction> &actions) { 1217 auto fileIt = impl->files.find(uri.file()); 1218 if (fileIt != impl->files.end()) 1219 fileIt->second->getCodeActions(uri, pos, context, actions); 1220 } 1221