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/Logging.h" 11 #include "lsp/Protocol.h" 12 #include "mlir/IR/Operation.h" 13 #include "mlir/Parser.h" 14 #include "mlir/Parser/AsmParserState.h" 15 #include "llvm/Support/SourceMgr.h" 16 17 using namespace mlir; 18 19 /// Returns a language server position for the given source location. 20 static lsp::Position getPosFromLoc(llvm::SourceMgr &mgr, llvm::SMLoc loc) { 21 std::pair<unsigned, unsigned> lineAndCol = mgr.getLineAndColumn(loc); 22 lsp::Position pos; 23 pos.line = lineAndCol.first - 1; 24 pos.character = lineAndCol.second; 25 return pos; 26 } 27 28 /// Returns a source location from the given language server position. 29 static llvm::SMLoc getPosFromLoc(llvm::SourceMgr &mgr, lsp::Position pos) { 30 return mgr.FindLocForLineAndColumn(mgr.getMainFileID(), pos.line + 1, 31 pos.character); 32 } 33 34 /// Returns a language server range for the given source range. 35 static lsp::Range getRangeFromLoc(llvm::SourceMgr &mgr, llvm::SMRange range) { 36 // lsp::Range is an inclusive range, SMRange is half-open. 37 llvm::SMLoc inclusiveEnd = 38 llvm::SMLoc::getFromPointer(range.End.getPointer() - 1); 39 return {getPosFromLoc(mgr, range.Start), getPosFromLoc(mgr, inclusiveEnd)}; 40 } 41 42 /// Returns a language server location from the given source range. 43 static lsp::Location getLocationFromLoc(llvm::SourceMgr &mgr, 44 llvm::SMRange range, 45 const lsp::URIForFile &uri) { 46 return lsp::Location{uri, getRangeFromLoc(mgr, range)}; 47 } 48 49 /// Returns a language server location from the given MLIR file location. 50 static Optional<lsp::Location> getLocationFromLoc(FileLineColLoc loc) { 51 llvm::Expected<lsp::URIForFile> sourceURI = 52 lsp::URIForFile::fromFile(loc.getFilename()); 53 if (!sourceURI) { 54 lsp::Logger::error("Failed to create URI for file `{0}`: {1}", 55 loc.getFilename(), 56 llvm::toString(sourceURI.takeError())); 57 return llvm::None; 58 } 59 60 lsp::Position position; 61 position.line = loc.getLine() - 1; 62 position.character = loc.getColumn(); 63 return lsp::Location{*sourceURI, lsp::Range(position)}; 64 } 65 66 /// Returns a language server location from the given MLIR location, or None if 67 /// one couldn't be created. `uri` is an optional additional filter that, when 68 /// present, is used to filter sub locations that do not share the same uri. 69 static Optional<lsp::Location> 70 getLocationFromLoc(Location loc, const lsp::URIForFile *uri = nullptr) { 71 Optional<lsp::Location> location; 72 loc->walk([&](Location nestedLoc) { 73 FileLineColLoc fileLoc = nestedLoc.dyn_cast<FileLineColLoc>(); 74 if (!fileLoc) 75 return WalkResult::advance(); 76 77 Optional<lsp::Location> sourceLoc = getLocationFromLoc(fileLoc); 78 if (sourceLoc && (!uri || sourceLoc->uri == *uri)) { 79 location = *sourceLoc; 80 return WalkResult::interrupt(); 81 } 82 return WalkResult::advance(); 83 }); 84 return location; 85 } 86 87 /// Collect all of the locations from the given MLIR location that are not 88 /// contained within the given URI. 89 static void collectLocationsFromLoc(Location loc, 90 std::vector<lsp::Location> &locations, 91 const lsp::URIForFile &uri) { 92 SetVector<Location> visitedLocs; 93 loc->walk([&](Location nestedLoc) { 94 FileLineColLoc fileLoc = nestedLoc.dyn_cast<FileLineColLoc>(); 95 if (!fileLoc || !visitedLocs.insert(nestedLoc)) 96 return WalkResult::advance(); 97 98 Optional<lsp::Location> sourceLoc = getLocationFromLoc(fileLoc); 99 if (sourceLoc && sourceLoc->uri != uri) 100 locations.push_back(*sourceLoc); 101 return WalkResult::advance(); 102 }); 103 } 104 105 /// Returns true if the given range contains the given source location. Note 106 /// that this has slightly different behavior than SMRange because it is 107 /// inclusive of the end location. 108 static bool contains(llvm::SMRange range, llvm::SMLoc loc) { 109 return range.Start.getPointer() <= loc.getPointer() && 110 loc.getPointer() <= range.End.getPointer(); 111 } 112 113 /// Returns true if the given location is contained by the definition or one of 114 /// the uses of the given SMDefinition. If provided, `overlappedRange` is set to 115 /// the range within `def` that the provided `loc` overlapped with. 116 static bool isDefOrUse(const AsmParserState::SMDefinition &def, llvm::SMLoc loc, 117 llvm::SMRange *overlappedRange = nullptr) { 118 // Check the main definition. 119 if (contains(def.loc, loc)) { 120 if (overlappedRange) 121 *overlappedRange = def.loc; 122 return true; 123 } 124 125 // Check the uses. 126 auto useIt = llvm::find_if(def.uses, [&](const llvm::SMRange &range) { 127 return contains(range, loc); 128 }); 129 if (useIt != def.uses.end()) { 130 if (overlappedRange) 131 *overlappedRange = *useIt; 132 return true; 133 } 134 return false; 135 } 136 137 /// Given a location pointing to a result, return the result number it refers 138 /// to or None if it refers to all of the results. 139 static Optional<unsigned> getResultNumberFromLoc(llvm::SMLoc loc) { 140 // Skip all of the identifier characters. 141 auto isIdentifierChar = [](char c) { 142 return isalnum(c) || c == '%' || c == '$' || c == '.' || c == '_' || 143 c == '-'; 144 }; 145 const char *curPtr = loc.getPointer(); 146 while (isIdentifierChar(*curPtr)) 147 ++curPtr; 148 149 // Check to see if this location indexes into the result group, via `#`. If it 150 // doesn't, we can't extract a sub result number. 151 if (*curPtr != '#') 152 return llvm::None; 153 154 // Compute the sub result number from the remaining portion of the string. 155 const char *numberStart = ++curPtr; 156 while (llvm::isDigit(*curPtr)) 157 ++curPtr; 158 StringRef numberStr(numberStart, curPtr - numberStart); 159 unsigned resultNumber = 0; 160 return numberStr.consumeInteger(10, resultNumber) ? Optional<unsigned>() 161 : resultNumber; 162 } 163 164 /// Given a source location range, return the text covered by the given range. 165 /// If the range is invalid, returns None. 166 static Optional<StringRef> getTextFromRange(llvm::SMRange range) { 167 if (!range.isValid()) 168 return None; 169 const char *startPtr = range.Start.getPointer(); 170 return StringRef(startPtr, range.End.getPointer() - startPtr); 171 } 172 173 /// Given a block, return its position in its parent region. 174 static unsigned getBlockNumber(Block *block) { 175 return std::distance(block->getParent()->begin(), block->getIterator()); 176 } 177 178 /// Given a block and source location, print the source name of the block to the 179 /// given output stream. 180 static void printDefBlockName(raw_ostream &os, Block *block, 181 llvm::SMRange loc = {}) { 182 // Try to extract a name from the source location. 183 Optional<StringRef> text = getTextFromRange(loc); 184 if (text && text->startswith("^")) { 185 os << *text; 186 return; 187 } 188 189 // Otherwise, we don't have a name so print the block number. 190 os << "<Block #" << getBlockNumber(block) << ">"; 191 } 192 static void printDefBlockName(raw_ostream &os, 193 const AsmParserState::BlockDefinition &def) { 194 printDefBlockName(os, def.block, def.definition.loc); 195 } 196 197 /// Convert the given MLIR diagnostic to the LSP form. 198 static lsp::Diagnostic getLspDiagnoticFromDiag(Diagnostic &diag, 199 const lsp::URIForFile &uri) { 200 lsp::Diagnostic lspDiag; 201 lspDiag.source = "mlir"; 202 203 // Note: Right now all of the diagnostics are treated as parser issues, but 204 // some are parser and some are verifier. 205 lspDiag.category = "Parse Error"; 206 207 // Try to grab a file location for this diagnostic. 208 // TODO: For simplicity, we just grab the first one. It may be likely that we 209 // will need a more interesting heuristic here.' 210 Optional<lsp::Location> lspLocation = 211 getLocationFromLoc(diag.getLocation(), &uri); 212 if (lspLocation) 213 lspDiag.range = lspLocation->range; 214 215 // Convert the severity for the diagnostic. 216 switch (diag.getSeverity()) { 217 case DiagnosticSeverity::Note: 218 llvm_unreachable("expected notes to be handled separately"); 219 case DiagnosticSeverity::Warning: 220 lspDiag.severity = lsp::DiagnosticSeverity::Warning; 221 break; 222 case DiagnosticSeverity::Error: 223 lspDiag.severity = lsp::DiagnosticSeverity::Error; 224 break; 225 case DiagnosticSeverity::Remark: 226 lspDiag.severity = lsp::DiagnosticSeverity::Information; 227 break; 228 } 229 lspDiag.message = diag.str(); 230 231 // Attach any notes to the main diagnostic as related information. 232 std::vector<lsp::DiagnosticRelatedInformation> relatedDiags; 233 for (Diagnostic ¬e : diag.getNotes()) { 234 lsp::Location noteLoc; 235 if (Optional<lsp::Location> loc = getLocationFromLoc(note.getLocation())) 236 noteLoc = *loc; 237 else 238 noteLoc.uri = uri; 239 relatedDiags.emplace_back(noteLoc, note.str()); 240 } 241 if (!relatedDiags.empty()) 242 lspDiag.relatedInformation = std::move(relatedDiags); 243 244 return lspDiag; 245 } 246 247 //===----------------------------------------------------------------------===// 248 // MLIRDocument 249 //===----------------------------------------------------------------------===// 250 251 namespace { 252 /// This class represents all of the information pertaining to a specific MLIR 253 /// document. 254 struct MLIRDocument { 255 MLIRDocument(const lsp::URIForFile &uri, StringRef contents, int64_t version, 256 DialectRegistry ®istry, 257 std::vector<lsp::Diagnostic> &diagnostics); 258 259 //===--------------------------------------------------------------------===// 260 // Definitions and References 261 //===--------------------------------------------------------------------===// 262 263 void getLocationsOf(const lsp::URIForFile &uri, const lsp::Position &defPos, 264 std::vector<lsp::Location> &locations); 265 void findReferencesOf(const lsp::URIForFile &uri, const lsp::Position &pos, 266 std::vector<lsp::Location> &references); 267 268 //===--------------------------------------------------------------------===// 269 // Hover 270 //===--------------------------------------------------------------------===// 271 272 Optional<lsp::Hover> findHover(const lsp::URIForFile &uri, 273 const lsp::Position &hoverPos); 274 Optional<lsp::Hover> 275 buildHoverForOperation(const AsmParserState::OperationDefinition &op); 276 lsp::Hover buildHoverForOperationResult(llvm::SMRange hoverRange, 277 Operation *op, unsigned resultStart, 278 unsigned resultEnd, 279 llvm::SMLoc posLoc); 280 lsp::Hover buildHoverForBlock(llvm::SMRange hoverRange, 281 const AsmParserState::BlockDefinition &block); 282 lsp::Hover 283 buildHoverForBlockArgument(llvm::SMRange hoverRange, BlockArgument arg, 284 const AsmParserState::BlockDefinition &block); 285 286 /// The version of this document. 287 int64_t version; 288 289 /// The context used to hold the state contained by the parsed document. 290 MLIRContext context; 291 292 /// The high level parser state used to find definitions and references within 293 /// the source file. 294 AsmParserState asmState; 295 296 /// The container for the IR parsed from the input file. 297 Block parsedIR; 298 299 /// The source manager containing the contents of the input file. 300 llvm::SourceMgr sourceMgr; 301 }; 302 } // namespace 303 304 MLIRDocument::MLIRDocument(const lsp::URIForFile &uri, StringRef contents, 305 int64_t version, DialectRegistry ®istry, 306 std::vector<lsp::Diagnostic> &diagnostics) 307 : version(version), context(registry) { 308 context.allowUnregisteredDialects(); 309 ScopedDiagnosticHandler handler(&context, [&](Diagnostic &diag) { 310 diagnostics.push_back(getLspDiagnoticFromDiag(diag, uri)); 311 }); 312 313 // Try to parsed the given IR string. 314 auto memBuffer = llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file()); 315 if (!memBuffer) { 316 lsp::Logger::error("Failed to create memory buffer for file", uri.file()); 317 return; 318 } 319 320 sourceMgr.AddNewSourceBuffer(std::move(memBuffer), llvm::SMLoc()); 321 if (failed(parseSourceFile(sourceMgr, &parsedIR, &context, nullptr, 322 &asmState))) { 323 // If parsing failed, clear out any of the current state. 324 parsedIR.clear(); 325 asmState = AsmParserState(); 326 return; 327 } 328 } 329 330 //===----------------------------------------------------------------------===// 331 // MLIRDocument: Definitions and References 332 //===----------------------------------------------------------------------===// 333 334 void MLIRDocument::getLocationsOf(const lsp::URIForFile &uri, 335 const lsp::Position &defPos, 336 std::vector<lsp::Location> &locations) { 337 llvm::SMLoc posLoc = getPosFromLoc(sourceMgr, defPos); 338 339 // Functor used to check if an SM definition contains the position. 340 auto containsPosition = [&](const AsmParserState::SMDefinition &def) { 341 if (!isDefOrUse(def, posLoc)) 342 return false; 343 locations.push_back(getLocationFromLoc(sourceMgr, def.loc, uri)); 344 return true; 345 }; 346 347 // Check all definitions related to operations. 348 for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) { 349 if (contains(op.loc, posLoc)) 350 return collectLocationsFromLoc(op.op->getLoc(), locations, uri); 351 for (const auto &result : op.resultGroups) 352 if (containsPosition(result.second)) 353 return collectLocationsFromLoc(op.op->getLoc(), locations, uri); 354 } 355 356 // Check all definitions related to blocks. 357 for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) { 358 if (containsPosition(block.definition)) 359 return; 360 for (const AsmParserState::SMDefinition &arg : block.arguments) 361 if (containsPosition(arg)) 362 return; 363 } 364 } 365 366 void MLIRDocument::findReferencesOf(const lsp::URIForFile &uri, 367 const lsp::Position &pos, 368 std::vector<lsp::Location> &references) { 369 // Functor used to append all of the definitions/uses of the given SM 370 // definition to the reference list. 371 auto appendSMDef = [&](const AsmParserState::SMDefinition &def) { 372 references.push_back(getLocationFromLoc(sourceMgr, def.loc, uri)); 373 for (const llvm::SMRange &use : def.uses) 374 references.push_back(getLocationFromLoc(sourceMgr, use, uri)); 375 }; 376 377 llvm::SMLoc posLoc = getPosFromLoc(sourceMgr, pos); 378 379 // Check all definitions related to operations. 380 for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) { 381 if (contains(op.loc, posLoc)) { 382 for (const auto &result : op.resultGroups) 383 appendSMDef(result.second); 384 return; 385 } 386 for (const auto &result : op.resultGroups) 387 if (isDefOrUse(result.second, posLoc)) 388 return appendSMDef(result.second); 389 } 390 391 // Check all definitions related to blocks. 392 for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) { 393 if (isDefOrUse(block.definition, posLoc)) 394 return appendSMDef(block.definition); 395 396 for (const AsmParserState::SMDefinition &arg : block.arguments) 397 if (isDefOrUse(arg, posLoc)) 398 return appendSMDef(arg); 399 } 400 } 401 402 //===----------------------------------------------------------------------===// 403 // MLIRDocument: Hover 404 //===----------------------------------------------------------------------===// 405 406 Optional<lsp::Hover> MLIRDocument::findHover(const lsp::URIForFile &uri, 407 const lsp::Position &hoverPos) { 408 llvm::SMLoc posLoc = getPosFromLoc(sourceMgr, hoverPos); 409 llvm::SMRange hoverRange; 410 411 // Check for Hovers on operations and results. 412 for (const AsmParserState::OperationDefinition &op : asmState.getOpDefs()) { 413 // Check if the position points at this operation. 414 if (contains(op.loc, posLoc)) 415 return buildHoverForOperation(op); 416 417 // Check if the position points at a result group. 418 for (unsigned i = 0, e = op.resultGroups.size(); i < e; ++i) { 419 const auto &result = op.resultGroups[i]; 420 if (!isDefOrUse(result.second, posLoc, &hoverRange)) 421 continue; 422 423 // Get the range of results covered by the over position. 424 unsigned resultStart = result.first; 425 unsigned resultEnd = 426 (i == e - 1) ? op.op->getNumResults() : op.resultGroups[i + 1].first; 427 return buildHoverForOperationResult(hoverRange, op.op, resultStart, 428 resultEnd, posLoc); 429 } 430 } 431 432 // Check to see if the hover is over a block argument. 433 for (const AsmParserState::BlockDefinition &block : asmState.getBlockDefs()) { 434 if (isDefOrUse(block.definition, posLoc, &hoverRange)) 435 return buildHoverForBlock(hoverRange, block); 436 437 for (const auto &arg : llvm::enumerate(block.arguments)) { 438 if (!isDefOrUse(arg.value(), posLoc, &hoverRange)) 439 continue; 440 441 return buildHoverForBlockArgument( 442 hoverRange, block.block->getArgument(arg.index()), block); 443 } 444 } 445 return llvm::None; 446 } 447 448 Optional<lsp::Hover> MLIRDocument::buildHoverForOperation( 449 const AsmParserState::OperationDefinition &op) { 450 // Don't show hovers for operations with regions to avoid huge hover blocks. 451 // TODO: Should we add support for printing an op without its regions? 452 if (llvm::any_of(op.op->getRegions(), 453 [](Region ®ion) { return !region.empty(); })) 454 return llvm::None; 455 456 lsp::Hover hover(getRangeFromLoc(sourceMgr, op.loc)); 457 llvm::raw_string_ostream os(hover.contents.value); 458 459 // For hovers on an operation, show the generic form. 460 os << "```mlir\n"; 461 op.op->print( 462 os, OpPrintingFlags().printGenericOpForm().elideLargeElementsAttrs()); 463 os << "\n```\n"; 464 465 return hover; 466 } 467 468 lsp::Hover MLIRDocument::buildHoverForOperationResult(llvm::SMRange hoverRange, 469 Operation *op, 470 unsigned resultStart, 471 unsigned resultEnd, 472 llvm::SMLoc posLoc) { 473 lsp::Hover hover(getRangeFromLoc(sourceMgr, hoverRange)); 474 llvm::raw_string_ostream os(hover.contents.value); 475 476 // Add the parent operation name to the hover. 477 os << "Operation: \"" << op->getName() << "\"\n\n"; 478 479 // Check to see if the location points to a specific result within the 480 // group. 481 if (Optional<unsigned> resultNumber = getResultNumberFromLoc(posLoc)) { 482 if ((resultStart + *resultNumber) < resultEnd) { 483 resultStart += *resultNumber; 484 resultEnd = resultStart + 1; 485 } 486 } 487 488 // Add the range of results and their types to the hover info. 489 if ((resultStart + 1) == resultEnd) { 490 os << "Result #" << resultStart << "\n\n" 491 << "Type: `" << op->getResult(resultStart).getType() << "`\n\n"; 492 } else { 493 os << "Result #[" << resultStart << ", " << (resultEnd - 1) << "]\n\n" 494 << "Types: "; 495 llvm::interleaveComma( 496 op->getResults().slice(resultStart, resultEnd), os, 497 [&](Value result) { os << "`" << result.getType() << "`"; }); 498 } 499 500 return hover; 501 } 502 503 lsp::Hover 504 MLIRDocument::buildHoverForBlock(llvm::SMRange hoverRange, 505 const AsmParserState::BlockDefinition &block) { 506 lsp::Hover hover(getRangeFromLoc(sourceMgr, hoverRange)); 507 llvm::raw_string_ostream os(hover.contents.value); 508 509 // Print the given block to the hover output stream. 510 auto printBlockToHover = [&](Block *newBlock) { 511 if (const auto *def = asmState.getBlockDef(newBlock)) 512 printDefBlockName(os, *def); 513 else 514 printDefBlockName(os, newBlock); 515 }; 516 517 // Display the parent operation, block number, predecessors, and successors. 518 os << "Operation: \"" << block.block->getParentOp()->getName() << "\"\n\n" 519 << "Block #" << getBlockNumber(block.block) << "\n\n"; 520 if (!block.block->hasNoPredecessors()) { 521 os << "Predecessors: "; 522 llvm::interleaveComma(block.block->getPredecessors(), os, 523 printBlockToHover); 524 os << "\n\n"; 525 } 526 if (!block.block->hasNoSuccessors()) { 527 os << "Successors: "; 528 llvm::interleaveComma(block.block->getSuccessors(), os, printBlockToHover); 529 os << "\n\n"; 530 } 531 532 return hover; 533 } 534 535 lsp::Hover MLIRDocument::buildHoverForBlockArgument( 536 llvm::SMRange hoverRange, BlockArgument arg, 537 const AsmParserState::BlockDefinition &block) { 538 lsp::Hover hover(getRangeFromLoc(sourceMgr, hoverRange)); 539 llvm::raw_string_ostream os(hover.contents.value); 540 541 // Display the parent operation, block, the argument number, and the type. 542 os << "Operation: \"" << block.block->getParentOp()->getName() << "\"\n\n" 543 << "Block: "; 544 printDefBlockName(os, block); 545 os << "\n\nArgument #" << arg.getArgNumber() << "\n\n" 546 << "Type: `" << arg.getType() << "`\n\n"; 547 548 return hover; 549 } 550 551 //===----------------------------------------------------------------------===// 552 // MLIRServer::Impl 553 //===----------------------------------------------------------------------===// 554 555 struct lsp::MLIRServer::Impl { 556 Impl(DialectRegistry ®istry) : registry(registry) {} 557 558 /// The registry containing dialects that can be recognized in parsed .mlir 559 /// files. 560 DialectRegistry ®istry; 561 562 /// The documents held by the server, mapped by their URI file name. 563 llvm::StringMap<std::unique_ptr<MLIRDocument>> documents; 564 }; 565 566 //===----------------------------------------------------------------------===// 567 // MLIRServer 568 //===----------------------------------------------------------------------===// 569 570 lsp::MLIRServer::MLIRServer(DialectRegistry ®istry) 571 : impl(std::make_unique<Impl>(registry)) {} 572 lsp::MLIRServer::~MLIRServer() {} 573 574 void lsp::MLIRServer::addOrUpdateDocument( 575 const URIForFile &uri, StringRef contents, int64_t version, 576 std::vector<Diagnostic> &diagnostics) { 577 impl->documents[uri.file()] = std::make_unique<MLIRDocument>( 578 uri, contents, version, impl->registry, diagnostics); 579 } 580 581 Optional<int64_t> lsp::MLIRServer::removeDocument(const URIForFile &uri) { 582 auto it = impl->documents.find(uri.file()); 583 if (it == impl->documents.end()) 584 return llvm::None; 585 586 int64_t version = it->second->version; 587 impl->documents.erase(it); 588 return version; 589 } 590 591 void lsp::MLIRServer::getLocationsOf(const URIForFile &uri, 592 const Position &defPos, 593 std::vector<Location> &locations) { 594 auto fileIt = impl->documents.find(uri.file()); 595 if (fileIt != impl->documents.end()) 596 fileIt->second->getLocationsOf(uri, defPos, locations); 597 } 598 599 void lsp::MLIRServer::findReferencesOf(const URIForFile &uri, 600 const Position &pos, 601 std::vector<Location> &references) { 602 auto fileIt = impl->documents.find(uri.file()); 603 if (fileIt != impl->documents.end()) 604 fileIt->second->findReferencesOf(uri, pos, references); 605 } 606 607 Optional<lsp::Hover> lsp::MLIRServer::findHover(const URIForFile &uri, 608 const Position &hoverPos) { 609 auto fileIt = impl->documents.find(uri.file()); 610 if (fileIt != impl->documents.end()) 611 return fileIt->second->findHover(uri, hoverPos); 612 return llvm::None; 613 } 614