From 9228009d7cc83c336f4b1e79229cc6b99fb0507a Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Mon, 24 Feb 2025 12:14:21 +0400 Subject: [PATCH] feat(all): add support for `get` methods with explicit ID Fixes #357 --- server/src/documentation/documentation.ts | 2 +- .../documentation/contract-functions.test | 54 +++++++++++++++++++ .../testcases/documentation/require.test | 2 +- .../suite/testcases/inlayHints/contract.test | 19 +++++++ server/src/inlays/collect.ts | 4 +- server/src/psi/Decls.ts | 31 ++++++++++- tree-sitter-tact/grammar.js | 7 ++- 7 files changed, 111 insertions(+), 8 deletions(-) diff --git a/server/src/documentation/documentation.ts b/server/src/documentation/documentation.ts index 470df7d0..070fe75c 100644 --- a/server/src/documentation/documentation.ts +++ b/server/src/documentation/documentation.ts @@ -79,7 +79,7 @@ export async function generateDocFor(node: NamedNode, place: SyntaxNode): Promis if (!ownerPresentation) return null // not possible in correct code const actualId = func.computeMethodId() - const actualIdPresentation = `Method ID: \`0x${actualId.toString(16)}\`\n\n` + const actualIdPresentation = `Method ID: \`${actualId}\`\n\n` const idPresentation = func.isGetMethod ? actualIdPresentation : "" diff --git a/server/src/e2e/suite/testcases/documentation/contract-functions.test b/server/src/e2e/suite/testcases/documentation/contract-functions.test index 6945fd67..c55c1554 100644 --- a/server/src/e2e/suite/testcases/documentation/contract-functions.test +++ b/server/src/e2e/suite/testcases/documentation/contract-functions.test @@ -104,3 +104,57 @@ some cool documentation here Called when a binary message of type `Msg` is sent to the contract Learn more in documentation: https://docs.tact-lang.org/book/receive/ + +======================================================================== +get function without method ID +======================================================================== +contract Foo { + get fun foo() {} +} +------------------------------------------------------------------------ +```tact +contract Foo +get fun foo() +``` +Method ID: `0x103fc` + +======================================================================== +get function with method ID +======================================================================== +contract Foo { + get(0x10000) fun foo() {} +} +------------------------------------------------------------------------ +```tact +contract Foo +get(0x10000) fun foo() +``` +Method ID: `0x10000` + +======================================================================== +get function with complex method ID +======================================================================== +contract Foo { + get(0x10000 + 10 * 10) fun foo() {} +} +------------------------------------------------------------------------ +```tact +contract Foo +get(0x10000 + 10 * 10) fun foo() +``` +Method ID: `0x10000 + 10 * 10` + +======================================================================== +get function with constant method ID +======================================================================== +const METHOD_ID: Int = 100; + +contract Foo { + get(METHOD_ID) fun foo() {} +} +------------------------------------------------------------------------ +```tact +contract Foo +get(METHOD_ID) fun foo() +``` +Method ID: `METHOD_ID` diff --git a/server/src/e2e/suite/testcases/documentation/require.test b/server/src/e2e/suite/testcases/documentation/require.test index 2e464398..61bd6839 100644 --- a/server/src/e2e/suite/testcases/documentation/require.test +++ b/server/src/e2e/suite/testcases/documentation/require.test @@ -19,7 +19,7 @@ Checks the condition and throws an error with an exit code generated from the er The generated exit code is guaranteed to be outside the common 0−255 range reserved for TVM and Tact contract errors, which makes it possible to distinguish exit codes from `require()` and any other standard exit codes. - + ```tact fun examples() { // now() has to return a value greater than 1000, diff --git a/server/src/e2e/suite/testcases/inlayHints/contract.test b/server/src/e2e/suite/testcases/inlayHints/contract.test index 097a5558..9c4a9dc1 100644 --- a/server/src/e2e/suite/testcases/inlayHints/contract.test +++ b/server/src/e2e/suite/testcases/inlayHints/contract.test @@ -53,6 +53,25 @@ contract Foo { } } +======================================================================== +Don't show method ID for method with explicit ID +======================================================================== +primitive Int; + +contract Foo { + get(0x1000) fun bar(){ + const a: Int; + } +} +------------------------------------------------------------------------ +primitive Int; + +contract Foo { + get(0x1000) fun bar(){ + const a: Int; + } +} + ======================================================================== Exit codes ======================================================================== diff --git a/server/src/inlays/collect.ts b/server/src/inlays/collect.ts index ea27794e..d30cc82f 100644 --- a/server/src/inlays/collect.ts +++ b/server/src/inlays/collect.ts @@ -460,16 +460,16 @@ export function collect( if (type === "storage_function" && hints.showMethodId) { const func = new Fun(n, file) if (!func.isGetMethod) return true + if (func.hasExplicitMethodId) return true const modifiers = n.childForFieldName("attributes") if (!modifiers) return true const actualId = func.computeMethodId() - const actualIdHex = actualId.toString(16) result.push({ kind: InlayHintKind.Type, - label: `(0x${actualIdHex})`, + label: `(${actualId})`, position: { line: modifiers.endPosition.row, character: modifiers.endPosition.column, diff --git a/server/src/psi/Decls.ts b/server/src/psi/Decls.ts index ee15395a..90a33293 100644 --- a/server/src/psi/Decls.ts +++ b/server/src/psi/Decls.ts @@ -261,6 +261,28 @@ export class Fun extends NamedNode { return this.modifiers().includes("get") } + public get hasExplicitMethodId(): boolean { + // check for + // get(0x1000) fun foo() {} + // ^^^^^^^^ this + const attributes = this.node.childForFieldName("attributes") + if (!attributes) return false + const getAttrs = attributes.children.find(attr => attr?.type === "get_attribute") + if (!getAttrs) return false + return getAttrs.children.some(it => it?.text === "(") + } + + public get getExplicitMethodId(): SyntaxNode | null { + // find + // get(0x1000) fun foo() {} + // ^^^^^^ this + const attributes = this.node.childForFieldName("attributes") + if (!attributes) return null + const getAttrs = attributes.children.find(attr => attr?.type === "get_attribute") + if (!getAttrs) return null + return getAttrs.childForFieldName("value") + } + public returnType(): Expression | null { const result = this.node.childForFieldName("result") if (!result) return null @@ -363,8 +385,13 @@ export class Fun extends NamedNode { return attr } - public computeMethodId(): number { - return (crc16(Buffer.from(this.name())) & 0xff_ff) | 0x1_00_00 + public computeMethodId(): string { + const explicitId = this.getExplicitMethodId + if (explicitId) { + return explicitId.text + } + + return "0x" + ((crc16(Buffer.from(this.name())) & 0xff_ff) | 0x1_00_00).toString(16) } public computeGasConsumption(gas: {loopGasCoefficient: number}): GasConsumption { diff --git a/tree-sitter-tact/grammar.js b/tree-sitter-tact/grammar.js index 73da2534..03a5d7f4 100644 --- a/tree-sitter-tact/grammar.js +++ b/tree-sitter-tact/grammar.js @@ -341,10 +341,10 @@ module.exports = grammar({ field("body", alias($.block_statement, $.function_body)), ), - function_attributes: (_) => + function_attributes: ($) => repeat1( choice( - "get", + $.get_attribute, "mutates", "extends", "virtual", @@ -354,6 +354,9 @@ module.exports = grammar({ ), ), + get_attribute: ($) => + seq("get", optional(seq("(", field("value", $._expression), ")"))), + parameter_list: ($) => seq("(", commaSepWithTrailing($.parameter), ")"), parameter: ($) =>