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 &note : 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 &registry,
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 &registry,
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 &region) { 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 &registry) : registry(registry) {}
557 
558   /// The registry containing dialects that can be recognized in parsed .mlir
559   /// files.
560   DialectRegistry &registry;
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 &registry)
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