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 &note : 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 &registry);
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 &region : 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 &region : 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 &region : 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 &registry) {
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 &registry,
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 &registry,
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 &noteDiag : *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 &registry) : registry(registry) {}
1140 
1141   /// The registry containing dialects that can be recognized in parsed .mlir
1142   /// files.
1143   DialectRegistry &registry;
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 &registry)
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