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, 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 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(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(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<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 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 (const Record *parentRecord : record->getSuperClasses()) { |
117 | if (auto *newBase = parentRecord->getValue(Name: 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(loc: record->getLoc().front())) {} |
149 | TableGenIndexSymbol(const RecordVal *value) |
150 | : definition(value), defLoc(convertTokenLocToRange(loc: 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 *>(Val: symbol->definition); |
169 | } |
170 | |
171 | /// Return the value of this symbol. |
172 | const Record *getValue() const { return cast<const Record *>(Val: 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 *>(Val: symbol->definition); |
182 | } |
183 | |
184 | /// Return the value of this symbol. |
185 | const RecordVal *getValue() const { |
186 | return cast<const RecordVal *>(Val: 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(Key: record, Args: nullptr); |
221 | if (it.second) |
222 | it.first->second = std::make_unique<TableGenRecordSymbol>(args&: 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(Key: value, Args: nullptr); |
229 | if (it.second) { |
230 | it.first->second = |
231 | std::make_unique<TableGenRecordValSymbol>(args&: record, args&: 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(loc: SMLoc::getFromPointer(Ptr: 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(a: startLoc, b: endLoc)) |
275 | intervalMap.insert(a: startLoc, b: endLoc, y: sym); |
276 | |
277 | if (!isDef) |
278 | sym->references.push_back(Elt: refLoc); |
279 | }; |
280 | auto classes = |
281 | llvm::make_pointee_range(Range: llvm::make_second_range(c: records.getClasses())); |
282 | auto defs = |
283 | llvm::make_pointee_range(Range: llvm::make_second_range(c: records.getDefs())); |
284 | for (const Record &def : llvm::concat<Record>(Ranges&: classes, Ranges&: defs)) { |
285 | auto *sym = getOrInsertDef(record: &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(record: &def, value: &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(x: loc.getPointer()); |
307 | if (!it.valid() || loc.getPointer() < it.start()) |
308 | return nullptr; |
309 | |
310 | if (overlappedRange) { |
311 | *overlappedRange = SMRange(SMLoc::getFromPointer(Ptr: it.start()), |
312 | SMLoc::getFromPointer(Ptr: 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(path&: uriDirectory); |
405 | includeDirs.push_back(x: uriDirectory.str().str()); |
406 | llvm::append_range(C&: includeDirs, R: extraIncludeDirs); |
407 | |
408 | // Initialize the file. |
409 | initialize(uri, newVersion: version, diagnostics); |
410 | } |
411 | |
412 | LogicalResult |
413 | TableGenTextFile::update(const lsp::URIForFile &uri, int64_t newVersion, |
414 | ArrayRef<lsp::TextDocumentContentChangeEvent> changes, |
415 | std::vector<lsp::Diagnostic> &diagnostics) { |
416 | if (failed(Result: lsp::TextDocumentContentChangeEvent::applyTo(changes, contents))) { |
417 | lsp::Logger::error(fmt: "Failed to update contents of {0}", vals: uri.file()); |
418 | return failure(); |
419 | } |
420 | |
421 | // If the file contents were properly changed, reinitialize the text file. |
422 | initialize(uri, newVersion, diagnostics); |
423 | return success(); |
424 | } |
425 | |
426 | void TableGenTextFile::initialize(const lsp::URIForFile &uri, |
427 | int64_t newVersion, |
428 | std::vector<lsp::Diagnostic> &diagnostics) { |
429 | version = newVersion; |
430 | sourceMgr = SourceMgr(); |
431 | recordKeeper = std::make_unique<RecordKeeper>(); |
432 | |
433 | // Build a buffer for this file. |
434 | auto memBuffer = llvm::MemoryBuffer::getMemBuffer(InputData: contents, BufferName: uri.file()); |
435 | if (!memBuffer) { |
436 | lsp::Logger::error(fmt: "Failed to create memory buffer for file", vals: uri.file()); |
437 | return; |
438 | } |
439 | sourceMgr.setIncludeDirs(includeDirs); |
440 | sourceMgr.AddNewSourceBuffer(F: std::move(memBuffer), IncludeLoc: SMLoc()); |
441 | |
442 | // This class provides a context argument for the SourceMgr diagnostic |
443 | // handler. |
444 | struct DiagHandlerContext { |
445 | std::vector<lsp::Diagnostic> &diagnostics; |
446 | const lsp::URIForFile &uri; |
447 | } handlerContext{.diagnostics: diagnostics, .uri: uri}; |
448 | |
449 | // Set the diagnostic handler for the tablegen source manager. |
450 | sourceMgr.setDiagHandler( |
451 | DH: [](const llvm::SMDiagnostic &diag, void *rawHandlerContext) { |
452 | auto *ctx = reinterpret_cast<DiagHandlerContext *>(rawHandlerContext); |
453 | if (auto lspDiag = getLspDiagnoticFromDiag(diag, uri: ctx->uri)) |
454 | ctx->diagnostics.push_back(x: *lspDiag); |
455 | }, |
456 | Ctx: &handlerContext); |
457 | bool failedToParse = llvm::TableGenParseFile(InputSrcMgr&: sourceMgr, Records&: *recordKeeper); |
458 | |
459 | // Process all of the include files. |
460 | lsp::gatherIncludeFiles(sourceMgr, includes&: parsedIncludes); |
461 | if (failedToParse) |
462 | return; |
463 | |
464 | // If we successfully parsed the file, we can now build the index. |
465 | index.initialize(records: *recordKeeper); |
466 | } |
467 | |
468 | //===----------------------------------------------------------------------===// |
469 | // TableGenTextFile: Definitions and References |
470 | //===----------------------------------------------------------------------===// |
471 | |
472 | void TableGenTextFile::getLocationsOf(const lsp::URIForFile &uri, |
473 | const lsp::Position &defPos, |
474 | std::vector<lsp::Location> &locations) { |
475 | SMLoc posLoc = defPos.getAsSMLoc(mgr&: sourceMgr); |
476 | const TableGenIndexSymbol *symbol = index.lookup(loc: posLoc); |
477 | if (!symbol) |
478 | return; |
479 | |
480 | // If this symbol is a record value and the def position is already the def of |
481 | // the symbol, check to see if the value has a base definition. This allows |
482 | // for a "go-to-def" on a "let" to resolve the definition in the base class. |
483 | auto *valSym = dyn_cast<TableGenRecordValSymbol>(Val: symbol); |
484 | if (valSym && lsp::contains(range: valSym->defLoc, loc: posLoc)) { |
485 | if (auto *val = getBaseValue(record: valSym->record, value: valSym->getValue()).second) { |
486 | locations.push_back(x: getLocationFromLoc(mgr&: sourceMgr, loc: val->getLoc(), uri)); |
487 | return; |
488 | } |
489 | } |
490 | |
491 | locations.push_back(x: getLocationFromLoc(mgr&: sourceMgr, loc: symbol->defLoc, uri)); |
492 | } |
493 | |
494 | void TableGenTextFile::findReferencesOf( |
495 | const lsp::URIForFile &uri, const lsp::Position &pos, |
496 | std::vector<lsp::Location> &references) { |
497 | SMLoc posLoc = pos.getAsSMLoc(mgr&: sourceMgr); |
498 | const TableGenIndexSymbol *symbol = index.lookup(loc: posLoc); |
499 | if (!symbol) |
500 | return; |
501 | |
502 | references.push_back(x: getLocationFromLoc(mgr&: sourceMgr, loc: symbol->defLoc, uri)); |
503 | for (SMRange refLoc : symbol->references) |
504 | references.push_back(x: getLocationFromLoc(mgr&: sourceMgr, loc: refLoc, uri)); |
505 | } |
506 | |
507 | //===--------------------------------------------------------------------===// |
508 | // TableGenTextFile: Document Links |
509 | //===--------------------------------------------------------------------===// |
510 | |
511 | void TableGenTextFile::getDocumentLinks(const lsp::URIForFile &uri, |
512 | std::vector<lsp::DocumentLink> &links) { |
513 | for (const lsp::SourceMgrInclude &include : parsedIncludes) |
514 | links.emplace_back(args: include.range, args: include.uri); |
515 | } |
516 | |
517 | //===----------------------------------------------------------------------===// |
518 | // TableGenTextFile: Hover |
519 | //===----------------------------------------------------------------------===// |
520 | |
521 | std::optional<lsp::Hover> |
522 | TableGenTextFile::findHover(const lsp::URIForFile &uri, |
523 | const lsp::Position &hoverPos) { |
524 | // Check for a reference to an include. |
525 | for (const lsp::SourceMgrInclude &include : parsedIncludes) |
526 | if (include.range.contains(pos: hoverPos)) |
527 | return include.buildHover(); |
528 | |
529 | // Find the symbol at the given location. |
530 | SMRange hoverRange; |
531 | SMLoc posLoc = hoverPos.getAsSMLoc(mgr&: sourceMgr); |
532 | const TableGenIndexSymbol *symbol = index.lookup(loc: posLoc, overlappedRange: &hoverRange); |
533 | if (!symbol) |
534 | return std::nullopt; |
535 | |
536 | // Build hover for a Record. |
537 | if (auto *record = dyn_cast<TableGenRecordSymbol>(Val: symbol)) |
538 | return buildHoverForRecord(record: record->getValue(), hoverRange); |
539 | |
540 | // Build hover for a RecordVal, which is either a template argument or a |
541 | // field. |
542 | auto *recordVal = cast<TableGenRecordValSymbol>(Val: symbol); |
543 | const RecordVal *value = recordVal->getValue(); |
544 | if (value->isTemplateArg()) |
545 | return buildHoverForTemplateArg(record: recordVal->record, value, hoverRange); |
546 | return buildHoverForField(record: recordVal->record, value, hoverRange); |
547 | } |
548 | |
549 | lsp::Hover TableGenTextFile::buildHoverForRecord(const Record *record, |
550 | const SMRange &hoverRange) { |
551 | lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); |
552 | { |
553 | llvm::raw_string_ostream hoverOS(hover.contents.value); |
554 | |
555 | // Format the type of record this is. |
556 | if (record->isClass()) { |
557 | hoverOS << "**class** `"<< record->getName() << "`"; |
558 | } else if (record->isAnonymous()) { |
559 | hoverOS << "**anonymous class**"; |
560 | } else { |
561 | hoverOS << "**def** `"<< record->getName() << "`"; |
562 | } |
563 | hoverOS << "\n***\n"; |
564 | |
565 | // Check if this record has summary/description fields. These are often used |
566 | // to hold documentation for the record. |
567 | auto printAndFormatField = [&](StringRef fieldName) { |
568 | // Check that the record actually has the given field, and that it's a |
569 | // string. |
570 | const RecordVal *value = record->getValue(Name: fieldName); |
571 | if (!value || !value->getValue()) |
572 | return; |
573 | auto *stringValue = dyn_cast<llvm::StringInit>(Val: value->getValue()); |
574 | if (!stringValue) |
575 | return; |
576 | |
577 | raw_indented_ostream ros(hoverOS); |
578 | ros.printReindented(str: stringValue->getValue().rtrim(Chars: " \t")); |
579 | hoverOS << "\n***\n"; |
580 | }; |
581 | printAndFormatField("summary"); |
582 | printAndFormatField("description"); |
583 | |
584 | // Check for documentation in the source file. |
585 | if (std::optional<std::string> doc = |
586 | lsp::extractSourceDocComment(sourceMgr, loc: record->getLoc().front())) { |
587 | hoverOS << "\n"<< *doc << "\n"; |
588 | } |
589 | } |
590 | return hover; |
591 | } |
592 | |
593 | lsp::Hover TableGenTextFile::buildHoverForTemplateArg( |
594 | const Record *record, const RecordVal *value, const SMRange &hoverRange) { |
595 | lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); |
596 | { |
597 | llvm::raw_string_ostream hoverOS(hover.contents.value); |
598 | StringRef name = value->getName().rsplit(Separator: ':').second; |
599 | |
600 | hoverOS << "**template arg** `"<< name << "`\n***\nType: `"; |
601 | value->getType()->print(OS&: hoverOS); |
602 | hoverOS << "`\n"; |
603 | } |
604 | return hover; |
605 | } |
606 | |
607 | lsp::Hover TableGenTextFile::buildHoverForField(const Record *record, |
608 | const RecordVal *value, |
609 | const SMRange &hoverRange) { |
610 | lsp::Hover hover(lsp::Range(sourceMgr, hoverRange)); |
611 | { |
612 | llvm::raw_string_ostream hoverOS(hover.contents.value); |
613 | hoverOS << "**field** `"<< value->getName() << "`\n***\nType: `"; |
614 | value->getType()->print(OS&: hoverOS); |
615 | hoverOS << "`\n***\n"; |
616 | |
617 | // Check for documentation in the source file. |
618 | if (std::optional<std::string> doc = |
619 | lsp::extractSourceDocComment(sourceMgr, loc: value->getLoc())) { |
620 | hoverOS << "\n"<< *doc << "\n"; |
621 | hoverOS << "\n***\n"; |
622 | } |
623 | |
624 | // Check to see if there is a base value that we can use for |
625 | // documentation. |
626 | auto [baseRecord, baseValue] = getBaseValue(record, value); |
627 | if (baseValue) { |
628 | if (std::optional<std::string> doc = |
629 | lsp::extractSourceDocComment(sourceMgr, loc: baseValue->getLoc())) { |
630 | hoverOS << "\n *From `"<< baseRecord->getName() << "`*:\n\n" |
631 | << *doc << "\n"; |
632 | } |
633 | } |
634 | } |
635 | return hover; |
636 | } |
637 | |
638 | //===----------------------------------------------------------------------===// |
639 | // TableGenServer::Impl |
640 | //===----------------------------------------------------------------------===// |
641 | |
642 | struct lsp::TableGenServer::Impl { |
643 | explicit Impl(const Options &options) |
644 | : options(options), compilationDatabase(options.compilationDatabases) {} |
645 | |
646 | /// TableGen LSP options. |
647 | const Options &options; |
648 | |
649 | /// The compilation database containing additional information for files |
650 | /// passed to the server. |
651 | lsp::CompilationDatabase compilationDatabase; |
652 | |
653 | /// The files held by the server, mapped by their URI file name. |
654 | llvm::StringMap<std::unique_ptr<TableGenTextFile>> files; |
655 | }; |
656 | |
657 | //===----------------------------------------------------------------------===// |
658 | // TableGenServer |
659 | //===----------------------------------------------------------------------===// |
660 | |
661 | lsp::TableGenServer::TableGenServer(const Options &options) |
662 | : impl(std::make_unique<Impl>(args: options)) {} |
663 | lsp::TableGenServer::~TableGenServer() = default; |
664 | |
665 | void lsp::TableGenServer::addDocument(const URIForFile &uri, StringRef contents, |
666 | int64_t version, |
667 | std::vector<Diagnostic> &diagnostics) { |
668 | // Build the set of additional include directories. |
669 | std::vector<std::string> additionalIncludeDirs = impl->options.extraDirs; |
670 | const auto &fileInfo = impl->compilationDatabase.getFileInfo(filename: uri.file()); |
671 | llvm::append_range(C&: additionalIncludeDirs, R: fileInfo.includeDirs); |
672 | |
673 | impl->files[uri.file()] = std::make_unique<TableGenTextFile>( |
674 | args: uri, args&: contents, args&: version, args&: additionalIncludeDirs, args&: diagnostics); |
675 | } |
676 | |
677 | void lsp::TableGenServer::updateDocument( |
678 | const URIForFile &uri, ArrayRef<TextDocumentContentChangeEvent> changes, |
679 | int64_t version, std::vector<Diagnostic> &diagnostics) { |
680 | // Check that we actually have a document for this uri. |
681 | auto it = impl->files.find(Key: uri.file()); |
682 | if (it == impl->files.end()) |
683 | return; |
684 | |
685 | // Try to update the document. If we fail, erase the file from the server. A |
686 | // failed updated generally means we've fallen out of sync somewhere. |
687 | if (failed(Result: it->second->update(uri, newVersion: version, changes, diagnostics))) |
688 | impl->files.erase(I: it); |
689 | } |
690 | |
691 | std::optional<int64_t> |
692 | lsp::TableGenServer::removeDocument(const URIForFile &uri) { |
693 | auto it = impl->files.find(Key: uri.file()); |
694 | if (it == impl->files.end()) |
695 | return std::nullopt; |
696 | |
697 | int64_t version = it->second->getVersion(); |
698 | impl->files.erase(I: it); |
699 | return version; |
700 | } |
701 | |
702 | void lsp::TableGenServer::getLocationsOf(const URIForFile &uri, |
703 | const Position &defPos, |
704 | std::vector<Location> &locations) { |
705 | auto fileIt = impl->files.find(Key: uri.file()); |
706 | if (fileIt != impl->files.end()) |
707 | fileIt->second->getLocationsOf(uri, defPos, locations); |
708 | } |
709 | |
710 | void lsp::TableGenServer::findReferencesOf(const URIForFile &uri, |
711 | const Position &pos, |
712 | std::vector<Location> &references) { |
713 | auto fileIt = impl->files.find(Key: uri.file()); |
714 | if (fileIt != impl->files.end()) |
715 | fileIt->second->findReferencesOf(uri, pos, references); |
716 | } |
717 | |
718 | void lsp::TableGenServer::getDocumentLinks( |
719 | const URIForFile &uri, std::vector<DocumentLink> &documentLinks) { |
720 | auto fileIt = impl->files.find(Key: uri.file()); |
721 | if (fileIt != impl->files.end()) |
722 | return fileIt->second->getDocumentLinks(uri, links&: documentLinks); |
723 | } |
724 | |
725 | std::optional<lsp::Hover> |
726 | lsp::TableGenServer::findHover(const URIForFile &uri, |
727 | const Position &hoverPos) { |
728 | auto fileIt = impl->files.find(Key: uri.file()); |
729 | if (fileIt != impl->files.end()) |
730 | return fileIt->second->findHover(uri, hoverPos); |
731 | return std::nullopt; |
732 | } |
733 |
Definitions
- convertTokenLocToRange
- getURIFromLoc
- getLocationFromLoc
- getLocationFromLoc
- getLspDiagnoticFromDiag
- getBaseValue
- TableGenIndexSymbol
- TableGenIndexSymbol
- TableGenIndexSymbol
- ~TableGenIndexSymbol
- TableGenRecordSymbol
- TableGenRecordSymbol
- ~TableGenRecordSymbol
- classof
- getValue
- TableGenRecordValSymbol
- TableGenRecordValSymbol
- ~TableGenRecordValSymbol
- classof
- getValue
- TableGenIndex
- TableGenIndex
- getOrInsertDef
- getOrInsertDef
- initialize
- lookup
- TableGenTextFile
- getVersion
- TableGenTextFile
- update
- initialize
- getLocationsOf
- findReferencesOf
- getDocumentLinks
- findHover
- buildHoverForRecord
- buildHoverForTemplateArg
- buildHoverForField
- Impl
- Impl
- TableGenServer
- ~TableGenServer
- addDocument
- updateDocument
- removeDocument
- getLocationsOf
- findReferencesOf
- getDocumentLinks
Learn to use CMake with our Intro Training
Find out more