xref: /llvm-project/mlir/lib/Tools/tblgen-lsp-server/TableGenServer.cpp (revision 4f4e2abb1a5ff1225d32410fd02b732d077aa056)
1 //===- TableGenServer.cpp - TableGen 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 "TableGenServer.h"
10 
11 #include "mlir/Support/IndentedOstream.h"
12 #include "mlir/Tools/lsp-server-support/CompilationDatabase.h"
13 #include "mlir/Tools/lsp-server-support/Logging.h"
14 #include "mlir/Tools/lsp-server-support/Protocol.h"
15 #include "mlir/Tools/lsp-server-support/SourceMgrUtils.h"
16 #include "llvm/ADT/IntervalMap.h"
17 #include "llvm/ADT/PointerUnion.h"
18 #include "llvm/ADT/StringMap.h"
19 #include "llvm/Support/Path.h"
20 #include "llvm/TableGen/Parser.h"
21 #include "llvm/TableGen/Record.h"
22 #include <optional>
23 
24 using namespace mlir;
25 using llvm::Record;
26 using llvm::RecordKeeper;
27 using llvm::RecordVal;
28 using llvm::SourceMgr;
29 
30 /// Returns the range of a lexical token given a SMLoc corresponding to the
31 /// start of an token location. The range is computed heuristically, and
32 /// supports identifier-like tokens, strings, etc.
33 static SMRange convertTokenLocToRange(SMLoc loc) {
34   return lsp::convertTokenLocToRange(loc, "$");
35 }
36 
37 /// Returns a language server uri for the given source location. `mainFileURI`
38 /// corresponds to the uri for the main file of the source manager.
39 static lsp::URIForFile getURIFromLoc(const SourceMgr &mgr, SMLoc loc,
40                                      const lsp::URIForFile &mainFileURI) {
41   int bufferId = mgr.FindBufferContainingLoc(loc);
42   if (bufferId == 0 || bufferId == static_cast<int>(mgr.getMainFileID()))
43     return mainFileURI;
44   llvm::Expected<lsp::URIForFile> fileForLoc = lsp::URIForFile::fromFile(
45       mgr.getBufferInfo(bufferId).Buffer->getBufferIdentifier());
46   if (fileForLoc)
47     return *fileForLoc;
48   lsp::Logger::error("Failed to create URI for include file: {0}",
49                      llvm::toString(fileForLoc.takeError()));
50   return mainFileURI;
51 }
52 
53 /// Returns a language server location from the given source range.
54 static lsp::Location getLocationFromLoc(SourceMgr &mgr, SMRange loc,
55                                         const lsp::URIForFile &uri) {
56   return lsp::Location(getURIFromLoc(mgr, loc.Start, uri),
57                        lsp::Range(mgr, loc));
58 }
59 static lsp::Location getLocationFromLoc(SourceMgr &mgr, SMLoc loc,
60                                         const lsp::URIForFile &uri) {
61   return getLocationFromLoc(mgr, convertTokenLocToRange(loc), uri);
62 }
63 
64 /// Convert the given TableGen diagnostic to the LSP form.
65 static std::optional<lsp::Diagnostic>
66 getLspDiagnoticFromDiag(const llvm::SMDiagnostic &diag,
67                         const lsp::URIForFile &uri) {
68   auto *sourceMgr = const_cast<SourceMgr *>(diag.getSourceMgr());
69   if (!sourceMgr || !diag.getLoc().isValid())
70     return std::nullopt;
71 
72   lsp::Diagnostic lspDiag;
73   lspDiag.source = "tablegen";
74   lspDiag.category = "Parse Error";
75 
76   // Try to grab a file location for this diagnostic.
77   lsp::Location loc = getLocationFromLoc(*sourceMgr, diag.getLoc(), uri);
78   lspDiag.range = loc.range;
79 
80   // Skip diagnostics that weren't emitted within the main file.
81   if (loc.uri != uri)
82     return std::nullopt;
83 
84   // Convert the severity for the diagnostic.
85   switch (diag.getKind()) {
86   case SourceMgr::DK_Warning:
87     lspDiag.severity = lsp::DiagnosticSeverity::Warning;
88     break;
89   case SourceMgr::DK_Error:
90     lspDiag.severity = lsp::DiagnosticSeverity::Error;
91     break;
92   case SourceMgr::DK_Note:
93     // Notes are emitted separately from the main diagnostic, so we just treat
94     // them as remarks given that we can't determine the diagnostic to relate
95     // them to.
96   case SourceMgr::DK_Remark:
97     lspDiag.severity = lsp::DiagnosticSeverity::Information;
98     break;
99   }
100   lspDiag.message = diag.getMessage().str();
101 
102   return lspDiag;
103 }
104 
105 /// Get the base definition of the given record value, or nullptr if one
106 /// couldn't be found.
107 static std::pair<const Record *, const RecordVal *>
108 getBaseValue(const Record *record, const RecordVal *value) {
109   if (value->isTemplateArg())
110     return {nullptr, nullptr};
111 
112   // Find a base value for the field in the super classes of the given record.
113   // On success, `record` is updated to the new parent record.
114   StringRef valueName = value->getName();
115   auto findValueInSupers = [&](const Record *&record) -> const RecordVal * {
116     for (auto [parentRecord, loc] : record->getSuperClasses()) {
117       if (auto *newBase = parentRecord->getValue(valueName)) {
118         record = parentRecord;
119         return newBase;
120       }
121     }
122     return nullptr;
123   };
124 
125   // Try to find the lowest definition of the record value.
126   std::pair<const Record *, const RecordVal *> baseValue = {};
127   while (const RecordVal *newBase = findValueInSupers(record))
128     baseValue = {record, newBase};
129 
130   // Check that the base isn't the same as the current value (e.g. if the value
131   // wasn't overridden).
132   if (!baseValue.second || baseValue.second->getLoc() == value->getLoc())
133     return {nullptr, nullptr};
134   return baseValue;
135 }
136 
137 //===----------------------------------------------------------------------===//
138 // TableGenIndex
139 //===----------------------------------------------------------------------===//
140 
141 namespace {
142 /// This class represents a single symbol definition within a TableGen index. It
143 /// contains the definition of the symbol, the location of the symbol, and any
144 /// recorded references.
145 struct TableGenIndexSymbol {
146   TableGenIndexSymbol(const Record *record)
147       : definition(record),
148         defLoc(convertTokenLocToRange(record->getLoc().front())) {}
149   TableGenIndexSymbol(const RecordVal *value)
150       : definition(value), defLoc(convertTokenLocToRange(value->getLoc())) {}
151   virtual ~TableGenIndexSymbol() = default;
152 
153   // The main definition of the symbol.
154   PointerUnion<const Record *, const RecordVal *> definition;
155 
156   /// The source location of the definition.
157   SMRange defLoc;
158 
159   /// The source location of the references of the definition.
160   SmallVector<SMRange> references;
161 };
162 /// This class represents a single record symbol.
163 struct TableGenRecordSymbol : public TableGenIndexSymbol {
164   TableGenRecordSymbol(const Record *record) : TableGenIndexSymbol(record) {}
165   ~TableGenRecordSymbol() override = default;
166 
167   static bool classof(const TableGenIndexSymbol *symbol) {
168     return isa<const Record *>(symbol->definition);
169   }
170 
171   /// Return the value of this symbol.
172   const Record *getValue() const { return cast<const Record *>(definition); }
173 };
174 /// This class represents a single record value symbol.
175 struct TableGenRecordValSymbol : public TableGenIndexSymbol {
176   TableGenRecordValSymbol(const Record *record, const RecordVal *value)
177       : TableGenIndexSymbol(value), record(record) {}
178   ~TableGenRecordValSymbol() override = default;
179 
180   static bool classof(const TableGenIndexSymbol *symbol) {
181     return isa<const RecordVal *>(symbol->definition);
182   }
183 
184   /// Return the value of this symbol.
185   const RecordVal *getValue() const {
186     return cast<const RecordVal *>(definition);
187   }
188 
189   /// The parent record of this symbol.
190   const Record *record;
191 };
192 
193 /// This class provides an index for definitions/uses within a TableGen
194 /// document. It provides efficient lookup of a definition given an input source
195 /// range.
196 class TableGenIndex {
197 public:
198   TableGenIndex() : intervalMap(allocator) {}
199 
200   /// Initialize the index with the given RecordKeeper.
201   void initialize(const RecordKeeper &records);
202 
203   /// Lookup a symbol for the given location. Returns nullptr if no symbol could
204   /// be found. If provided, `overlappedRange` is set to the range that the
205   /// provided `loc` overlapped with.
206   const TableGenIndexSymbol *lookup(SMLoc loc,
207                                     SMRange *overlappedRange = nullptr) const;
208 
209 private:
210   /// The type of interval map used to store source references. SMRange is
211   /// half-open, so we also need to use a half-open interval map.
212   using MapT = llvm::IntervalMap<
213       const char *, const TableGenIndexSymbol *,
214       llvm::IntervalMapImpl::NodeSizer<const char *,
215                                        const TableGenIndexSymbol *>::LeafSize,
216       llvm::IntervalMapHalfOpenInfo<const char *>>;
217 
218   /// Get or insert a symbol for the given record.
219   TableGenIndexSymbol *getOrInsertDef(const Record *record) {
220     auto it = defToSymbol.try_emplace(record, nullptr);
221     if (it.second)
222       it.first->second = std::make_unique<TableGenRecordSymbol>(record);
223     return &*it.first->second;
224   }
225   /// Get or insert a symbol for the given record value.
226   TableGenIndexSymbol *getOrInsertDef(const Record *record,
227                                       const RecordVal *value) {
228     auto it = defToSymbol.try_emplace(value, nullptr);
229     if (it.second) {
230       it.first->second =
231           std::make_unique<TableGenRecordValSymbol>(record, value);
232     }
233     return &*it.first->second;
234   }
235 
236   /// An allocator for the interval map.
237   MapT::Allocator allocator;
238 
239   /// An interval map containing a corresponding definition mapped to a source
240   /// interval.
241   MapT intervalMap;
242 
243   /// A mapping between definitions and their corresponding symbol.
244   DenseMap<const void *, std::unique_ptr<TableGenIndexSymbol>> defToSymbol;
245 };
246 } // namespace
247 
248 void TableGenIndex::initialize(const RecordKeeper &records) {
249   intervalMap.clear();
250   defToSymbol.clear();
251 
252   auto insertRef = [&](TableGenIndexSymbol *sym, SMRange refLoc,
253                        bool isDef = false) {
254     const char *startLoc = refLoc.Start.getPointer();
255     const char *endLoc = refLoc.End.getPointer();
256 
257     // If the location we got was empty, try to lex a token from the start
258     // location.
259     if (startLoc == endLoc) {
260       refLoc = convertTokenLocToRange(SMLoc::getFromPointer(startLoc));
261       startLoc = refLoc.Start.getPointer();
262       endLoc = refLoc.End.getPointer();
263 
264       // If the location is still empty, bail on trying to use this reference
265       // location.
266       if (startLoc == endLoc)
267         return;
268     }
269 
270     // Check to see if a symbol is already attached to this location.
271     // IntervalMap doesn't allow overlapping inserts, and we don't really
272     // want multiple symbols attached to a source location anyways. This
273     // shouldn't really happen in practice, but we should handle it gracefully.
274     if (!intervalMap.overlaps(startLoc, endLoc))
275       intervalMap.insert(startLoc, endLoc, sym);
276 
277     if (!isDef)
278       sym->references.push_back(refLoc);
279   };
280   auto classes =
281       llvm::make_pointee_range(llvm::make_second_range(records.getClasses()));
282   auto defs =
283       llvm::make_pointee_range(llvm::make_second_range(records.getDefs()));
284   for (const Record &def : llvm::concat<Record>(classes, defs)) {
285     auto *sym = getOrInsertDef(&def);
286     insertRef(sym, sym->defLoc, /*isDef=*/true);
287 
288     // Add references to the definition.
289     for (SMLoc loc : def.getLoc().drop_front())
290       insertRef(sym, convertTokenLocToRange(loc));
291     for (SMRange loc : def.getReferenceLocs())
292       insertRef(sym, loc);
293 
294     // Add definitions for any values.
295     for (const RecordVal &value : def.getValues()) {
296       auto *sym = getOrInsertDef(&def, &value);
297       insertRef(sym, sym->defLoc, /*isDef=*/true);
298       for (SMRange refLoc : value.getReferenceLocs())
299         insertRef(sym, refLoc);
300     }
301   }
302 }
303 
304 const TableGenIndexSymbol *
305 TableGenIndex::lookup(SMLoc loc, SMRange *overlappedRange) const {
306   auto it = intervalMap.find(loc.getPointer());
307   if (!it.valid() || loc.getPointer() < it.start())
308     return nullptr;
309 
310   if (overlappedRange) {
311     *overlappedRange = SMRange(SMLoc::getFromPointer(it.start()),
312                                SMLoc::getFromPointer(it.stop()));
313   }
314   return it.value();
315 }
316 
317 //===----------------------------------------------------------------------===//
318 // TableGenTextFile
319 //===----------------------------------------------------------------------===//
320 
321 namespace {
322 /// This class represents a text file containing one or more TableGen documents.
323 class TableGenTextFile {
324 public:
325   TableGenTextFile(const lsp::URIForFile &uri, StringRef fileContents,
326                    int64_t version,
327                    const std::vector<std::string> &extraIncludeDirs,
328                    std::vector<lsp::Diagnostic> &diagnostics);
329 
330   /// Return the current version of this text file.
331   int64_t getVersion() const { return version; }
332 
333   /// Update the file to the new version using the provided set of content
334   /// changes. Returns failure if the update was unsuccessful.
335   LogicalResult update(const lsp::URIForFile &uri, int64_t newVersion,
336                        ArrayRef<lsp::TextDocumentContentChangeEvent> changes,
337                        std::vector<lsp::Diagnostic> &diagnostics);
338 
339   //===--------------------------------------------------------------------===//
340   // Definitions and References
341   //===--------------------------------------------------------------------===//
342 
343   void getLocationsOf(const lsp::URIForFile &uri, const lsp::Position &defPos,
344                       std::vector<lsp::Location> &locations);
345   void findReferencesOf(const lsp::URIForFile &uri, const lsp::Position &pos,
346                         std::vector<lsp::Location> &references);
347 
348   //===--------------------------------------------------------------------===//
349   // Document Links
350   //===--------------------------------------------------------------------===//
351 
352   void getDocumentLinks(const lsp::URIForFile &uri,
353                         std::vector<lsp::DocumentLink> &links);
354 
355   //===--------------------------------------------------------------------===//
356   // Hover
357   //===--------------------------------------------------------------------===//
358 
359   std::optional<lsp::Hover> findHover(const lsp::URIForFile &uri,
360                                       const lsp::Position &hoverPos);
361   lsp::Hover buildHoverForRecord(const Record *record,
362                                  const SMRange &hoverRange);
363   lsp::Hover buildHoverForTemplateArg(const Record *record,
364                                       const RecordVal *value,
365                                       const SMRange &hoverRange);
366   lsp::Hover buildHoverForField(const Record *record, const RecordVal *value,
367                                 const SMRange &hoverRange);
368 
369 private:
370   /// Initialize the text file from the given file contents.
371   void initialize(const lsp::URIForFile &uri, int64_t newVersion,
372                   std::vector<lsp::Diagnostic> &diagnostics);
373 
374   /// The full string contents of the file.
375   std::string contents;
376 
377   /// The version of this file.
378   int64_t version;
379 
380   /// The include directories for this file.
381   std::vector<std::string> includeDirs;
382 
383   /// The source manager containing the contents of the input file.
384   SourceMgr sourceMgr;
385 
386   /// The record keeper containing the parsed tablegen constructs.
387   std::unique_ptr<RecordKeeper> recordKeeper;
388 
389   /// The index of the parsed file.
390   TableGenIndex index;
391 
392   /// The set of includes of the parsed file.
393   SmallVector<lsp::SourceMgrInclude> parsedIncludes;
394 };
395 } // namespace
396 
397 TableGenTextFile::TableGenTextFile(
398     const lsp::URIForFile &uri, StringRef fileContents, int64_t version,
399     const std::vector<std::string> &extraIncludeDirs,
400     std::vector<lsp::Diagnostic> &diagnostics)
401     : contents(fileContents.str()), version(version) {
402   // Build the set of include directories for this file.
403   llvm::SmallString<32> uriDirectory(uri.file());
404   llvm::sys::path::remove_filename(uriDirectory);
405   includeDirs.push_back(uriDirectory.str().str());
406   includeDirs.insert(includeDirs.end(), extraIncludeDirs.begin(),
407                      extraIncludeDirs.end());
408 
409   // Initialize the file.
410   initialize(uri, version, diagnostics);
411 }
412 
413 LogicalResult
414 TableGenTextFile::update(const lsp::URIForFile &uri, int64_t newVersion,
415                          ArrayRef<lsp::TextDocumentContentChangeEvent> changes,
416                          std::vector<lsp::Diagnostic> &diagnostics) {
417   if (failed(lsp::TextDocumentContentChangeEvent::applyTo(changes, contents))) {
418     lsp::Logger::error("Failed to update contents of {0}", uri.file());
419     return failure();
420   }
421 
422   // If the file contents were properly changed, reinitialize the text file.
423   initialize(uri, newVersion, diagnostics);
424   return success();
425 }
426 
427 void TableGenTextFile::initialize(const lsp::URIForFile &uri,
428                                   int64_t newVersion,
429                                   std::vector<lsp::Diagnostic> &diagnostics) {
430   version = newVersion;
431   sourceMgr = SourceMgr();
432   recordKeeper = std::make_unique<RecordKeeper>();
433 
434   // Build a buffer for this file.
435   auto memBuffer = llvm::MemoryBuffer::getMemBuffer(contents, uri.file());
436   if (!memBuffer) {
437     lsp::Logger::error("Failed to create memory buffer for file", uri.file());
438     return;
439   }
440   sourceMgr.setIncludeDirs(includeDirs);
441   sourceMgr.AddNewSourceBuffer(std::move(memBuffer), SMLoc());
442 
443   // This class provides a context argument for the SourceMgr diagnostic
444   // handler.
445   struct DiagHandlerContext {
446     std::vector<lsp::Diagnostic> &diagnostics;
447     const lsp::URIForFile &uri;
448   } handlerContext{diagnostics, uri};
449 
450   // Set the diagnostic handler for the tablegen source manager.
451   sourceMgr.setDiagHandler(
452       [](const llvm::SMDiagnostic &diag, void *rawHandlerContext) {
453         auto *ctx = reinterpret_cast<DiagHandlerContext *>(rawHandlerContext);
454         if (auto lspDiag = getLspDiagnoticFromDiag(diag, ctx->uri))
455           ctx->diagnostics.push_back(*lspDiag);
456       },
457       &handlerContext);
458   bool failedToParse = llvm::TableGenParseFile(sourceMgr, *recordKeeper);
459 
460   // Process all of the include files.
461   lsp::gatherIncludeFiles(sourceMgr, parsedIncludes);
462   if (failedToParse)
463     return;
464 
465   // If we successfully parsed the file, we can now build the index.
466   index.initialize(*recordKeeper);
467 }
468 
469 //===----------------------------------------------------------------------===//
470 // TableGenTextFile: Definitions and References
471 //===----------------------------------------------------------------------===//
472 
473 void TableGenTextFile::getLocationsOf(const lsp::URIForFile &uri,
474                                       const lsp::Position &defPos,
475                                       std::vector<lsp::Location> &locations) {
476   SMLoc posLoc = defPos.getAsSMLoc(sourceMgr);
477   const TableGenIndexSymbol *symbol = index.lookup(posLoc);
478   if (!symbol)
479     return;
480 
481   // If this symbol is a record value and the def position is already the def of
482   // the symbol, check to see if the value has a base definition. This allows
483   // for a "go-to-def" on a "let" to resolve the definition in the base class.
484   auto *valSym = dyn_cast<TableGenRecordValSymbol>(symbol);
485   if (valSym && lsp::contains(valSym->defLoc, posLoc)) {
486     if (auto *val = getBaseValue(valSym->record, valSym->getValue()).second) {
487       locations.push_back(getLocationFromLoc(sourceMgr, val->getLoc(), uri));
488       return;
489     }
490   }
491 
492   locations.push_back(getLocationFromLoc(sourceMgr, symbol->defLoc, uri));
493 }
494 
495 void TableGenTextFile::findReferencesOf(
496     const lsp::URIForFile &uri, const lsp::Position &pos,
497     std::vector<lsp::Location> &references) {
498   SMLoc posLoc = pos.getAsSMLoc(sourceMgr);
499   const TableGenIndexSymbol *symbol = index.lookup(posLoc);
500   if (!symbol)
501     return;
502 
503   references.push_back(getLocationFromLoc(sourceMgr, symbol->defLoc, uri));
504   for (SMRange refLoc : symbol->references)
505     references.push_back(getLocationFromLoc(sourceMgr, refLoc, uri));
506 }
507 
508 //===--------------------------------------------------------------------===//
509 // TableGenTextFile: Document Links
510 //===--------------------------------------------------------------------===//
511 
512 void TableGenTextFile::getDocumentLinks(const lsp::URIForFile &uri,
513                                         std::vector<lsp::DocumentLink> &links) {
514   for (const lsp::SourceMgrInclude &include : parsedIncludes)
515     links.emplace_back(include.range, include.uri);
516 }
517 
518 //===----------------------------------------------------------------------===//
519 // TableGenTextFile: Hover
520 //===----------------------------------------------------------------------===//
521 
522 std::optional<lsp::Hover>
523 TableGenTextFile::findHover(const lsp::URIForFile &uri,
524                             const lsp::Position &hoverPos) {
525   // Check for a reference to an include.
526   for (const lsp::SourceMgrInclude &include : parsedIncludes)
527     if (include.range.contains(hoverPos))
528       return include.buildHover();
529 
530   // Find the symbol at the given location.
531   SMRange hoverRange;
532   SMLoc posLoc = hoverPos.getAsSMLoc(sourceMgr);
533   const TableGenIndexSymbol *symbol = index.lookup(posLoc, &hoverRange);
534   if (!symbol)
535     return std::nullopt;
536 
537   // Build hover for a Record.
538   if (auto *record = dyn_cast<TableGenRecordSymbol>(symbol))
539     return buildHoverForRecord(record->getValue(), hoverRange);
540 
541   // Build hover for a RecordVal, which is either a template argument or a
542   // field.
543   auto *recordVal = cast<TableGenRecordValSymbol>(symbol);
544   const RecordVal *value = recordVal->getValue();
545   if (value->isTemplateArg())
546     return buildHoverForTemplateArg(recordVal->record, value, hoverRange);
547   return buildHoverForField(recordVal->record, value, hoverRange);
548 }
549 
550 lsp::Hover TableGenTextFile::buildHoverForRecord(const Record *record,
551                                                  const SMRange &hoverRange) {
552   lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
553   {
554     llvm::raw_string_ostream hoverOS(hover.contents.value);
555 
556     // Format the type of record this is.
557     if (record->isClass()) {
558       hoverOS << "**class** `" << record->getName() << "`";
559     } else if (record->isAnonymous()) {
560       hoverOS << "**anonymous class**";
561     } else {
562       hoverOS << "**def** `" << record->getName() << "`";
563     }
564     hoverOS << "\n***\n";
565 
566     // Check if this record has summary/description fields. These are often used
567     // to hold documentation for the record.
568     auto printAndFormatField = [&](StringRef fieldName) {
569       // Check that the record actually has the given field, and that it's a
570       // string.
571       const RecordVal *value = record->getValue(fieldName);
572       if (!value || !value->getValue())
573         return;
574       auto *stringValue = dyn_cast<llvm::StringInit>(value->getValue());
575       if (!stringValue)
576         return;
577 
578       raw_indented_ostream ros(hoverOS);
579       ros.printReindented(stringValue->getValue().rtrim(" \t"));
580       hoverOS << "\n***\n";
581     };
582     printAndFormatField("summary");
583     printAndFormatField("description");
584 
585     // Check for documentation in the source file.
586     if (std::optional<std::string> doc =
587             lsp::extractSourceDocComment(sourceMgr, record->getLoc().front())) {
588       hoverOS << "\n" << *doc << "\n";
589     }
590   }
591   return hover;
592 }
593 
594 lsp::Hover TableGenTextFile::buildHoverForTemplateArg(
595     const Record *record, const RecordVal *value, const SMRange &hoverRange) {
596   lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
597   {
598     llvm::raw_string_ostream hoverOS(hover.contents.value);
599     StringRef name = value->getName().rsplit(':').second;
600 
601     hoverOS << "**template arg** `" << name << "`\n***\nType: `";
602     value->getType()->print(hoverOS);
603     hoverOS << "`\n";
604   }
605   return hover;
606 }
607 
608 lsp::Hover TableGenTextFile::buildHoverForField(const Record *record,
609                                                 const RecordVal *value,
610                                                 const SMRange &hoverRange) {
611   lsp::Hover hover(lsp::Range(sourceMgr, hoverRange));
612   {
613     llvm::raw_string_ostream hoverOS(hover.contents.value);
614     hoverOS << "**field** `" << value->getName() << "`\n***\nType: `";
615     value->getType()->print(hoverOS);
616     hoverOS << "`\n***\n";
617 
618     // Check for documentation in the source file.
619     if (std::optional<std::string> doc =
620             lsp::extractSourceDocComment(sourceMgr, value->getLoc())) {
621       hoverOS << "\n" << *doc << "\n";
622       hoverOS << "\n***\n";
623     }
624 
625     // Check to see if there is a base value that we can use for
626     // documentation.
627     auto [baseRecord, baseValue] = getBaseValue(record, value);
628     if (baseValue) {
629       if (std::optional<std::string> doc =
630               lsp::extractSourceDocComment(sourceMgr, baseValue->getLoc())) {
631         hoverOS << "\n *From `" << baseRecord->getName() << "`*:\n\n"
632                 << *doc << "\n";
633       }
634     }
635   }
636   return hover;
637 }
638 
639 //===----------------------------------------------------------------------===//
640 // TableGenServer::Impl
641 //===----------------------------------------------------------------------===//
642 
643 struct lsp::TableGenServer::Impl {
644   explicit Impl(const Options &options)
645       : options(options), compilationDatabase(options.compilationDatabases) {}
646 
647   /// TableGen LSP options.
648   const Options &options;
649 
650   /// The compilation database containing additional information for files
651   /// passed to the server.
652   lsp::CompilationDatabase compilationDatabase;
653 
654   /// The files held by the server, mapped by their URI file name.
655   llvm::StringMap<std::unique_ptr<TableGenTextFile>> files;
656 };
657 
658 //===----------------------------------------------------------------------===//
659 // TableGenServer
660 //===----------------------------------------------------------------------===//
661 
662 lsp::TableGenServer::TableGenServer(const Options &options)
663     : impl(std::make_unique<Impl>(options)) {}
664 lsp::TableGenServer::~TableGenServer() = default;
665 
666 void lsp::TableGenServer::addDocument(const URIForFile &uri, StringRef contents,
667                                       int64_t version,
668                                       std::vector<Diagnostic> &diagnostics) {
669   // Build the set of additional include directories.
670   std::vector<std::string> additionalIncludeDirs = impl->options.extraDirs;
671   const auto &fileInfo = impl->compilationDatabase.getFileInfo(uri.file());
672   llvm::append_range(additionalIncludeDirs, fileInfo.includeDirs);
673 
674   impl->files[uri.file()] = std::make_unique<TableGenTextFile>(
675       uri, contents, version, additionalIncludeDirs, diagnostics);
676 }
677 
678 void lsp::TableGenServer::updateDocument(
679     const URIForFile &uri, ArrayRef<TextDocumentContentChangeEvent> changes,
680     int64_t version, std::vector<Diagnostic> &diagnostics) {
681   // Check that we actually have a document for this uri.
682   auto it = impl->files.find(uri.file());
683   if (it == impl->files.end())
684     return;
685 
686   // Try to update the document. If we fail, erase the file from the server. A
687   // failed updated generally means we've fallen out of sync somewhere.
688   if (failed(it->second->update(uri, version, changes, diagnostics)))
689     impl->files.erase(it);
690 }
691 
692 std::optional<int64_t>
693 lsp::TableGenServer::removeDocument(const URIForFile &uri) {
694   auto it = impl->files.find(uri.file());
695   if (it == impl->files.end())
696     return std::nullopt;
697 
698   int64_t version = it->second->getVersion();
699   impl->files.erase(it);
700   return version;
701 }
702 
703 void lsp::TableGenServer::getLocationsOf(const URIForFile &uri,
704                                          const Position &defPos,
705                                          std::vector<Location> &locations) {
706   auto fileIt = impl->files.find(uri.file());
707   if (fileIt != impl->files.end())
708     fileIt->second->getLocationsOf(uri, defPos, locations);
709 }
710 
711 void lsp::TableGenServer::findReferencesOf(const URIForFile &uri,
712                                            const Position &pos,
713                                            std::vector<Location> &references) {
714   auto fileIt = impl->files.find(uri.file());
715   if (fileIt != impl->files.end())
716     fileIt->second->findReferencesOf(uri, pos, references);
717 }
718 
719 void lsp::TableGenServer::getDocumentLinks(
720     const URIForFile &uri, std::vector<DocumentLink> &documentLinks) {
721   auto fileIt = impl->files.find(uri.file());
722   if (fileIt != impl->files.end())
723     return fileIt->second->getDocumentLinks(uri, documentLinks);
724 }
725 
726 std::optional<lsp::Hover>
727 lsp::TableGenServer::findHover(const URIForFile &uri,
728                                const Position &hoverPos) {
729   auto fileIt = impl->files.find(uri.file());
730   if (fileIt != impl->files.end())
731     return fileIt->second->findHover(uri, hoverPos);
732   return std::nullopt;
733 }
734