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 | |
28 | using 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. |
33 | static 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. |
39 | static 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. |
54 | static 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 | } |
59 | static 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. |
65 | static std::optional<lsp::Diagnostic> |
66 | getLspDiagnoticFromDiag(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. |
107 | static std::pair<const llvm::Record *, const llvm::RecordVal *> |
108 | getBaseValue(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 | |
142 | namespace { |
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. |
146 | struct 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. |
164 | struct 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. |
179 | struct 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. |
201 | class TableGenIndex { |
202 | public: |
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 | |
214 | private: |
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 | |
253 | void 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 | |
309 | const TableGenIndexSymbol * |
310 | TableGenIndex::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 | |
326 | namespace { |
327 | /// This class represents a text file containing one or more TableGen documents. |
328 | class TableGenTextFile { |
329 | public: |
330 | TableGenTextFile(const lsp::URIForFile &uri, StringRef fileContents, |
331 | int64_t version, |
332 | const std::vector<std::string> &, |
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 | |
375 | private: |
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 | |
403 | TableGenTextFile::TableGenTextFile( |
404 | const lsp::URIForFile &uri, StringRef fileContents, int64_t version, |
405 | const std::vector<std::string> &, |
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 | |
419 | LogicalResult |
420 | TableGenTextFile::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 | |
433 | void 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 | |
479 | void 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 | |
501 | void 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 | |
518 | void 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 | |
528 | std::optional<lsp::Hover> |
529 | TableGenTextFile::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 | |
556 | lsp::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 | |
600 | lsp::Hover |
601 | TableGenTextFile::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 | |
616 | lsp::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 | |
651 | struct 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 | |
670 | lsp::TableGenServer::TableGenServer(const Options &options) |
671 | : impl(std::make_unique<Impl>(args: options)) {} |
672 | lsp::TableGenServer::~TableGenServer() = default; |
673 | |
674 | void 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 | |
686 | void 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 | |
700 | std::optional<int64_t> |
701 | lsp::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 | |
711 | void 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 | |
719 | void 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 | |
727 | void 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 | |
734 | std::optional<lsp::Hover> |
735 | lsp::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 | |