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

source code of mlir/lib/Tools/tblgen-lsp-server/TableGenServer.cpp