diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72d984b5f..3ab56004a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New CSpell dictionaries: TVM instructions and adjusted list of Fift words: PR [#881](https://github.com/tact-lang/tact/pull/881)
- Docs: the `description` property to the frontmatter of the each page for better SEO: PR [#916](https://github.com/tact-lang/tact/pull/916)
- Docs: Google Analytics tags per every page: PR [#921](https://github.com/tact-lang/tact/pull/921)
+- Ability to specify a compile-time method ID expression for getters: PR [#922](https://github.com/tact-lang/tact/pull/922)
### Changed
diff --git a/docs/src/content/docs/book/functions.mdx b/docs/src/content/docs/book/functions.mdx
index ab96a9a98..1a29b2be9 100644
--- a/docs/src/content/docs/book/functions.mdx
+++ b/docs/src/content/docs/book/functions.mdx
@@ -3,6 +3,8 @@ title: Functions
description: "Global, asm, native functions, as well as receivers, getters and storage functions, plus the many attributes that allow for great flexibility and expressivity of Tact language"
---
+import { Badge } from '@astrojs/starlight/components';
+
Functions in Tact could be defined in different ways:
* Global static function
@@ -128,6 +130,7 @@ contract Treasure {
## Getter Functions
Getter functions define getters on smart contracts and can be defined only within a contract or trait.
+Getter functions cannot be used to read some other contract's state: if you need to obtain some data you need to do that by sending a message with a request and define a receiver which would process the request answer.
```tact
contract Treasure {
@@ -136,3 +139,37 @@ contract Treasure {
}
}
```
+
+### Explicit resolution of method ID collisions
+
+
+
+As other functions in TVM contracts, getters have their *unique* associated function selectors which are some integers ids (called *method IDs*).
+Some of those integers are reserved for internal purposes, e.g. -4, -3, -2, -1, 0 are reserved IDs and
+regular functions (internal to a contract and not callable from outside) are usually numbered by subsequent (small) integers starting from 1.
+By default, getters have associated method IDs that are derived from their names using the [CRC16](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) algorithm as follows:
+`crc16() & 0xffff) | 0x10000`.
+Sometimes this can get you the same method ID for getters with different names.
+If this happens, you can either rename some of the contract's getters or
+specify the getter's method ID manually as a compile-time expression like so:
+
+```tact
+contract ManualMethodId {
+ const methodId: Int = 16384 + 42;
+
+ get(self.methodId) fun methodId1(): Int {
+ return self.methodId;
+ }
+
+ get(crc32("crc32") + 42 & 0x3ffff | 0x4000)
+ fun methodId2(): Int {
+ return 0;
+ }
+}
+```
+
+Note that you *cannot* use method IDs that are reserved by TVM and you cannot use some initial positive integers because those will be used as function selectors by the compiler.
+
+User-specified method IDs are 19-bit signed integers, so you can use integers from $-2^{18}$ to $-5$ and from $2^{14}$ to $2^{18} - 1$.
+
+Also, a few method IDs are reserved for the usage by the getters the Tact compiler can insert during compilation, those are 113617, 115390, 121275.
diff --git a/knip.json b/knip.json
index d268afa29..b6828108d 100644
--- a/knip.json
+++ b/knip.json
@@ -7,9 +7,5 @@
"src/prettyPrinter.ts",
".github/workflows/tact*.yml"
],
- "ignoreDependencies": [
- "@tact-lang/ton-abi",
- "@tact-lang/ton-jest",
- "@types/jest"
- ]
+ "ignoreDependencies": ["@tact-lang/ton-abi"]
}
diff --git a/src/bindings/writeTypescript.ts b/src/bindings/writeTypescript.ts
index e2b5c6f7c..4f06b19f6 100644
--- a/src/bindings/writeTypescript.ts
+++ b/src/bindings/writeTypescript.ts
@@ -600,9 +600,19 @@ export function writeTypescript(
writeArgumentToStack(a.name, a.type, w);
}
}
- w.append(
- `let source = (await provider.get('${g.name}', builder.build())).stack;`,
- );
+ if (g.methodId) {
+ // 'as any' is used because Sandbox contracts's getters can be called
+ // using the function name or the method id number
+ // but the ContractProvider's interface get methods can only
+ // take strings (function names)
+ w.append(
+ `let source = (await provider.get(${g.methodId} as any, builder.build())).stack;`,
+ );
+ } else {
+ w.append(
+ `let source = (await provider.get('${g.name}', builder.build())).stack;`,
+ );
+ }
if (g.returnType) {
writeGetParser("result", g.returnType, w);
w.append(`return result;`);
diff --git a/src/generator/createABI.ts b/src/generator/createABI.ts
index 45f6ae6e1..503edfa1c 100644
--- a/src/generator/createABI.ts
+++ b/src/generator/createABI.ts
@@ -123,6 +123,7 @@ export function createABI(ctx: CompilerContext, name: string): ContractABI {
if (f.isGetter) {
getters.push({
name: f.name,
+ methodId: f.methodId,
arguments: f.params.map((v) => ({
name: idText(v.name),
type: createABITypeRefFromTypeRef(ctx, v.type, v.loc),
diff --git a/src/generator/writers/writeFunction.ts b/src/generator/writers/writeFunction.ts
index 79cccaa43..980a57f9c 100644
--- a/src/generator/writers/writeFunction.ts
+++ b/src/generator/writers/writeFunction.ts
@@ -12,7 +12,6 @@ import {
import { getType, resolveTypeRef } from "../../types/resolveDescriptors";
import { getExpType } from "../../types/resolveExpression";
import { FunctionDescription, TypeRef } from "../../types/types";
-import { getMethodId } from "../../utils/utils";
import { WriterContext } from "../Writer";
import { resolveFuncPrimitive } from "./resolveFuncPrimitive";
import { resolveFuncType } from "./resolveFuncType";
@@ -681,45 +680,45 @@ function writeNonMutatingFunction(
});
}
-export function writeGetter(f: FunctionDescription, ctx: WriterContext) {
+export function writeGetter(f: FunctionDescription, wCtx: WriterContext) {
// Render tensors
- const self = f.self?.kind === "ref" ? getType(ctx.ctx, f.self.name) : null;
+ const self = f.self?.kind === "ref" ? getType(wCtx.ctx, f.self.name) : null;
if (!self) {
throw new Error(`No self type for getter ${idTextErr(f.name)}`); // Impossible
}
- ctx.append(
- `_ %${f.name}(${f.params.map((v) => resolveFuncTupleType(v.type, ctx) + " " + funcIdOf(v.name)).join(", ")}) method_id(${getMethodId(f.name)}) {`,
+ wCtx.append(
+ `_ %${f.name}(${f.params.map((v) => resolveFuncTupleType(v.type, wCtx) + " " + funcIdOf(v.name)).join(", ")}) method_id(${f.methodId!}) {`,
);
- ctx.inIndent(() => {
+ wCtx.inIndent(() => {
// Unpack parameters
for (const param of f.params) {
unwrapExternal(
funcIdOf(param.name),
funcIdOf(param.name),
param.type,
- ctx,
+ wCtx,
);
}
// Load contract state
- ctx.append(`var self = ${ops.contractLoad(self.name, ctx)}();`);
+ wCtx.append(`var self = ${ops.contractLoad(self.name, wCtx)}();`);
// Execute get method
- ctx.append(
- `var res = self~${ctx.used(ops.extension(self.name, f.name))}(${f.params.map((v) => funcIdOf(v.name)).join(", ")});`,
+ wCtx.append(
+ `var res = self~${wCtx.used(ops.extension(self.name, f.name))}(${f.params.map((v) => funcIdOf(v.name)).join(", ")});`,
);
// Pack if needed
if (f.returns.kind === "ref") {
- const t = getType(ctx.ctx, f.returns.name);
+ const t = getType(wCtx.ctx, f.returns.name);
if (t.kind === "struct" || t.kind === "contract") {
if (f.returns.optional) {
- ctx.append(
- `return ${ops.typeToOptExternal(t.name, ctx)}(res);`,
+ wCtx.append(
+ `return ${ops.typeToOptExternal(t.name, wCtx)}(res);`,
);
} else {
- ctx.append(
- `return ${ops.typeToExternal(t.name, ctx)}(res);`,
+ wCtx.append(
+ `return ${ops.typeToExternal(t.name, wCtx)}(res);`,
);
}
return;
@@ -727,8 +726,8 @@ export function writeGetter(f: FunctionDescription, ctx: WriterContext) {
}
// Return result
- ctx.append(`return res;`);
+ wCtx.append(`return res;`);
});
- ctx.append(`}`);
- ctx.append();
+ wCtx.append(`}`);
+ wCtx.append();
}
diff --git a/src/grammar/__snapshots__/grammar.spec.ts.snap b/src/grammar/__snapshots__/grammar.spec.ts.snap
index f3d715854..5bfd1e508 100644
--- a/src/grammar/__snapshots__/grammar.spec.ts.snap
+++ b/src/grammar/__snapshots__/grammar.spec.ts.snap
@@ -141,6 +141,17 @@ Line 1, col 20:
"
`;
+exports[`grammar should fail contract-getter-parens-no-method-id 1`] = `
+":2:9: Parse error: expected "\\"", "initOf", "null", "_", "A".."Z", "a".."z", "false", "true", "0", "1".."9", "0O", "0o", "0B", "0b", "0X", "0x", "(", "~", "!", "+", or "-"
+
+Line 2, col 9:
+ 1 | contract Test {
+> 2 | get() fun test(): Int {
+ ^
+ 3 | return 0
+"
+`;
+
exports[`grammar should fail contract-init-trailing-comma-empty-params 1`] = `
"Syntax error: :2:10: Empty parameter list should not have a dangling comma.
Line 2, col 10:
@@ -934,6 +945,133 @@ exports[`grammar should parse case-35 1`] = `
}
`;
+exports[`grammar should parse contract-getter-with-method-id 1`] = `
+{
+ "id": 17,
+ "imports": [],
+ "items": [
+ {
+ "attributes": [],
+ "declarations": [
+ {
+ "attributes": [
+ {
+ "loc": get(crc32("crc32") + 42 & 0x3ffff | 0x4000),
+ "methodId": {
+ "id": 10,
+ "kind": "op_binary",
+ "left": {
+ "id": 8,
+ "kind": "op_binary",
+ "left": {
+ "id": 6,
+ "kind": "op_binary",
+ "left": {
+ "args": [
+ {
+ "id": 3,
+ "kind": "string",
+ "loc": "crc32",
+ "value": "crc32",
+ },
+ ],
+ "function": {
+ "id": 2,
+ "kind": "id",
+ "loc": crc32,
+ "text": "crc32",
+ },
+ "id": 4,
+ "kind": "static_call",
+ "loc": crc32("crc32"),
+ },
+ "loc": crc32("crc32") + 42,
+ "op": "+",
+ "right": {
+ "base": 10,
+ "id": 5,
+ "kind": "number",
+ "loc": 42,
+ "value": 42n,
+ },
+ },
+ "loc": crc32("crc32") + 42 & 0x3ffff,
+ "op": "&",
+ "right": {
+ "base": 16,
+ "id": 7,
+ "kind": "number",
+ "loc": 0x3ffff,
+ "value": 262143n,
+ },
+ },
+ "loc": crc32("crc32") + 42 & 0x3ffff | 0x4000,
+ "op": "|",
+ "right": {
+ "base": 16,
+ "id": 9,
+ "kind": "number",
+ "loc": 0x4000,
+ "value": 16384n,
+ },
+ },
+ "type": "get",
+ },
+ ],
+ "id": 15,
+ "kind": "function_def",
+ "loc": get(crc32("crc32") + 42 & 0x3ffff | 0x4000) fun test(): Int {
+ return 0
+ },
+ "name": {
+ "id": 11,
+ "kind": "id",
+ "loc": test,
+ "text": "test",
+ },
+ "params": [],
+ "return": {
+ "id": 12,
+ "kind": "type_id",
+ "loc": Int,
+ "text": "Int",
+ },
+ "statements": [
+ {
+ "expression": {
+ "base": 10,
+ "id": 13,
+ "kind": "number",
+ "loc": 0,
+ "value": 0n,
+ },
+ "id": 14,
+ "kind": "statement_return",
+ "loc": return 0,
+ },
+ ],
+ },
+ ],
+ "id": 16,
+ "kind": "contract",
+ "loc": contract Test {
+ get(crc32("crc32") + 42 & 0x3ffff | 0x4000) fun test(): Int {
+ return 0
+ }
+},
+ "name": {
+ "id": 1,
+ "kind": "id",
+ "loc": Test,
+ "text": "Test",
+ },
+ "traits": [],
+ },
+ ],
+ "kind": "module",
+}
+`;
+
exports[`grammar should parse contract-optional-semicolon-for-last-const-def 1`] = `
{
"id": 7,
diff --git a/src/grammar/ast.ts b/src/grammar/ast.ts
index c8cdfa6df..b9267b6d1 100644
--- a/src/grammar/ast.ts
+++ b/src/grammar/ast.ts
@@ -633,7 +633,7 @@ export type AstContractAttribute = {
};
export type AstFunctionAttribute =
- | { type: "get"; loc: SrcInfo }
+ | { type: "get"; methodId: AstExpression | null; loc: SrcInfo }
| { type: "mutates"; loc: SrcInfo }
| { type: "extends"; loc: SrcInfo }
| { type: "virtual"; loc: SrcInfo }
diff --git a/src/grammar/grammar.ohm b/src/grammar/grammar.ohm
index 3b8dec037..d4f38897e 100644
--- a/src/grammar/grammar.ohm
+++ b/src/grammar/grammar.ohm
@@ -74,7 +74,8 @@ Tact {
ContractAttribute = "@interface" "(" stringLiteral ")" --interface
- FunctionAttribute = "get" --getter // 'get' cannot be a reserved word because there is the map '.get' method
+ // 'get' cannot be a reserved word because there is the map '.get' method
+ FunctionAttribute = "get" ("(" Expression ")")? --getter
| mutates --mutates
| extends --extends
| virtual --virtual
diff --git a/src/grammar/grammar.ts b/src/grammar/grammar.ts
index ef702b3c3..760d58b13 100644
--- a/src/grammar/grammar.ts
+++ b/src/grammar/grammar.ts
@@ -583,8 +583,12 @@ semantics.addOperation("astOfAsmInstruction", {
});
semantics.addOperation("astOfFunctionAttributes", {
- FunctionAttribute_getter(_) {
- return { type: "get", loc: createRef(this) };
+ FunctionAttribute_getter(_getKwd, _optLparen, optMethodId, _optRparen) {
+ return {
+ type: "get",
+ methodId: unwrapOptNode(optMethodId, (e) => e.astOfExpression()),
+ loc: createRef(this),
+ };
},
FunctionAttribute_extends(_) {
return { type: "extends", loc: createRef(this) };
diff --git a/src/grammar/rename.ts b/src/grammar/rename.ts
index 6d46105b5..7ec7523d0 100644
--- a/src/grammar/rename.ts
+++ b/src/grammar/rename.ts
@@ -15,6 +15,7 @@ import {
AstFunctionDecl,
AstConstantDecl,
AstNode,
+ AstFunctionAttribute,
} from "./ast";
import { dummySrcInfo } from "./grammar";
import { AstSorter } from "./sort";
@@ -266,8 +267,29 @@ export class AstRenamer {
private renameFunctionContents(
functionDef: AstFunctionDef,
): AstFunctionDef {
+ const attributes = this.renameFunctionAttributes(
+ functionDef.attributes,
+ );
const statements = this.renameStatements(functionDef.statements);
- return { ...functionDef, statements };
+ return { ...functionDef, attributes, statements };
+ }
+
+ /**
+ * Renames getter's methodId expression.
+ */
+ private renameFunctionAttributes(
+ functionAttrs: AstFunctionAttribute[],
+ ): AstFunctionAttribute[] {
+ return functionAttrs.map((attr) => {
+ if (attr.type === "get" && attr.methodId !== null) {
+ return {
+ ...attr,
+ methodId: this.renameExpression(attr.methodId),
+ };
+ } else {
+ return attr;
+ }
+ });
}
/**
@@ -403,7 +425,7 @@ export class AstRenamer {
case "id":
return {
...expr,
- text: this.renamed.get(expr.text) ?? expr.text,
+ text: this.givenNames.get(expr.text) ?? expr.text,
};
case "op_binary":
return {
diff --git a/src/grammar/test-failed/contract-getter-parens-no-method-id.tact b/src/grammar/test-failed/contract-getter-parens-no-method-id.tact
new file mode 100644
index 000000000..f3de3915f
--- /dev/null
+++ b/src/grammar/test-failed/contract-getter-parens-no-method-id.tact
@@ -0,0 +1,5 @@
+contract Test {
+ get() fun test(): Int {
+ return 0
+ }
+}
diff --git a/src/grammar/test/contract-getter-with-method-id.tact b/src/grammar/test/contract-getter-with-method-id.tact
new file mode 100644
index 000000000..2026cdaf4
--- /dev/null
+++ b/src/grammar/test/contract-getter-with-method-id.tact
@@ -0,0 +1,5 @@
+contract Test {
+ get(crc32("crc32") + 42 & 0x3ffff | 0x4000) fun test(): Int {
+ return 0
+ }
+}
diff --git a/src/prettyPrinter.ts b/src/prettyPrinter.ts
index b70e69e01..0ac67d4d9 100644
--- a/src/prettyPrinter.ts
+++ b/src/prettyPrinter.ts
@@ -464,12 +464,22 @@ export class PrettyPrinter {
`${this.ppAstId(arg.name)}: ${this.ppAstType(arg.type)}`,
)
.join(", ");
- const attrsRaw = attributes.map((attr) => attr.type).join(" ");
+ const attrsRaw = attributes
+ .map((attr) => this.ppAstFunctionAttribute(attr))
+ .join(" ");
const attrsFormatted = attrsRaw ? `${attrsRaw} ` : "";
const returnType = retTy ? `: ${this.ppAstType(retTy)}` : "";
return `${attrsFormatted}fun ${this.ppAstId(name)}(${argsFormatted})${returnType}`;
}
+ ppAstFunctionAttribute(attr: AstFunctionAttribute): string {
+ if (attr.type === "get" && attr.methodId !== null) {
+ return `get(${this.ppAstExpression(attr.methodId)})`;
+ } else {
+ return attr.type;
+ }
+ }
+
ppAstReceiver(receive: AstReceiver): string {
const header = this.ppAstReceiverHeader(receive);
const stmtsFormatted = this.ppStatementBlock(receive.statements);
diff --git a/src/storage/__snapshots__/resolveAllocation.spec.ts.snap b/src/storage/__snapshots__/resolveAllocation.spec.ts.snap
index 274c24bff..79708c492 100644
--- a/src/storage/__snapshots__/resolveAllocation.spec.ts.snap
+++ b/src/storage/__snapshots__/resolveAllocation.spec.ts.snap
@@ -2258,6 +2258,7 @@ exports[`resolveAllocation should write program 1`] = `
"isMutating": true,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "main",
"origin": "user",
"params": [
diff --git a/src/test/contracts/getter-with-method-id-const.tact b/src/test/contracts/getter-with-method-id-const.tact
new file mode 100644
index 000000000..6780dc499
--- /dev/null
+++ b/src/test/contracts/getter-with-method-id-const.tact
@@ -0,0 +1,7 @@
+const FOO: Int = crc32("crc32") + 42;
+
+contract Test {
+ get(FOO) fun test(): Int {
+ return FOO;
+ }
+}
diff --git a/src/test/contracts/getter-with-method-id.tact b/src/test/contracts/getter-with-method-id.tact
new file mode 100644
index 000000000..9857e558e
--- /dev/null
+++ b/src/test/contracts/getter-with-method-id.tact
@@ -0,0 +1,6 @@
+contract Test {
+ get(crc32("crc32") + 42 & 0x3ffff | 0x4000) fun test(): Int {
+ return 0;
+ }
+}
+
diff --git a/src/test/contracts/renamer-expected/case-4.tact b/src/test/contracts/renamer-expected/case-4.tact
index a6f007b01..28dbc9e1e 100644
--- a/src/test/contracts/renamer-expected/case-4.tact
+++ b/src/test/contracts/renamer-expected/case-4.tact
@@ -37,10 +37,10 @@ contract contract_6 {
fun function_def_8() {
let d: Int? = null;
self.a = 10;
- d = a > 0 ? self.a : 0;
+ d = constant_def_2 > 0 ? self.a : 0;
let res: Bool = isZero(1, 2, false, self.c);
let e = 42;
- self.b = a;
+ self.b = constant_def_2;
self.c = Source{a: 10, b: 20};
}
diff --git a/src/test/contracts/renamer-expected/getter-with-method-id-const.tact b/src/test/contracts/renamer-expected/getter-with-method-id-const.tact
new file mode 100644
index 000000000..e842470d9
--- /dev/null
+++ b/src/test/contracts/renamer-expected/getter-with-method-id-const.tact
@@ -0,0 +1,7 @@
+const constant_def_0: Int = crc32("crc32") + 42;
+
+contract contract_1 {
+ get(constant_def_0) fun function_def_2(): Int {
+ return constant_def_0;
+ }
+}
diff --git a/src/test/contracts/renamer-expected/getter-with-method-id.tact b/src/test/contracts/renamer-expected/getter-with-method-id.tact
new file mode 100644
index 000000000..2d8e63ff3
--- /dev/null
+++ b/src/test/contracts/renamer-expected/getter-with-method-id.tact
@@ -0,0 +1,5 @@
+contract contract_0 {
+ get(crc32("crc32") + 42 & 0x3ffff | 0x4000) fun function_def_1(): Int {
+ return 0;
+ }
+}
diff --git a/src/test/e2e-emulated/__snapshots__/local-type-inference.spec.ts.snap b/src/test/e2e-emulated/__snapshots__/local-type-inference.spec.ts.snap
index 9d3c1e7d9..33a4b617f 100644
--- a/src/test/e2e-emulated/__snapshots__/local-type-inference.spec.ts.snap
+++ b/src/test/e2e-emulated/__snapshots__/local-type-inference.spec.ts.snap
@@ -115,6 +115,7 @@ exports[`local-type-inference should automatically set types for let statements
"getters": [
{
"arguments": [],
+ "methodId": 70304,
"name": "test1",
"returnType": {
"format": 257,
@@ -125,6 +126,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 74435,
"name": "test2",
"returnType": {
"format": 257,
@@ -135,6 +137,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 78562,
"name": "test3",
"returnType": {
"kind": "simple",
@@ -144,6 +147,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 82437,
"name": "test4",
"returnType": {
"kind": "simple",
@@ -153,6 +157,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 86564,
"name": "test5",
"returnType": {
"kind": "simple",
@@ -162,6 +167,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 90695,
"name": "test6",
"returnType": {
"kind": "simple",
@@ -171,6 +177,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 94822,
"name": "test7",
"returnType": {
"kind": "simple",
@@ -180,6 +187,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 99209,
"name": "test8",
"returnType": {
"kind": "simple",
@@ -189,6 +197,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 103336,
"name": "test9",
"returnType": {
"kind": "simple",
@@ -198,6 +207,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 107552,
"name": "test10",
"returnType": {
"kind": "simple",
@@ -207,6 +217,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 111617,
"name": "test11",
"returnType": {
"kind": "simple",
@@ -216,6 +227,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 99426,
"name": "test12",
"returnType": {
"key": "int",
@@ -225,6 +237,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 103491,
"name": "test13",
"returnType": {
"key": "int",
@@ -235,6 +248,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 124068,
"name": "test14",
"returnType": {
"kind": "simple",
@@ -244,6 +258,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 128133,
"name": "test15",
"returnType": {
"kind": "simple",
@@ -253,6 +268,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 115942,
"name": "test16",
"returnType": {
"format": 257,
@@ -263,6 +279,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 120007,
"name": "test17",
"returnType": {
"format": 257,
@@ -273,6 +290,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 75048,
"name": "test18",
"returnType": {
"format": 257,
@@ -283,6 +301,7 @@ exports[`local-type-inference should automatically set types for let statements
},
{
"arguments": [],
+ "methodId": 79113,
"name": "test19",
"returnType": {
"format": 257,
diff --git a/src/test/e2e-emulated/contracts/getters.tact b/src/test/e2e-emulated/contracts/getters.tact
index bbc89b550..18e5d5026 100644
--- a/src/test/e2e-emulated/contracts/getters.tact
+++ b/src/test/e2e-emulated/contracts/getters.tact
@@ -10,6 +10,8 @@ message SetIdAndData {
data: Cell;
}
+const METHOD_ID: Int = 16384;
+
contract Test with Deployable {
id: Int as uint32 = 0;
anotherData: Cell;
@@ -54,4 +56,19 @@ contract Test with Deployable {
get fun contractAsInput(test: Test): Test {
return test;
}
+
+ // method ids are 19 bit signed integers
+ // so we truncate the result with a (18-bit) bit mask
+ get((crc32("methodId") + 42) & 0b111111111111111111) fun methodIdExpr(): Bool {
+ return true;
+ }
+ get(METHOD_ID) fun methodIdConst(): Int {
+ return METHOD_ID;
+ }
+ get(-pow2(18)) fun methodIdMin(): Bool {
+ return true;
+ }
+ get(pow2(18) - 1) fun methodIdMax(): Bool {
+ return true;
+ }
}
\ No newline at end of file
diff --git a/src/test/e2e-emulated/getters.spec.ts b/src/test/e2e-emulated/getters.spec.ts
index 8719f19cf..341307ed4 100644
--- a/src/test/e2e-emulated/getters.spec.ts
+++ b/src/test/e2e-emulated/getters.spec.ts
@@ -78,5 +78,10 @@ describe("getters", () => {
anotherData: beginCell().storeUint(123, 64).endCell(),
}),
).toMatchSnapshot();
+
+ expect(await contract.getMethodIdExpr()).toBe(true);
+ expect(await contract.getMethodIdConst()).toBe(2n ** 14n);
+ expect(await contract.getMethodIdMin()).toBe(true);
+ expect(await contract.getMethodIdMax()).toBe(true);
});
});
diff --git a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap
index dd0d9ca5d..8437d6927 100644
--- a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap
+++ b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap
@@ -328,39 +328,6 @@ Line 4, col 17:
"
`;
-exports[`resolveDescriptors should fail descriptors for getter-collision 1`] = `
-":8:13: Method ID collision: getter 'pko' has the same method ID 103057 as getter 'getter1'
-Pick a different getter name to avoid collisions
-Line 8, col 13:
- 7 |
-> 8 | get fun pko() {
- ^~~
- 9 | return;
-"
-`;
-
-exports[`resolveDescriptors should fail descriptors for getter-collision-trait 1`] = `
-":8:13: Method ID collision: getter 'pko' has the same method ID 103057 as getter 'getter1'
-Pick a different getter name to avoid collisions
-Line 8, col 13:
- 7 |
-> 8 | get fun pko() {
- ^~~
- 9 | return;
-"
-`;
-
-exports[`resolveDescriptors should fail descriptors for getter-collision-with-trait 1`] = `
-":4:13: Method ID collision: getter 'pko' has the same method ID 103057 as getter 'getter1'
-Pick a different getter name to avoid collisions
-Line 4, col 13:
- 3 | trait T {
-> 4 | get fun pko() {
- ^~~
- 5 | return;
-"
-`;
-
exports[`resolveDescriptors should fail descriptors for getter-outside-contract 1`] = `
":8:1: Getters must be defined within a contract
Line 8, col 1:
@@ -761,6 +728,7 @@ exports[`resolveDescriptors should resolve descriptors for asm-extends-fun 1`] =
"isMutating": true,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "loadRef",
"origin": "user",
"params": [],
@@ -920,6 +888,7 @@ exports[`resolveDescriptors should resolve descriptors for asm-fun-no-arg-shuffl
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "foo",
"origin": "user",
"params": [
@@ -1064,6 +1033,7 @@ exports[`resolveDescriptors should resolve descriptors for asm-fun-no-ret-shuffl
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "foo",
"origin": "user",
"params": [
@@ -1188,6 +1158,7 @@ exports[`resolveDescriptors should resolve descriptors for asm-fun-no-shuffle 2`
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "foo",
"origin": "user",
"params": [
@@ -1485,6 +1456,7 @@ exports[`resolveDescriptors should resolve descriptors for asm-fun-shuffle-pair
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "foo",
"origin": "user",
"params": [
@@ -4428,6 +4400,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
},
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -4475,6 +4448,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
},
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -4501,6 +4475,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
"isMutating": true,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "getter",
"origin": "user",
"params": [],
@@ -4574,6 +4549,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
},
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -4642,6 +4618,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
},
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -4682,6 +4659,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
"isMutating": true,
"isOverride": true,
"isVirtual": false,
+ "methodId": null,
"name": "getter",
"origin": "user",
"params": [],
@@ -4763,6 +4741,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
},
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -4810,6 +4789,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
},
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -4836,6 +4816,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
"isMutating": true,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "getter",
"origin": "user",
"params": [],
@@ -4980,6 +4961,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
},
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -5041,6 +5023,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
},
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -5081,6 +5064,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
"isMutating": true,
"isOverride": false,
"isVirtual": true,
+ "methodId": null,
"name": "getter",
"origin": "user",
"params": [],
@@ -5154,6 +5138,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
},
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -5222,6 +5207,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
},
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -5262,6 +5248,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
"isMutating": true,
"isOverride": true,
"isVirtual": false,
+ "methodId": null,
"name": "getter",
"origin": "user",
"params": [],
@@ -5343,6 +5330,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
},
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -5404,6 +5392,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
},
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -5444,6 +5433,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr
"isMutating": true,
"isOverride": false,
"isVirtual": true,
+ "methodId": null,
"name": "getter",
"origin": "user",
"params": [],
@@ -5618,6 +5608,7 @@ exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "test_extends",
"origin": "user",
"params": [
@@ -5734,6 +5725,7 @@ exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "test_extends_self",
"origin": "user",
"params": [
@@ -5854,6 +5846,7 @@ exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self
"isMutating": true,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "test_mutates",
"origin": "user",
"params": [
@@ -5974,6 +5967,7 @@ exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self
"isMutating": true,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "test_mutates_self",
"origin": "user",
"params": [
@@ -6097,6 +6091,7 @@ exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self
"isMutating": true,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "test_mutates_self_opt",
"origin": "user",
"params": [
@@ -6227,6 +6222,7 @@ exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "test",
"origin": "user",
"params": [
@@ -6583,6 +6579,7 @@ exports[`resolveDescriptors should resolve descriptors for init-vars-analysis-un
"isMutating": true,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "hello",
"origin": "user",
"params": [],
@@ -6619,6 +6616,7 @@ exports[`resolveDescriptors should resolve descriptors for init-vars-analysis-un
"isMutating": true,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "hello2",
"origin": "user",
"params": [],
@@ -6837,6 +6835,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "testFunc",
"origin": "user",
"params": [],
@@ -6884,6 +6883,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "testFunc2",
"origin": "user",
"params": [],
@@ -6964,6 +6964,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "testFunc3",
"origin": "user",
"params": [
@@ -7274,6 +7275,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors
"attributes": [
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -7314,6 +7316,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors
"attributes": [
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -7424,6 +7427,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors
"attributes": [
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -7466,6 +7470,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors
"isMutating": true,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "hello",
"origin": "user",
"params": [],
@@ -7485,6 +7490,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors
"attributes": [
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -7527,6 +7533,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors
"isMutating": true,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "hello2",
"origin": "user",
"params": [],
@@ -7764,6 +7771,7 @@ exports[`resolveDescriptors should resolve descriptors for item-method 1`] = `
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "inc",
"origin": "user",
"params": [],
@@ -8010,6 +8018,7 @@ native sample(a: Int): Int;,
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "sample",
"origin": "user",
"params": [
@@ -8116,6 +8125,7 @@ mutates extends native inc(self: Int): Int;,
"isMutating": true,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "inc",
"origin": "user",
"params": [],
@@ -8322,6 +8332,7 @@ exports[`resolveDescriptors should resolve descriptors for map-value-as-coins 1`
"attributes": [
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -8649,6 +8660,7 @@ exports[`resolveDescriptors should resolve descriptors for map-value-as-coins 1`
"attributes": [
{
"loc": get,
+ "methodId": null,
"type": "get",
},
],
@@ -8890,6 +8902,7 @@ exports[`resolveDescriptors should resolve descriptors for map-value-as-coins 1`
"isMutating": true,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "test",
"origin": "user",
"params": [],
@@ -9070,6 +9083,7 @@ exports[`resolveDescriptors should resolve descriptors for scope-loops 2`] = `
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "scopeUntil",
"origin": "user",
"params": [],
@@ -9169,6 +9183,7 @@ exports[`resolveDescriptors should resolve descriptors for scope-loops 2`] = `
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "scopeRepeat",
"origin": "user",
"params": [],
@@ -9267,6 +9282,7 @@ exports[`resolveDescriptors should resolve descriptors for scope-loops 2`] = `
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "scopeWhile",
"origin": "user",
"params": [],
@@ -9367,6 +9383,7 @@ exports[`resolveDescriptors should resolve descriptors for scope-loops 2`] = `
"isMutating": false,
"isOverride": false,
"isVirtual": false,
+ "methodId": null,
"name": "scopeIf",
"origin": "user",
"params": [],
diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap
index 6e92c641e..f83f3b84b 100644
--- a/src/types/__snapshots__/resolveStatements.spec.ts.snap
+++ b/src/types/__snapshots__/resolveStatements.spec.ts.snap
@@ -90,6 +90,26 @@ Line 23, col 22:
"
`;
+exports[`resolveStatements should fail statements for contract-getter-with-method-id-1 1`] = `
+":5:9: Invalid type "String" for binary operator "+"
+Line 5, col 9:
+ 4 | contract Test {
+> 5 | get(crc32("crc32") + "42") fun test(): Int {
+ ^~~~~~~~~~~~~~~~~~~~~
+ 6 | return 0
+"
+`;
+
+exports[`resolveStatements should fail statements for contract-getter-with-method-id-2 1`] = `
+":5:9: Getter's method id expression must be of type "Int" but it has type "Bool"
+Line 5, col 9:
+ 4 | contract Test {
+> 5 | get(true) fun test(): Int {
+ ^~~~
+ 6 | return 0
+"
+`;
+
exports[`resolveStatements should fail statements for contract-initof-wrong-arg 1`] = `
":26:25: Invalid type "String" for argument "owner"
Line 26, col 25:
@@ -535,6 +555,39 @@ Line 13, col 12:
"
`;
+exports[`resolveStatements should fail statements for getter-collision 1`] = `
+":8:13: Method ID collision: getter 'pko' has the same method ID 103057 as getter 'getter1'
+Pick a different getter name or explicit method ID to avoid collisions
+Line 8, col 13:
+ 7 |
+> 8 | get fun pko() {
+ ^~~
+ 9 | return;
+"
+`;
+
+exports[`resolveStatements should fail statements for getter-collision-trait 1`] = `
+":8:13: Method ID collision: getter 'pko' has the same method ID 103057 as getter 'getter1'
+Pick a different getter name or explicit method ID to avoid collisions
+Line 8, col 13:
+ 7 |
+> 8 | get fun pko() {
+ ^~~
+ 9 | return;
+"
+`;
+
+exports[`resolveStatements should fail statements for getter-collision-with-trait 1`] = `
+":4:13: Method ID collision: getter 'pko' has the same method ID 103057 as getter 'getter1'
+Pick a different getter name or explicit method ID to avoid collisions
+Line 4, col 13:
+ 3 | trait T {
+> 4 | get fun pko() {
+ ^~~
+ 5 | return;
+"
+`;
+
exports[`resolveStatements should fail statements for init-vars-analysis-if 1`] = `
":10:5: Field "value" is not set
Line 10, col 5:
@@ -1580,6 +1633,53 @@ exports[`resolveStatements should resolve statements for assign-self-mutating-me
]
`;
+exports[`resolveStatements should resolve statements for contract-getter-with-method-id-1 1`] = `
+[
+ [
+ "0",
+ "Int",
+ ],
+]
+`;
+
+exports[`resolveStatements should resolve statements for contract-getter-with-method-id-2 1`] = `
+[
+ [
+ "16384",
+ "Int",
+ ],
+ [
+ "MethodId",
+ "Int",
+ ],
+]
+`;
+
+exports[`resolveStatements should resolve statements for contract-getter-with-method-id-3 1`] = `
+[
+ [
+ "16384",
+ "Int",
+ ],
+ [
+ "41",
+ "Int",
+ ],
+ [
+ "16384 + 41",
+ "Int",
+ ],
+ [
+ "self",
+ "ManualMethodId",
+ ],
+ [
+ "self.methodId",
+ "Int",
+ ],
+]
+`;
+
exports[`resolveStatements should resolve statements for contract-receiver-bounced 1`] = `
[
[
diff --git a/src/types/resolveDescriptors.ts b/src/types/resolveDescriptors.ts
index 1247ba6be..f6eaafa3e 100644
--- a/src/types/resolveDescriptors.ts
+++ b/src/types/resolveDescriptors.ts
@@ -52,7 +52,6 @@ import { ItemOrigin } from "../grammar/grammar";
import { getExpType, resolveExpression } from "./resolveExpression";
import { emptyContext } from "./resolveStatements";
import { isAssignable } from "./subtyping";
-import { getMethodId } from "../utils/utils";
const store = createContextStore();
const staticFunctionsStore = createContextStore();
@@ -1003,6 +1002,7 @@ export function resolveDescriptors(ctx: CompilerContext) {
isOverride: !!isOverride,
isInline: !!isInline,
isAbstract: !!isAbstract,
+ methodId: null,
};
}
@@ -1947,25 +1947,6 @@ export function resolveDescriptors(ctx: CompilerContext) {
staticConstants.set(idText(a.name), buildConstantDescription(a));
}
- // Check for collisions in getter method IDs
- for (const t of types.values()) {
- const methodIds: Map = new Map();
- for (const f of t.functions.values()) {
- if (f.isGetter) {
- const methodId = getMethodId(f.name);
- const existing = methodIds.get(methodId);
- if (existing) {
- throwCompilationError(
- `Method ID collision: getter '${f.name}' has the same method ID ${methodId} as getter '${existing}'\nPick a different getter name to avoid collisions`,
- f.ast.name.loc,
- );
- } else {
- methodIds.set(methodId, f.name);
- }
- }
- }
- }
-
//
// Register types and functions in context
//
diff --git a/src/types/resolveStatements.ts b/src/types/resolveStatements.ts
index 91ff414dd..81ce982e9 100644
--- a/src/types/resolveStatements.ts
+++ b/src/types/resolveStatements.ts
@@ -15,6 +15,7 @@ import { isAssignable } from "./subtyping";
import {
idTextErr,
throwCompilationError,
+ throwConstEvalError,
throwInternalCompilerError,
} from "../errors";
import {
@@ -26,7 +27,10 @@ import {
getAllTypes,
} from "./resolveDescriptors";
import { getExpType, resolveExpression } from "./resolveExpression";
-import { printTypeRef, TypeRef } from "./types";
+import { FunctionDescription, printTypeRef, TypeRef } from "./types";
+import { evalConstantExpression } from "../constEval";
+import { ensureInt } from "../interpreter";
+import { crc16 } from "../utils/crc16";
export type StatementContext = {
root: SrcInfo;
@@ -856,6 +860,7 @@ export function resolveStatements(ctx: CompilerContext) {
}
// Process functions
+ const methodIds: Map = new Map();
for (const f of t.functions.values()) {
if (
f.ast.kind !== "native_function_decl" &&
@@ -864,12 +869,29 @@ export function resolveStatements(ctx: CompilerContext) {
) {
// Build statement context
let sctx = emptyContext(f.ast.loc, f.name, f.returns);
+
if (f.self === null) {
throwInternalCompilerError(
"Self is null where it should not be",
);
}
sctx = addVariable(selfId, f.self, ctx, sctx);
+
+ // Check for collisions in getter method IDs
+ if (f.isGetter) {
+ const methodId = getMethodId(f, ctx, sctx);
+ const existing = methodIds.get(methodId);
+ if (existing) {
+ throwCompilationError(
+ `Method ID collision: getter '${f.name}' has the same method ID ${methodId} as getter '${existing}'\nPick a different getter name or explicit method ID to avoid collisions`,
+ f.ast.name.loc,
+ );
+ } else {
+ f.methodId = methodId;
+ methodIds.set(methodId, f.name);
+ }
+ }
+
for (const a of f.params) {
sctx = addVariable(a.name, a.type, ctx, sctx);
}
@@ -881,3 +903,63 @@ export function resolveStatements(ctx: CompilerContext) {
return ctx;
}
+
+function getMethodId(
+ funcDescr: FunctionDescription,
+ ctx: CompilerContext,
+ sctx: StatementContext,
+): number {
+ const optMethodId = funcDescr.ast.attributes.find(
+ (attr) => attr.type === "get",
+ )?.methodId;
+
+ if (optMethodId) {
+ ctx = resolveExpression(optMethodId, sctx, ctx);
+ const ty = getExpType(ctx, optMethodId);
+ if (!(ty.kind === "ref" && ty.name === "Int")) {
+ throwCompilationError(
+ `Getter's method id expression must be of type "Int" but it has type "${printTypeRef(ty)}"`,
+ optMethodId.loc,
+ );
+ }
+
+ const methodId = ensureInt(
+ evalConstantExpression(optMethodId, ctx),
+ optMethodId.loc,
+ );
+ // method ids are 19-bit signed integers
+ if (methodId < -(2n ** 18n) || methodId >= 2n ** 18n) {
+ throwConstEvalError(
+ "method ids must fit 19-bit signed integer range",
+ true,
+ optMethodId!.loc,
+ );
+ }
+ // method ids -4, -3, -2, -1, 0 ... 2^14 - 1 (inclusive) are kind of reserved by TVM
+ // for the upper bound see F12_n (CALL) TVM instruction
+ // and many small ids will be taken by internal procedures
+ //
+ // also, some ids are taken by the getters generated by Tact:
+ // supported_interfaces -> 113617
+ // lazy_deployment_completed -> 115390
+ // get_abi_ipfs -> 121275
+ if (-4n <= methodId && methodId < 2n ** 14n) {
+ throwConstEvalError(
+ "method ids cannot overlap with the TVM reserved ids: -4, -3, -2, -1, 0 ... 2^14 - 1",
+ true,
+ optMethodId!.loc,
+ );
+ }
+ const tactGeneratedGetterMethodIds = [113617n, 115390n, 121275n];
+ if (tactGeneratedGetterMethodIds.includes(methodId)) {
+ throwConstEvalError(
+ `method ids cannot overlap with Tact reserved method ids: ${tactGeneratedGetterMethodIds.map((n) => n.toString()).join(", ")}`,
+ true,
+ optMethodId!.loc,
+ );
+ }
+ return Number(methodId);
+ } else {
+ return (crc16(funcDescr.name) & 0xffff) | 0x10000;
+ }
+}
diff --git a/src/types/stmts-failed/contract-getter-with-method-id-1.tact b/src/types/stmts-failed/contract-getter-with-method-id-1.tact
new file mode 100644
index 000000000..6a91191b2
--- /dev/null
+++ b/src/types/stmts-failed/contract-getter-with-method-id-1.tact
@@ -0,0 +1,8 @@
+primitive Int;
+trait BaseTrait { }
+
+contract Test {
+ get(crc32("crc32") + "42") fun test(): Int {
+ return 0
+ }
+}
diff --git a/src/types/stmts-failed/contract-getter-with-method-id-2.tact b/src/types/stmts-failed/contract-getter-with-method-id-2.tact
new file mode 100644
index 000000000..0d9de630c
--- /dev/null
+++ b/src/types/stmts-failed/contract-getter-with-method-id-2.tact
@@ -0,0 +1,8 @@
+primitive Int;
+trait BaseTrait { }
+
+contract Test {
+ get(true) fun test(): Int {
+ return 0
+ }
+}
diff --git a/src/types/test-failed/getter-collision-trait.tact b/src/types/stmts-failed/getter-collision-trait.tact
similarity index 100%
rename from src/types/test-failed/getter-collision-trait.tact
rename to src/types/stmts-failed/getter-collision-trait.tact
diff --git a/src/types/test-failed/getter-collision-with-trait.tact b/src/types/stmts-failed/getter-collision-with-trait.tact
similarity index 100%
rename from src/types/test-failed/getter-collision-with-trait.tact
rename to src/types/stmts-failed/getter-collision-with-trait.tact
diff --git a/src/types/test-failed/getter-collision.tact b/src/types/stmts-failed/getter-collision.tact
similarity index 100%
rename from src/types/test-failed/getter-collision.tact
rename to src/types/stmts-failed/getter-collision.tact
diff --git a/src/types/stmts/contract-getter-with-method-id-1.tact b/src/types/stmts/contract-getter-with-method-id-1.tact
new file mode 100644
index 000000000..d66b5bdc7
--- /dev/null
+++ b/src/types/stmts/contract-getter-with-method-id-1.tact
@@ -0,0 +1,8 @@
+primitive Int;
+trait BaseTrait { }
+
+contract Test {
+ get((crc32("crc32") + 42) & 0x3FFFF | 0x4000) fun test(): Int {
+ return 0
+ }
+}
diff --git a/src/types/stmts/contract-getter-with-method-id-2.tact b/src/types/stmts/contract-getter-with-method-id-2.tact
new file mode 100644
index 000000000..af00be486
--- /dev/null
+++ b/src/types/stmts/contract-getter-with-method-id-2.tact
@@ -0,0 +1,10 @@
+primitive Int;
+trait BaseTrait { }
+
+const MethodId: Int = 16384;
+
+contract Test {
+ get(MethodId) fun test(): Int {
+ return MethodId
+ }
+}
diff --git a/src/types/stmts/contract-getter-with-method-id-3.tact b/src/types/stmts/contract-getter-with-method-id-3.tact
new file mode 100644
index 000000000..9b3c54d1a
--- /dev/null
+++ b/src/types/stmts/contract-getter-with-method-id-3.tact
@@ -0,0 +1,9 @@
+primitive Int;
+trait BaseTrait { }
+
+contract ManualMethodId {
+ const methodId: Int = 16384 + 41;
+ get(self.methodId + 1) fun methodId(): Int {
+ return self.methodId;
+ }
+}
diff --git a/src/types/types.ts b/src/types/types.ts
index f82fbc70e..8b34e74cf 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -144,6 +144,7 @@ export type FunctionDescription = {
name: string;
origin: ItemOrigin;
isGetter: boolean;
+ methodId: number | null;
isMutating: boolean;
isOverride: boolean;
isVirtual: boolean;
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index d88294dc8..8d3a45ec9 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -1,4 +1,3 @@
-import { crc16 } from "./crc16";
import { throwInternalCompilerError } from "../errors";
export function topologicalSort(src: T[], references: (src: T) => T[]) {
@@ -24,7 +23,3 @@ export function topologicalSort(src: T[], references: (src: T) => T[]) {
}
return result;
}
-
-export function getMethodId(name: string) {
- return (crc16(name) & 0xffff) | 0x10000;
-}