Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> = 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: {
Expand Down
20 changes: 20 additions & 0 deletions server/src/languages/tlb/documentation/documentation.ts
Original file line number Diff line number Diff line change
@@ -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
}
117 changes: 117 additions & 0 deletions server/src/languages/tlb/documentation/index.ts
Original file line number Diff line number Diff line change
@@ -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",
}
}
3 changes: 3 additions & 0 deletions server/src/languages/tlb/semantic-tokens/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
8 changes: 8 additions & 0 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -518,6 +519,13 @@ connection.onInitialize(async (initParams: lsp.InitializeParams): Promise<lsp.In
return provideFiftDocumentation(hoverNode, file)
}

if (isTlbFile(uri)) {
const file = findTlbFile(uri)
const hoverNode = nodeAtPosition(params, file)
if (!hoverNode) return null
return provideTlbDocumentation(hoverNode, file)
}

if (isTactFile(uri)) {
const file = findTactFile(params.textDocument.uri)
const hoverNode = nodeAtPosition(params, file)
Expand Down
Loading