diff --git a/server/src/languages/tlb/completion/providers/BuiltinTypesCompletionProvider.ts b/server/src/languages/tlb/completion/providers/BuiltinTypesCompletionProvider.ts index 14aa7b2b..deb7930c 100644 --- a/server/src/languages/tlb/completion/providers/BuiltinTypesCompletionProvider.ts +++ b/server/src/languages/tlb/completion/providers/BuiltinTypesCompletionProvider.ts @@ -9,47 +9,48 @@ import { } from "@server/languages/tlb/completion/WeightedCompletionItem" import {CompletionItemKind} from "vscode-languageserver-types" -export class BuiltinTypesCompletionProvider implements CompletionProvider { - private readonly types: [string, string][] = [ - ["#", "Nat, 32-bit unsigned integer"], - ["##", "Nat: unsigned integer with `x` bits."], - [ - "#<", - "Nat: unsigned integer less than `x` bits, stored as `lenBits(x - 1)` bits up to 31 bits.", - ], - [ - "#<=", - "Nat: unsigned integer less than or equal to x bits, stored as lenBits(x) bits up to 32 bits.", - ], - ["Any", "remaining bits and references."], - ["Cell", "remaining bits and references."], - ["Int", "257 bits"], - ["UInt", "256 bits"], - ["Bits", "1023 bits"], - ["bits", "X bits"], - ["uint", ""], - ["uint8", ""], - ["uint16", ""], - ["uint32", ""], - ["uint64", ""], - ["uint128", ""], - ["uint256", ""], - ["int", ""], - ["int8", ""], - ["int16", ""], - ["int32", ""], - ["int64", ""], - ["int128", ""], - ["int256", ""], - ["int257", ""], - ] +export const BUILTIN_TYPES: Map = new Map([ + ["#", "Nat, 32-bit unsigned integer"], + ["##", "Nat: unsigned integer with `x` bits"], + [ + "#<", + "Nat: unsigned integer less than `x` stored with the minimum number `⌈log2 x⌉` of bits (up to 31) to represent the number `x`", + ], + [ + "#<=", + "Nat: unsigned integer less than or equal `x` stored with the minimum number `⌈log2(x+1)⌉` of bits (up to 32) to represent the number `x`", + ], + ["Any", "Remaining bits and references"], + ["Cell", "Remaining bits and references"], + ["Int", "257 bits"], + ["UInt", "256 bits"], + ["Bits", "1023 bits"], + ["bits", "X bits"], + ["uint", ""], + ["uint8", ""], + ["uint16", ""], + ["uint32", ""], + ["uint64", ""], + ["uint128", ""], + ["uint256", ""], + ["int", ""], + ["int8", ""], + ["int16", ""], + ["int32", ""], + ["int64", ""], + ["int128", ""], + ["int256", ""], + ["int257", ""], + ["Type", "Built-in TL-B type representing the type of types"], +]) +export class BuiltinTypesCompletionProvider implements CompletionProvider { public isAvailable(ctx: CompletionContext): boolean { return ctx.isType } public addCompletion(_ctx: CompletionContext, result: CompletionResult): void { - for (const [type, description] of this.types) { + for (const [type, description] of BUILTIN_TYPES) { result.add({ label: type, labelDetails: { diff --git a/server/src/languages/tlb/documentation/documentation.ts b/server/src/languages/tlb/documentation/documentation.ts new file mode 100644 index 00000000..a6fd65ce --- /dev/null +++ b/server/src/languages/tlb/documentation/documentation.ts @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2025 TON Studio +import {NamedNode} from "@server/languages/tlb/psi/TlbNode" + +const CODE_FENCE = "```" + +export function generateDeclarationDoc(decls: NamedNode[]): string { + let result = `${CODE_FENCE}\n` + + decls.forEach((decl, index) => { + result += `${decl.node.text}\n` + + if (index !== decls.length - 1) { + result += `\n` + } + }) + + result += `\n${CODE_FENCE}` + return result +} diff --git a/server/src/languages/tlb/documentation/index.ts b/server/src/languages/tlb/documentation/index.ts new file mode 100644 index 00000000..3c7f29b6 --- /dev/null +++ b/server/src/languages/tlb/documentation/index.ts @@ -0,0 +1,117 @@ +import * as lsp from "vscode-languageserver" +import type {Node as SyntaxNode} from "web-tree-sitter" +import {TlbFile} from "@server/languages/tlb/psi/TlbFile" +import {asLspRange} from "@server/utils/position" +import {DeclarationNode, NamedNode} from "@server/languages/tlb/psi/TlbNode" +import {TlbReference} from "@server/languages/tlb/psi/TlbReference" +import {BUILTIN_TYPES} from "@server/languages/tlb/completion/providers/BuiltinTypesCompletionProvider" +import {generateDeclarationDoc} from "@server/languages/tlb/documentation/documentation" + +export function provideTlbDocumentation(hoverNode: SyntaxNode, file: TlbFile): lsp.Hover | null { + function generateKeywordDoc(node: SyntaxNode, doc: string): lsp.Hover | null { + return { + range: asLspRange(node), + contents: { + kind: "markdown", + value: doc, + }, + } + } + + const text = hoverNode.text + + const builtinDoc = BUILTIN_TYPES.get(text) + if (builtinDoc) { + const parent = hoverNode.parent + if (parent?.type === "constructor_tag") return null + return generateKeywordDoc(hoverNode, builtinDoc) + } + + if (hoverNode.type !== "identifier" && hoverNode.type !== "type_identifier") { + return null + } + + const results = TlbReference.multiResolve(new NamedNode(hoverNode, file)) + if (results.length === 0) { + const typeDoc = generateTypeDoc(text) + if (typeDoc) { + return { + range: asLspRange(hoverNode), + contents: { + kind: "markdown", + value: typeDoc, + }, + } + } + return null + } + + if (results[0] instanceof DeclarationNode) { + const declDoc = generateDeclarationDoc(results) + return { + range: asLspRange(hoverNode), + contents: { + kind: "markdown", + value: declDoc, + }, + } + } + + return null +} + +interface TypeDoc { + readonly label: string + readonly range: string + readonly size: string + readonly description?: string +} + +function generateTypeDoc(word: string): string | undefined { + const typeInfo = generateArbitraryIntDoc(word) + if (!typeInfo) return undefined + + return ` +**${word}** — ${typeInfo.label} + +- **Range**: ${typeInfo.range} +- **Size**: ${typeInfo.size} +` +} + +function generateArbitraryIntDoc(type: string): TypeDoc | undefined { + const match = /^(u?int|bits)(\d+)$/.exec(type) + if (!match) return undefined + + const [_, prefix, bits] = match + const bitWidth = Number.parseInt(bits) + + if (prefix === "uint" && (bitWidth < 1 || bitWidth > 256)) return undefined + if (prefix === "int" && (bitWidth < 1 || bitWidth > 257)) return undefined + if (prefix === "bits" && (bitWidth < 1 || bitWidth > 257)) return undefined + + if (prefix === "bits") { + return { + label: `${bitWidth}-bit data`, + range: `0 to ${bitWidth} bits`, + size: `${bitWidth} bits`, + description: "Arbitrary bit-width data", + } + } + + if (prefix === "uint") { + return { + label: `${bitWidth}-bit unsigned integer`, + range: `0 to 2^${bitWidth} - 1`, + size: `${bitWidth} bits`, + description: "Arbitrary bit-width unsigned integer type", + } + } + + return { + label: `${bitWidth}-bit signed integer`, + range: `-2^${bitWidth - 1} to 2^${bitWidth - 1} - 1`, + size: `${bitWidth} bits`, + description: "Arbitrary bit-width signed integer type", + } +} diff --git a/server/src/languages/tlb/semantic-tokens/index.ts b/server/src/languages/tlb/semantic-tokens/index.ts index 50d7bcd0..ab4279d3 100644 --- a/server/src/languages/tlb/semantic-tokens/index.ts +++ b/server/src/languages/tlb/semantic-tokens/index.ts @@ -29,6 +29,9 @@ export function provideTlbSemanticTokens(file: TlbFile): SemanticTokens { case "#<": case "#<=": case "builtin_field": { + const parent = node.parent + if (parent?.type === "constructor_tag") break + pushToken(node, SemanticTokenTypes.macro) break } diff --git a/server/src/server.ts b/server/src/server.ts index 43e4d5fe..0cb58d8d 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -61,6 +61,7 @@ import { } from "@server/files" import {provideTactDocumentation} from "@server/languages/tact/documentation" import {provideFiftDocumentation} from "@server/languages/fift/documentation" +import {provideTlbDocumentation} from "@server/languages/tlb/documentation" import { provideTactDefinition, provideTactTypeDefinition, @@ -518,6 +519,13 @@ connection.onInitialize(async (initParams: lsp.InitializeParams): Promise