From bee2b5ace3632424b7b6e74083e409eeabd7f8ad Mon Sep 17 00:00:00 2001 From: Gusarich Date: Tue, 11 Jun 2024 21:57:00 +0300 Subject: [PATCH 1/8] implement constant string receivers --- src/grammar/ast.ts | 8 + src/grammar/grammar.ohm | 2 + src/grammar/grammar.ts | 38 ++ .../const-string-receiver.spec.ts.snap | 353 ++++++++++++++++++ .../const-string-receiver.spec.ts | 33 ++ .../contracts/const-string-receiver.tact | 21 ++ .../resolveDescriptors.spec.ts.snap | 30 ++ src/types/resolveDescriptors.ts | 107 +++++- ...tract-receiver-const-string-duplicate.tact | 23 ++ ...ract-receiver-const-string-duplicate2.tact | 18 + ...ract-receiver-const-string-duplicate3.tact | 23 ++ tact.config.json | 5 + 12 files changed, 641 insertions(+), 20 deletions(-) create mode 100644 src/test/e2e-emulated/__snapshots__/const-string-receiver.spec.ts.snap create mode 100644 src/test/e2e-emulated/const-string-receiver.spec.ts create mode 100644 src/test/e2e-emulated/contracts/const-string-receiver.tact create mode 100644 src/types/test-failed/contract-receiver-const-string-duplicate.tact create mode 100644 src/types/test-failed/contract-receiver-const-string-duplicate2.tact create mode 100644 src/types/test-failed/contract-receiver-const-string-duplicate3.tact diff --git a/src/grammar/ast.ts b/src/grammar/ast.ts index 68561a76c..180609e96 100644 --- a/src/grammar/ast.ts +++ b/src/grammar/ast.ts @@ -365,6 +365,10 @@ export type ASTReceiveType = kind: "internal-comment"; comment: ASTString; } + | { + kind: "internal-const-comment"; + comment: ASTId; + } | { kind: "bounce"; arg: ASTArgument; @@ -379,6 +383,10 @@ export type ASTReceiveType = | { kind: "external-comment"; comment: ASTString; + } + | { + kind: "external-const-comment"; + comment: ASTId; }; export type ASTReceive = { diff --git a/src/grammar/grammar.ohm b/src/grammar/grammar.ohm index ba54c33b7..bb6afc8e7 100644 --- a/src/grammar/grammar.ohm +++ b/src/grammar/grammar.ohm @@ -85,9 +85,11 @@ Tact { Receiver = receive "(" Parameter? ")" "{" Statement* "}" --regular | receive "(" stringLiteral ")" "{" Statement* "}" --comment + | receive "(" id ")" "{" Statement* "}" --constComment | "bounced" "(" Parameter ")" "{" Statement* "}" --bounced // cannot be a reserved word because there a 'bounced' field in stdlib's 'Context' structure | external "(" Parameter? ")" "{" Statement* "}" --externalRegular | external "(" stringLiteral ")" "{" Statement* "}" --externalComment + | external "(" id ")" "{" Statement* "}" --constExternalComment Statement = StatementLet | StatementBlock diff --git a/src/grammar/grammar.ts b/src/grammar/grammar.ts index f5173f619..e4d3c8ea7 100644 --- a/src/grammar/grammar.ts +++ b/src/grammar/grammar.ts @@ -340,6 +340,25 @@ semantics.addOperation("astOfItem", { ref: createRef(this), }); }, + Receiver_constComment( + _receiveKwd, + _lparen, + comment, + _rparen, + _lbrace, + receiverBody, + _rbrace, + ) { + return createNode({ + kind: "def_receive", + selector: { + kind: "internal-const-comment", + comment: comment.astOfExpression(), + }, + statements: receiverBody.children.map((s) => s.astOfStatement()), + ref: createRef(this), + }); + }, Receiver_bounced( _bouncedKwd, _lparen, @@ -398,6 +417,25 @@ semantics.addOperation("astOfItem", { ref: createRef(this), }); }, + Receiver_constExternalComment( + _externalKwd, + _lparen, + comment, + _rparen, + _lbrace, + receiverBody, + _rbrace, + ) { + return createNode({ + kind: "def_receive", + selector: { + kind: "external-const-comment", + comment: comment.astOfExpression(), + }, + statements: receiverBody.children.map((s) => s.astOfStatement()), + ref: createRef(this), + }); + }, }); semantics.addOperation("astOfFunctionAttributes", { diff --git a/src/test/e2e-emulated/__snapshots__/const-string-receiver.spec.ts.snap b/src/test/e2e-emulated/__snapshots__/const-string-receiver.spec.ts.snap new file mode 100644 index 000000000..d249ddb64 --- /dev/null +++ b/src/test/e2e-emulated/__snapshots__/const-string-receiver.spec.ts.snap @@ -0,0 +1,353 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`constant string receiver should implement const string receiver correctly 1`] = ` +{ + "errors": { + "10": { + "message": "Dictionary error", + }, + "128": { + "message": "Null reference exception", + }, + "129": { + "message": "Invalid serialization prefix", + }, + "13": { + "message": "Out of gas error", + }, + "130": { + "message": "Invalid incoming message", + }, + "131": { + "message": "Constraints error", + }, + "132": { + "message": "Access denied", + }, + "133": { + "message": "Contract stopped", + }, + "134": { + "message": "Invalid argument", + }, + "135": { + "message": "Code of a contract was not found", + }, + "136": { + "message": "Invalid address", + }, + "137": { + "message": "Masterchain support is not enabled for this contract", + }, + "2": { + "message": "Stack underflow", + }, + "3": { + "message": "Stack overflow", + }, + "32": { + "message": "Method ID not found", + }, + "34": { + "message": "Action is invalid or not supported", + }, + "37": { + "message": "Not enough TON", + }, + "38": { + "message": "Not enough extra-currencies", + }, + "4": { + "message": "Integer overflow", + }, + "5": { + "message": "Integer out of expected range", + }, + "6": { + "message": "Invalid opcode", + }, + "7": { + "message": "Type check error", + }, + "8": { + "message": "Cell overflow", + }, + "9": { + "message": "Cell underflow", + }, + }, + "getters": [], + "receivers": [ + { + "message": { + "kind": "text", + "text": "string 1", + }, + "receiver": "internal", + }, + { + "message": { + "kind": "text", + "text": "string 2", + }, + "receiver": "internal", + }, + { + "message": { + "kind": "text", + "text": "string 3", + }, + "receiver": "internal", + }, + ], + "types": [ + { + "fields": [ + { + "name": "code", + "type": { + "kind": "simple", + "optional": false, + "type": "cell", + }, + }, + { + "name": "data", + "type": { + "kind": "simple", + "optional": false, + "type": "cell", + }, + }, + ], + "header": null, + "name": "StateInit", + }, + { + "fields": [ + { + "name": "bounced", + "type": { + "kind": "simple", + "optional": false, + "type": "bool", + }, + }, + { + "name": "sender", + "type": { + "kind": "simple", + "optional": false, + "type": "address", + }, + }, + { + "name": "value", + "type": { + "format": 257, + "kind": "simple", + "optional": false, + "type": "int", + }, + }, + { + "name": "raw", + "type": { + "kind": "simple", + "optional": false, + "type": "slice", + }, + }, + ], + "header": null, + "name": "Context", + }, + { + "fields": [ + { + "name": "bounce", + "type": { + "kind": "simple", + "optional": false, + "type": "bool", + }, + }, + { + "name": "to", + "type": { + "kind": "simple", + "optional": false, + "type": "address", + }, + }, + { + "name": "value", + "type": { + "format": 257, + "kind": "simple", + "optional": false, + "type": "int", + }, + }, + { + "name": "mode", + "type": { + "format": 257, + "kind": "simple", + "optional": false, + "type": "int", + }, + }, + { + "name": "body", + "type": { + "kind": "simple", + "optional": true, + "type": "cell", + }, + }, + { + "name": "code", + "type": { + "kind": "simple", + "optional": true, + "type": "cell", + }, + }, + { + "name": "data", + "type": { + "kind": "simple", + "optional": true, + "type": "cell", + }, + }, + ], + "header": null, + "name": "SendParameters", + }, + ], +} +`; + +exports[`constant string receiver should implement const string receiver correctly 2`] = ` +[ + { + "$seq": 1, + "events": [ + { + "$type": "received", + "message": { + "body": { + "text": "string 1", + "type": "text", + }, + "bounce": true, + "from": "@treasure(treasure)", + "to": "kQDg3ce4axASb2iP16WY4k7nP9uV--zZxPfmGYbm8ZF_YPj4", + "type": "internal", + "value": "10", + }, + }, + { + "$type": "processed", + "gasUsed": 6689n, + }, + { + "$type": "sent", + "messages": [ + { + "body": { + "text": "string 1", + "type": "text", + }, + "bounce": true, + "from": "kQDg3ce4axASb2iP16WY4k7nP9uV--zZxPfmGYbm8ZF_YPj4", + "to": "@treasure(treasure)", + "type": "internal", + "value": "9.992115", + }, + ], + }, + ], + }, + { + "$seq": 2, + "events": [ + { + "$type": "received", + "message": { + "body": { + "text": "string 2", + "type": "text", + }, + "bounce": true, + "from": "@treasure(treasure)", + "to": "kQDg3ce4axASb2iP16WY4k7nP9uV--zZxPfmGYbm8ZF_YPj4", + "type": "internal", + "value": "10", + }, + }, + { + "$type": "processed", + "gasUsed": 9300n, + }, + { + "$type": "sent", + "messages": [ + { + "body": { + "text": "string 2", + "type": "text", + }, + "bounce": true, + "from": "kQDg3ce4axASb2iP16WY4k7nP9uV--zZxPfmGYbm8ZF_YPj4", + "to": "@treasure(treasure)", + "type": "internal", + "value": "9.989504", + }, + ], + }, + ], + }, + { + "$seq": 3, + "events": [ + { + "$type": "received", + "message": { + "body": { + "text": "string 3", + "type": "text", + }, + "bounce": true, + "from": "@treasure(treasure)", + "to": "kQDg3ce4axASb2iP16WY4k7nP9uV--zZxPfmGYbm8ZF_YPj4", + "type": "internal", + "value": "10", + }, + }, + { + "$type": "processed", + "gasUsed": 9377n, + }, + { + "$type": "sent", + "messages": [ + { + "body": { + "text": "string 3", + "type": "text", + }, + "bounce": true, + "from": "kQDg3ce4axASb2iP16WY4k7nP9uV--zZxPfmGYbm8ZF_YPj4", + "to": "@treasure(treasure)", + "type": "internal", + "value": "9.989427", + }, + ], + }, + ], + }, +] +`; diff --git a/src/test/e2e-emulated/const-string-receiver.spec.ts b/src/test/e2e-emulated/const-string-receiver.spec.ts new file mode 100644 index 000000000..1d27bce37 --- /dev/null +++ b/src/test/e2e-emulated/const-string-receiver.spec.ts @@ -0,0 +1,33 @@ +import { toNano } from "@ton/core"; +import { ContractSystem } from "@tact-lang/emulator"; +import { __DANGER_resetNodeId } from "../../grammar/ast"; +import { ConstStringReceiverTester } from "../e2e-emulated/contracts/output/const-string-receiver_ConstStringReceiverTester"; + +describe("constant string receiver", () => { + beforeEach(() => { + __DANGER_resetNodeId(); + }); + it("should implement const string receiver correctly", async () => { + // Init + const system = await ContractSystem.create(); + const treasure = system.treasure("treasure"); + const contract = system.open( + await ConstStringReceiverTester.fromInit(), + ); + + expect(contract.abi).toMatchSnapshot(); + + // Deploy + await contract.send(treasure, { value: toNano("10") }, "string 1"); + await system.run(); + + const tracker = system.track(contract); + + await contract.send(treasure, { value: toNano("10") }, "string 1"); + await contract.send(treasure, { value: toNano("10") }, "string 2"); + await contract.send(treasure, { value: toNano("10") }, "string 3"); + await system.run(); + + expect(tracker.collect()).toMatchSnapshot(); + }); +}); diff --git a/src/test/e2e-emulated/contracts/const-string-receiver.tact b/src/test/e2e-emulated/contracts/const-string-receiver.tact new file mode 100644 index 000000000..5580d624e --- /dev/null +++ b/src/test/e2e-emulated/contracts/const-string-receiver.tact @@ -0,0 +1,21 @@ +const string1: String = "string 1"; +const string2: String = "string 2"; +const string3: String = "string 3"; + +contract ConstStringReceiverTester { + init() { + + } + + receive("string 1") { + self.reply("string 1".asComment()); + } + + receive(string2) { + self.reply(string2.asComment()); + } + + receive(string3) { + self.reply(string3.asComment()); + } +} \ No newline at end of file diff --git a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap index 3cdb50b2f..9d8add971 100644 --- a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap +++ b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap @@ -90,6 +90,36 @@ Line 12, col 23: " `; +exports[`resolveDescriptors should fail descriptors for contract-receiver-const-string-duplicate 1`] = ` +":20:5: Receive function for string "string 1" already exists +Line 20, col 5: + 19 | +> 20 | receive(string1) { + ^~~~~~~~~~~~~~~~~~ + 21 | +" +`; + +exports[`resolveDescriptors should fail descriptors for contract-receiver-const-string-duplicate2 1`] = ` +":15:5: Receive function for "string 1" already exists +Line 15, col 5: + 14 | +> 15 | receive("string 1") { + ^~~~~~~~~~~~~~~~~~~~~ + 16 | +" +`; + +exports[`resolveDescriptors should fail descriptors for contract-receiver-const-string-duplicate3 1`] = ` +":20:5: Constant "string3" not found +Line 20, col 5: + 19 | +> 20 | receive(string3) { + ^~~~~~~~~~~~~~~~~~ + 21 | +" +`; + exports[`resolveDescriptors should fail descriptors for contract-receiver-int 1`] = ` ":13:3: Receive function can only accept message, Slice or String Line 13, col 3: diff --git a/src/types/resolveDescriptors.ts b/src/types/resolveDescriptors.ts index c84c1d13a..5c768f3a0 100644 --- a/src/types/resolveDescriptors.ts +++ b/src/types/resolveDescriptors.ts @@ -2,6 +2,7 @@ import { ASTConstant, ASTField, ASTFunction, + ASTId, ASTInitFunction, ASTNativeFunction, ASTNode, @@ -792,6 +793,26 @@ export function resolveDescriptors(ctx: CompilerContext) { }; } + // + // Resolve static constants + // + + for (const a of ast.constants) { + if (staticConstants.has(a.name)) { + throwSyntaxError( + `Static constant "${a.name}" already exists`, + a.ref, + ); + } + if (staticFunctions.has(a.name) || GlobalFunctions.has(a.name)) { + throwSyntaxError( + `Static function "${a.name}" already exists`, + a.ref, + ); + } + staticConstants.set(a.name, buildConstantDescription(a)); + } + for (const a of ast.types) { if (a.kind === "def_contract" || a.kind === "def_trait") { const s = types.get(a.name)!; @@ -1006,6 +1027,72 @@ export function resolveDescriptors(ctx: CompilerContext) { }, ast: d, }); + } else if ( + d.selector.kind === "internal-const-comment" || + d.selector.kind === "external-const-comment" + ) { + const internal = + d.selector.kind === "internal-const-comment"; + + const commentId = d.selector.comment.value; + const commentConstant = + s.constants.find((v) => v.name === commentId) ?? + staticConstants.get(commentId); + + if (!commentConstant) { + throwSyntaxError( + `Constant "${commentId}" not found`, + d.ref, + ); + } + + if (commentConstant.type.kind !== "ref") { + throwSyntaxError( + `Constant "${commentId}" must be a reference type`, + d.ref, + ); + } + + if (commentConstant.type.name !== "String") { + throwSyntaxError( + `Constant "${commentId}" must be of type String`, + d.ref, + ); + } + + const c = commentConstant.value as string; + + if (c === "") { + throwSyntaxError( + "To use empty comment receiver, just remove argument instead of passing empty string", + d.ref, + ); + } + + if ( + s.receivers.find( + (v) => + v.selector.kind === + (internal + ? "internal-comment" + : "external-comment") && + v.selector.comment === c, + ) + ) { + throwSyntaxError( + `Receive function for string "${c}" already exists`, + d.ref, + ); + } + s.receivers.push({ + selector: { + kind: internal + ? "internal-comment" + : "external-comment", + comment: c, + }, + ast: d, + }); } else if ( d.selector.kind === "internal-fallback" || d.selector.kind === "external-fallback" @@ -1595,26 +1682,6 @@ export function resolveDescriptors(ctx: CompilerContext) { } } - // - // Resolve static constants - // - - for (const a of ast.constants) { - if (staticConstants.has(a.name)) { - throwSyntaxError( - `Static constant "${a.name}" already exists`, - a.ref, - ); - } - if (staticFunctions.has(a.name) || GlobalFunctions.has(a.name)) { - throwSyntaxError( - `Static function "${a.name}" already exists`, - a.ref, - ); - } - staticConstants.set(a.name, buildConstantDescription(a)); - } - // // Register types and functions in context // diff --git a/src/types/test-failed/contract-receiver-const-string-duplicate.tact b/src/types/test-failed/contract-receiver-const-string-duplicate.tact new file mode 100644 index 000000000..67db131f7 --- /dev/null +++ b/src/types/test-failed/contract-receiver-const-string-duplicate.tact @@ -0,0 +1,23 @@ +primitive Int; +primitive String; + +const string1: String = "string 1"; +const string2: String = "string 2"; + +trait BaseTrait { + +} + +contract Main { + receive("string 1") { + + } + + receive("string 2") { + + } + + receive(string1) { + + } +} \ No newline at end of file diff --git a/src/types/test-failed/contract-receiver-const-string-duplicate2.tact b/src/types/test-failed/contract-receiver-const-string-duplicate2.tact new file mode 100644 index 000000000..d65d0cae0 --- /dev/null +++ b/src/types/test-failed/contract-receiver-const-string-duplicate2.tact @@ -0,0 +1,18 @@ +primitive Int; +primitive String; + +const string1: String = "string 1"; + +trait BaseTrait { + +} + +contract Main { + receive(string1) { + + } + + receive("string 1") { + + } +} \ No newline at end of file diff --git a/src/types/test-failed/contract-receiver-const-string-duplicate3.tact b/src/types/test-failed/contract-receiver-const-string-duplicate3.tact new file mode 100644 index 000000000..3d94d9a59 --- /dev/null +++ b/src/types/test-failed/contract-receiver-const-string-duplicate3.tact @@ -0,0 +1,23 @@ +primitive Int; +primitive String; + +const string1: String = "string 1"; +const string2: String = "string 2"; + +trait BaseTrait { + +} + +contract Main { + receive("string 1") { + + } + + receive("string 2") { + + } + + receive(string3) { + + } +} \ No newline at end of file diff --git a/tact.config.json b/tact.config.json index 4a98e5963..fb8d3d7d1 100644 --- a/tact.config.json +++ b/tact.config.json @@ -220,6 +220,11 @@ "external": true } }, + { + "name": "const-string-receiver", + "path": "./src/test/e2e-emulated/contracts/const-string-receiver.tact", + "output": "./src/test/e2e-emulated/contracts/output" + }, { "name": "debug", "path": "./src/test/e2e-emulated/contracts/debug.tact", From bd3e6c1d22b69d7e881403d88f2ee7fa639b4343 Mon Sep 17 00:00:00 2001 From: Gusarich Date: Tue, 11 Jun 2024 22:00:01 +0300 Subject: [PATCH 2/8] fix and update changelog --- CHANGELOG.md | 1 + src/test/e2e-emulated/contracts/const-string-receiver.tact | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77ca2704f..ad9b46b55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - CLI flag `--with-decompilation` to turn on decompilation of BoC files at the end of the compilation pipeline: PR [#417](https://github.com/tact-lang/tact/pull/417) - Support more Tact expressions in the constant evaluator: condition expressions, struct instances, struct field access, `emptyMap()`: PR [#432](https://github.com/tact-lang/tact/pull/432) - The `fromCell` and `fromSlice` methods for struct parsing: PR [#418](https://github.com/tact-lang/tact/pull/418) +- Constants can now be used in receivers: PR [#401](https://github.com/tact-lang/tact/pull/401) ### Changed diff --git a/src/test/e2e-emulated/contracts/const-string-receiver.tact b/src/test/e2e-emulated/contracts/const-string-receiver.tact index 5580d624e..50aa62841 100644 --- a/src/test/e2e-emulated/contracts/const-string-receiver.tact +++ b/src/test/e2e-emulated/contracts/const-string-receiver.tact @@ -3,9 +3,7 @@ const string2: String = "string 2"; const string3: String = "string 3"; contract ConstStringReceiverTester { - init() { - - } + receive() { } receive("string 1") { self.reply("string 1".asComment()); From 2492d836986803b9b86f1cfc5c14779742993a6f Mon Sep 17 00:00:00 2001 From: Gusarich Date: Tue, 11 Jun 2024 22:06:35 +0300 Subject: [PATCH 3/8] fix eslint --- src/types/resolveDescriptors.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/resolveDescriptors.ts b/src/types/resolveDescriptors.ts index 5c768f3a0..c784659c3 100644 --- a/src/types/resolveDescriptors.ts +++ b/src/types/resolveDescriptors.ts @@ -2,7 +2,6 @@ import { ASTConstant, ASTField, ASTFunction, - ASTId, ASTInitFunction, ASTNativeFunction, ASTNode, From 9cd41fe333e616960729dd118a7601574cd26726 Mon Sep 17 00:00:00 2001 From: Gusarich Date: Tue, 11 Jun 2024 23:53:18 +0300 Subject: [PATCH 4/8] fix for contract-scoped constants --- src/grammar/ast.ts | 4 +- src/grammar/grammar.ohm | 4 +- src/grammar/grammar.ts | 4 +- .../const-string-receiver.spec.ts.snap | 76 ++++++++++++++++--- .../const-string-receiver.spec.ts | 1 + .../contracts/const-string-receiver.tact | 6 ++ .../resolveDescriptors.spec.ts.snap | 70 +++++++++-------- src/types/resolveDescriptors.ts | 23 +++++- ...ract-duplicate-const-string-receiver.tact} | 0 ...act-duplicate-const-string-receiver2.tact} | 0 ...act-duplicate-const-string-receiver3.tact} | 0 ...ract-duplicate-const-string-receiver4.tact | 18 +++++ 12 files changed, 154 insertions(+), 52 deletions(-) rename src/types/test-failed/{contract-receiver-const-string-duplicate.tact => contract-duplicate-const-string-receiver.tact} (100%) rename src/types/test-failed/{contract-receiver-const-string-duplicate2.tact => contract-duplicate-const-string-receiver2.tact} (100%) rename src/types/test-failed/{contract-receiver-const-string-duplicate3.tact => contract-duplicate-const-string-receiver3.tact} (100%) create mode 100644 src/types/test-failed/contract-duplicate-const-string-receiver4.tact diff --git a/src/grammar/ast.ts b/src/grammar/ast.ts index 180609e96..8669edeaa 100644 --- a/src/grammar/ast.ts +++ b/src/grammar/ast.ts @@ -367,7 +367,7 @@ export type ASTReceiveType = } | { kind: "internal-const-comment"; - comment: ASTId; + comment: ASTLvalueRef[]; } | { kind: "bounce"; @@ -386,7 +386,7 @@ export type ASTReceiveType = } | { kind: "external-const-comment"; - comment: ASTId; + comment: ASTLvalueRef[]; }; export type ASTReceive = { diff --git a/src/grammar/grammar.ohm b/src/grammar/grammar.ohm index bb6afc8e7..282ba5234 100644 --- a/src/grammar/grammar.ohm +++ b/src/grammar/grammar.ohm @@ -85,11 +85,11 @@ Tact { Receiver = receive "(" Parameter? ")" "{" Statement* "}" --regular | receive "(" stringLiteral ")" "{" Statement* "}" --comment - | receive "(" id ")" "{" Statement* "}" --constComment + | receive "(" LValue ")" "{" Statement* "}" --constComment | "bounced" "(" Parameter ")" "{" Statement* "}" --bounced // cannot be a reserved word because there a 'bounced' field in stdlib's 'Context' structure | external "(" Parameter? ")" "{" Statement* "}" --externalRegular | external "(" stringLiteral ")" "{" Statement* "}" --externalComment - | external "(" id ")" "{" Statement* "}" --constExternalComment + | external "(" LValue ")" "{" Statement* "}" --constExternalComment Statement = StatementLet | StatementBlock diff --git a/src/grammar/grammar.ts b/src/grammar/grammar.ts index e4d3c8ea7..68dfa8a47 100644 --- a/src/grammar/grammar.ts +++ b/src/grammar/grammar.ts @@ -353,7 +353,7 @@ semantics.addOperation("astOfItem", { kind: "def_receive", selector: { kind: "internal-const-comment", - comment: comment.astOfExpression(), + comment: comment.astOfLValue(), }, statements: receiverBody.children.map((s) => s.astOfStatement()), ref: createRef(this), @@ -430,7 +430,7 @@ semantics.addOperation("astOfItem", { kind: "def_receive", selector: { kind: "external-const-comment", - comment: comment.astOfExpression(), + comment: comment.astOfLValue(), }, statements: receiverBody.children.map((s) => s.astOfStatement()), ref: createRef(this), diff --git a/src/test/e2e-emulated/__snapshots__/const-string-receiver.spec.ts.snap b/src/test/e2e-emulated/__snapshots__/const-string-receiver.spec.ts.snap index d249ddb64..4c3de2697 100644 --- a/src/test/e2e-emulated/__snapshots__/const-string-receiver.spec.ts.snap +++ b/src/test/e2e-emulated/__snapshots__/const-string-receiver.spec.ts.snap @@ -78,6 +78,12 @@ exports[`constant string receiver should implement const string receiver correct }, "getters": [], "receivers": [ + { + "message": { + "kind": "empty", + }, + "receiver": "internal", + }, { "message": { "kind": "text", @@ -99,6 +105,13 @@ exports[`constant string receiver should implement const string receiver correct }, "receiver": "internal", }, + { + "message": { + "kind": "text", + "text": "string 4", + }, + "receiver": "internal", + }, ], "types": [ { @@ -244,14 +257,14 @@ exports[`constant string receiver should implement const string receiver correct }, "bounce": true, "from": "@treasure(treasure)", - "to": "kQDg3ce4axASb2iP16WY4k7nP9uV--zZxPfmGYbm8ZF_YPj4", + "to": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", "type": "internal", "value": "10", }, }, { "$type": "processed", - "gasUsed": 6689n, + "gasUsed": 6857n, }, { "$type": "sent", @@ -262,10 +275,10 @@ exports[`constant string receiver should implement const string receiver correct "type": "text", }, "bounce": true, - "from": "kQDg3ce4axASb2iP16WY4k7nP9uV--zZxPfmGYbm8ZF_YPj4", + "from": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", "to": "@treasure(treasure)", "type": "internal", - "value": "9.992115", + "value": "9.991947", }, ], }, @@ -283,14 +296,14 @@ exports[`constant string receiver should implement const string receiver correct }, "bounce": true, "from": "@treasure(treasure)", - "to": "kQDg3ce4axASb2iP16WY4k7nP9uV--zZxPfmGYbm8ZF_YPj4", + "to": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", "type": "internal", "value": "10", }, }, { "$type": "processed", - "gasUsed": 9300n, + "gasUsed": 9468n, }, { "$type": "sent", @@ -301,10 +314,10 @@ exports[`constant string receiver should implement const string receiver correct "type": "text", }, "bounce": true, - "from": "kQDg3ce4axASb2iP16WY4k7nP9uV--zZxPfmGYbm8ZF_YPj4", + "from": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", "to": "@treasure(treasure)", "type": "internal", - "value": "9.989504", + "value": "9.989336", }, ], }, @@ -322,14 +335,14 @@ exports[`constant string receiver should implement const string receiver correct }, "bounce": true, "from": "@treasure(treasure)", - "to": "kQDg3ce4axASb2iP16WY4k7nP9uV--zZxPfmGYbm8ZF_YPj4", + "to": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", "type": "internal", "value": "10", }, }, { "$type": "processed", - "gasUsed": 9377n, + "gasUsed": 9581n, }, { "$type": "sent", @@ -340,10 +353,49 @@ exports[`constant string receiver should implement const string receiver correct "type": "text", }, "bounce": true, - "from": "kQDg3ce4axASb2iP16WY4k7nP9uV--zZxPfmGYbm8ZF_YPj4", + "from": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", + "to": "@treasure(treasure)", + "type": "internal", + "value": "9.989223", + }, + ], + }, + ], + }, + { + "$seq": 4, + "events": [ + { + "$type": "received", + "message": { + "body": { + "text": "string 4", + "type": "text", + }, + "bounce": true, + "from": "@treasure(treasure)", + "to": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", + "type": "internal", + "value": "10", + }, + }, + { + "$type": "processed", + "gasUsed": 9730n, + }, + { + "$type": "sent", + "messages": [ + { + "body": { + "text": "string 4", + "type": "text", + }, + "bounce": true, + "from": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", "to": "@treasure(treasure)", "type": "internal", - "value": "9.989427", + "value": "9.989074", }, ], }, diff --git a/src/test/e2e-emulated/const-string-receiver.spec.ts b/src/test/e2e-emulated/const-string-receiver.spec.ts index 1d27bce37..88f5fe823 100644 --- a/src/test/e2e-emulated/const-string-receiver.spec.ts +++ b/src/test/e2e-emulated/const-string-receiver.spec.ts @@ -26,6 +26,7 @@ describe("constant string receiver", () => { await contract.send(treasure, { value: toNano("10") }, "string 1"); await contract.send(treasure, { value: toNano("10") }, "string 2"); await contract.send(treasure, { value: toNano("10") }, "string 3"); + await contract.send(treasure, { value: toNano("10") }, "string 4"); await system.run(); expect(tracker.collect()).toMatchSnapshot(); diff --git a/src/test/e2e-emulated/contracts/const-string-receiver.tact b/src/test/e2e-emulated/contracts/const-string-receiver.tact index 50aa62841..b0f37186b 100644 --- a/src/test/e2e-emulated/contracts/const-string-receiver.tact +++ b/src/test/e2e-emulated/contracts/const-string-receiver.tact @@ -3,6 +3,8 @@ const string2: String = "string 2"; const string3: String = "string 3"; contract ConstStringReceiverTester { + const string4: String = "string 4"; + receive() { } receive("string 1") { @@ -16,4 +18,8 @@ contract ConstStringReceiverTester { receive(string3) { self.reply(string3.asComment()); } + + receive(self.string4) { + self.reply(self.string4.asComment()); + } } \ No newline at end of file diff --git a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap index 9d8add971..8de114d47 100644 --- a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap +++ b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap @@ -50,6 +50,46 @@ Line 24, col 3: " `; +exports[`resolveDescriptors should fail descriptors for contract-duplicate-const-string-receiver 1`] = ` +":20:5: Receive function for string "string 1" already exists +Line 20, col 5: + 19 | +> 20 | receive(string1) { + ^~~~~~~~~~~~~~~~~~ + 21 | +" +`; + +exports[`resolveDescriptors should fail descriptors for contract-duplicate-const-string-receiver2 1`] = ` +":15:5: Receive function for "string 1" already exists +Line 15, col 5: + 14 | +> 15 | receive("string 1") { + ^~~~~~~~~~~~~~~~~~~~~ + 16 | +" +`; + +exports[`resolveDescriptors should fail descriptors for contract-duplicate-const-string-receiver3 1`] = ` +":20:5: Constant "string3" not found +Line 20, col 5: + 19 | +> 20 | receive(string3) { + ^~~~~~~~~~~~~~~~~~ + 21 | +" +`; + +exports[`resolveDescriptors should fail descriptors for contract-duplicate-const-string-receiver4 1`] = ` +":15:5: Receive function for string "string 1" already exists +Line 15, col 5: + 14 | +> 15 | receive(self.string1) { + ^~~~~~~~~~~~~~~~~~~~~~~ + 16 | +" +`; + exports[`resolveDescriptors should fail descriptors for contract-duplicate-external-fallback-receiver 1`] = ` ":20:5: Empty receive function already exists Line 20, col 5: @@ -90,36 +130,6 @@ Line 12, col 23: " `; -exports[`resolveDescriptors should fail descriptors for contract-receiver-const-string-duplicate 1`] = ` -":20:5: Receive function for string "string 1" already exists -Line 20, col 5: - 19 | -> 20 | receive(string1) { - ^~~~~~~~~~~~~~~~~~ - 21 | -" -`; - -exports[`resolveDescriptors should fail descriptors for contract-receiver-const-string-duplicate2 1`] = ` -":15:5: Receive function for "string 1" already exists -Line 15, col 5: - 14 | -> 15 | receive("string 1") { - ^~~~~~~~~~~~~~~~~~~~~ - 16 | -" -`; - -exports[`resolveDescriptors should fail descriptors for contract-receiver-const-string-duplicate3 1`] = ` -":20:5: Constant "string3" not found -Line 20, col 5: - 19 | -> 20 | receive(string3) { - ^~~~~~~~~~~~~~~~~~ - 21 | -" -`; - exports[`resolveDescriptors should fail descriptors for contract-receiver-int 1`] = ` ":13:3: Receive function can only accept message, Slice or String Line 13, col 3: diff --git a/src/types/resolveDescriptors.ts b/src/types/resolveDescriptors.ts index c784659c3..344250532 100644 --- a/src/types/resolveDescriptors.ts +++ b/src/types/resolveDescriptors.ts @@ -1033,10 +1033,25 @@ export function resolveDescriptors(ctx: CompilerContext) { const internal = d.selector.kind === "internal-const-comment"; - const commentId = d.selector.comment.value; - const commentConstant = - s.constants.find((v) => v.name === commentId) ?? - staticConstants.get(commentId); + if (d.selector.comment.length > 2) { + // TEMPORARY + // to be reworked after #284 and #400 are resolved + throwSyntaxError( + "Invalid comment receiver selector", + d.ref, + ); + } + + const isSelf = + d.selector.comment.length === 2 && + d.selector.comment[0].name === "self"; + + const commentId = + d.selector.comment[d.selector.comment.length - 1] + .name; + const commentConstant = isSelf + ? s.constants.find((v) => v.name === commentId) + : staticConstants.get(commentId); if (!commentConstant) { throwSyntaxError( diff --git a/src/types/test-failed/contract-receiver-const-string-duplicate.tact b/src/types/test-failed/contract-duplicate-const-string-receiver.tact similarity index 100% rename from src/types/test-failed/contract-receiver-const-string-duplicate.tact rename to src/types/test-failed/contract-duplicate-const-string-receiver.tact diff --git a/src/types/test-failed/contract-receiver-const-string-duplicate2.tact b/src/types/test-failed/contract-duplicate-const-string-receiver2.tact similarity index 100% rename from src/types/test-failed/contract-receiver-const-string-duplicate2.tact rename to src/types/test-failed/contract-duplicate-const-string-receiver2.tact diff --git a/src/types/test-failed/contract-receiver-const-string-duplicate3.tact b/src/types/test-failed/contract-duplicate-const-string-receiver3.tact similarity index 100% rename from src/types/test-failed/contract-receiver-const-string-duplicate3.tact rename to src/types/test-failed/contract-duplicate-const-string-receiver3.tact diff --git a/src/types/test-failed/contract-duplicate-const-string-receiver4.tact b/src/types/test-failed/contract-duplicate-const-string-receiver4.tact new file mode 100644 index 000000000..045c7562c --- /dev/null +++ b/src/types/test-failed/contract-duplicate-const-string-receiver4.tact @@ -0,0 +1,18 @@ +primitive Int; +primitive String; + +trait BaseTrait { + +} + +contract Main { + const string1: String = "string 1"; + + receive("string 1") { + + } + + receive(self.string1) { + + } +} \ No newline at end of file From dc6e62d1052fab2f42b32a60fedc417db74d36c0 Mon Sep 17 00:00:00 2001 From: Gusarich Date: Tue, 11 Jun 2024 23:59:04 +0300 Subject: [PATCH 5/8] fix lvalue path limitation --- .../resolveDescriptors.spec.ts.snap | 10 +++++++ src/types/resolveDescriptors.ts | 6 +++- ...ract-duplicate-const-string-receiver5.tact | 28 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/types/test-failed/contract-duplicate-const-string-receiver5.tact diff --git a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap index 8de114d47..74f26fdca 100644 --- a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap +++ b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap @@ -90,6 +90,16 @@ Line 15, col 5: " `; +exports[`resolveDescriptors should fail descriptors for contract-duplicate-const-string-receiver5 1`] = ` +":25:5: Invalid comment receiver selector +Line 25, col 5: + 24 | +> 25 | receive(self.struct.s) { + ^~~~~~~~~~~~~~~~~~~~~~~~ + 26 | +" +`; + exports[`resolveDescriptors should fail descriptors for contract-duplicate-external-fallback-receiver 1`] = ` ":20:5: Empty receive function already exists Line 20, col 5: diff --git a/src/types/resolveDescriptors.ts b/src/types/resolveDescriptors.ts index 344250532..1af3b084f 100644 --- a/src/types/resolveDescriptors.ts +++ b/src/types/resolveDescriptors.ts @@ -1033,7 +1033,11 @@ export function resolveDescriptors(ctx: CompilerContext) { const internal = d.selector.kind === "internal-const-comment"; - if (d.selector.comment.length > 2) { + if ( + d.selector.comment.length > 2 || + (d.selector.comment.length == 2 && + d.selector.comment[0].name !== "self") + ) { // TEMPORARY // to be reworked after #284 and #400 are resolved throwSyntaxError( diff --git a/src/types/test-failed/contract-duplicate-const-string-receiver5.tact b/src/types/test-failed/contract-duplicate-const-string-receiver5.tact new file mode 100644 index 000000000..dd4ead50c --- /dev/null +++ b/src/types/test-failed/contract-duplicate-const-string-receiver5.tact @@ -0,0 +1,28 @@ +primitive Int; +primitive String; + +trait BaseTrait { + +} + +struct MyStruct { + s: String; +} + +contract Main { + struct: MyStruct; + + init () { + self.struct = MyStruct { + s: "string 1" + }; + } + + receive("string 1") { + + } + + receive(self.struct.s) { + + } +} \ No newline at end of file From 5a8fbe6dc5c236fd113fab098ae84fef3011c9d5 Mon Sep 17 00:00:00 2001 From: Gusarich Date: Wed, 12 Jun 2024 00:02:12 +0300 Subject: [PATCH 6/8] add snapshot to spell checker ignore --- cspell.json | 1 + 1 file changed, 1 insertion(+) diff --git a/cspell.json b/cspell.json index 700e7c0a1..cef42341c 100644 --- a/cspell.json +++ b/cspell.json @@ -88,6 +88,7 @@ "src/test/e2e-emulated/__snapshots__/masterchain.spec.ts.snap", "src/test/e2e-emulated/__snapshots__/sample-jetton.spec.ts.snap", "src/test/e2e-emulated/__snapshots__/try-catch.spec.ts.snap", + "src/test/e2e-emulated/__snapshots__/const-string-receiver.spec.ts.snap", "src/test/e2e-emulated/address.spec.ts", "src/test/e2e-emulated/intrinsics.spec.ts", "src/test/e2e-emulated/optionals.spec.ts", From 9f1a72d06c42ea8020be872399a4098318503be8 Mon Sep 17 00:00:00 2001 From: Gusarich Date: Wed, 19 Jun 2024 13:43:41 +0300 Subject: [PATCH 7/8] implement for structs and fields --- .../const-string-receiver.spec.ts.snap | 140 +++- .../const-string-receiver.spec.ts | 2 + .../contracts/const-string-receiver.tact | 24 + .../resolveDescriptors.spec.ts.snap | 712 +++++++++++++++++- src/types/resolveDescriptors.ts | 77 +- ...tract-const-string-receiver-not-found.tact | 24 + ...ract-const-string-receiver-not-found2.tact | 24 + ...ract-const-string-receiver-not-found3.tact | 28 + ...ract-duplicate-const-string-receiver5.tact | 10 +- .../test/contract-const-string-receiver.tact | 20 + .../test/contract-const-string-receiver2.tact | 18 + 11 files changed, 1022 insertions(+), 57 deletions(-) create mode 100644 src/types/test-failed/contract-const-string-receiver-not-found.tact create mode 100644 src/types/test-failed/contract-const-string-receiver-not-found2.tact create mode 100644 src/types/test-failed/contract-const-string-receiver-not-found3.tact create mode 100644 src/types/test/contract-const-string-receiver.tact create mode 100644 src/types/test/contract-const-string-receiver2.tact diff --git a/src/test/e2e-emulated/__snapshots__/const-string-receiver.spec.ts.snap b/src/test/e2e-emulated/__snapshots__/const-string-receiver.spec.ts.snap index 4c3de2697..ae2fb0596 100644 --- a/src/test/e2e-emulated/__snapshots__/const-string-receiver.spec.ts.snap +++ b/src/test/e2e-emulated/__snapshots__/const-string-receiver.spec.ts.snap @@ -112,6 +112,20 @@ exports[`constant string receiver should implement const string receiver correct }, "receiver": "internal", }, + { + "message": { + "kind": "text", + "text": "string 5", + }, + "receiver": "internal", + }, + { + "message": { + "kind": "text", + "text": "string 6", + }, + "receiver": "internal", + }, ], "types": [ { @@ -239,6 +253,34 @@ exports[`constant string receiver should implement const string receiver correct "header": null, "name": "SendParameters", }, + { + "fields": [ + { + "name": "s", + "type": { + "kind": "simple", + "optional": false, + "type": "string", + }, + }, + ], + "header": null, + "name": "MyStruct", + }, + { + "fields": [ + { + "name": "struct", + "type": { + "kind": "simple", + "optional": false, + "type": "MyStruct", + }, + }, + ], + "header": null, + "name": "MyNestedStruct", + }, ], } `; @@ -257,7 +299,7 @@ exports[`constant string receiver should implement const string receiver correct }, "bounce": true, "from": "@treasure(treasure)", - "to": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", + "to": "kQBO2__AL4wOwtXplvVV7psQ7PAJKpbbqzS36LyZWcW01Pl7", "type": "internal", "value": "10", }, @@ -275,7 +317,7 @@ exports[`constant string receiver should implement const string receiver correct "type": "text", }, "bounce": true, - "from": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", + "from": "kQBO2__AL4wOwtXplvVV7psQ7PAJKpbbqzS36LyZWcW01Pl7", "to": "@treasure(treasure)", "type": "internal", "value": "9.991947", @@ -296,7 +338,7 @@ exports[`constant string receiver should implement const string receiver correct }, "bounce": true, "from": "@treasure(treasure)", - "to": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", + "to": "kQBO2__AL4wOwtXplvVV7psQ7PAJKpbbqzS36LyZWcW01Pl7", "type": "internal", "value": "10", }, @@ -314,7 +356,7 @@ exports[`constant string receiver should implement const string receiver correct "type": "text", }, "bounce": true, - "from": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", + "from": "kQBO2__AL4wOwtXplvVV7psQ7PAJKpbbqzS36LyZWcW01Pl7", "to": "@treasure(treasure)", "type": "internal", "value": "9.989336", @@ -335,7 +377,7 @@ exports[`constant string receiver should implement const string receiver correct }, "bounce": true, "from": "@treasure(treasure)", - "to": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", + "to": "kQBO2__AL4wOwtXplvVV7psQ7PAJKpbbqzS36LyZWcW01Pl7", "type": "internal", "value": "10", }, @@ -353,7 +395,7 @@ exports[`constant string receiver should implement const string receiver correct "type": "text", }, "bounce": true, - "from": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", + "from": "kQBO2__AL4wOwtXplvVV7psQ7PAJKpbbqzS36LyZWcW01Pl7", "to": "@treasure(treasure)", "type": "internal", "value": "9.989223", @@ -374,14 +416,14 @@ exports[`constant string receiver should implement const string receiver correct }, "bounce": true, "from": "@treasure(treasure)", - "to": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", + "to": "kQBO2__AL4wOwtXplvVV7psQ7PAJKpbbqzS36LyZWcW01Pl7", "type": "internal", "value": "10", }, }, { "$type": "processed", - "gasUsed": 9730n, + "gasUsed": 9766n, }, { "$type": "sent", @@ -392,10 +434,88 @@ exports[`constant string receiver should implement const string receiver correct "type": "text", }, "bounce": true, - "from": "kQCjCGWLilNv7PPffKoWdQHPZ-88MekCb9F_U5ddfPTsuH3_", + "from": "kQBO2__AL4wOwtXplvVV7psQ7PAJKpbbqzS36LyZWcW01Pl7", + "to": "@treasure(treasure)", + "type": "internal", + "value": "9.989038", + }, + ], + }, + ], + }, + { + "$seq": 5, + "events": [ + { + "$type": "received", + "message": { + "body": { + "text": "string 5", + "type": "text", + }, + "bounce": true, + "from": "@treasure(treasure)", + "to": "kQBO2__AL4wOwtXplvVV7psQ7PAJKpbbqzS36LyZWcW01Pl7", + "type": "internal", + "value": "10", + }, + }, + { + "$type": "processed", + "gasUsed": 7453n, + }, + { + "$type": "sent", + "messages": [ + { + "body": { + "text": "string 5", + "type": "text", + }, + "bounce": true, + "from": "kQBO2__AL4wOwtXplvVV7psQ7PAJKpbbqzS36LyZWcW01Pl7", + "to": "@treasure(treasure)", + "type": "internal", + "value": "9.991351", + }, + ], + }, + ], + }, + { + "$seq": 6, + "events": [ + { + "$type": "received", + "message": { + "body": { + "text": "string 6", + "type": "text", + }, + "bounce": true, + "from": "@treasure(treasure)", + "to": "kQBO2__AL4wOwtXplvVV7psQ7PAJKpbbqzS36LyZWcW01Pl7", + "type": "internal", + "value": "10", + }, + }, + { + "$type": "processed", + "gasUsed": 7602n, + }, + { + "$type": "sent", + "messages": [ + { + "body": { + "text": "string 6", + "type": "text", + }, + "bounce": true, + "from": "kQBO2__AL4wOwtXplvVV7psQ7PAJKpbbqzS36LyZWcW01Pl7", "to": "@treasure(treasure)", "type": "internal", - "value": "9.989074", + "value": "9.991202", }, ], }, diff --git a/src/test/e2e-emulated/const-string-receiver.spec.ts b/src/test/e2e-emulated/const-string-receiver.spec.ts index 88f5fe823..f119bb0a3 100644 --- a/src/test/e2e-emulated/const-string-receiver.spec.ts +++ b/src/test/e2e-emulated/const-string-receiver.spec.ts @@ -27,6 +27,8 @@ describe("constant string receiver", () => { await contract.send(treasure, { value: toNano("10") }, "string 2"); await contract.send(treasure, { value: toNano("10") }, "string 3"); await contract.send(treasure, { value: toNano("10") }, "string 4"); + await contract.send(treasure, { value: toNano("10") }, "string 5"); + await contract.send(treasure, { value: toNano("10") }, "string 6"); await system.run(); expect(tracker.collect()).toMatchSnapshot(); diff --git a/src/test/e2e-emulated/contracts/const-string-receiver.tact b/src/test/e2e-emulated/contracts/const-string-receiver.tact index b0f37186b..b514a5c27 100644 --- a/src/test/e2e-emulated/contracts/const-string-receiver.tact +++ b/src/test/e2e-emulated/contracts/const-string-receiver.tact @@ -2,8 +2,24 @@ const string1: String = "string 1"; const string2: String = "string 2"; const string3: String = "string 3"; +struct MyStruct { + s: String; +} + +struct MyNestedStruct { + struct: MyStruct; +} + contract ConstStringReceiverTester { const string4: String = "string 4"; + const struct: MyStruct = MyStruct { + s: "string 5" + }; + const nestedStruct: MyNestedStruct = MyNestedStruct { + struct: MyStruct { + s: "string 6" + } + }; receive() { } @@ -22,4 +38,12 @@ contract ConstStringReceiverTester { receive(self.string4) { self.reply(self.string4.asComment()); } + + receive(self.struct.s) { + self.reply("string 5".asComment()); // until const struct fields are fixed + } + + receive(self.nestedStruct.struct.s) { + self.reply("string 6".asComment()); // until const struct fields are fixed + } } \ No newline at end of file diff --git a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap index 74f26fdca..4e42d50f4 100644 --- a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap +++ b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap @@ -30,6 +30,36 @@ Line 15, col 3: " `; +exports[`resolveDescriptors should fail descriptors for contract-const-string-receiver-not-found 1`] = ` +":21:5: Constant "self.struct.s2" not found +Line 21, col 5: + 20 | +> 21 | receive(self.struct.s2) { + ^~~~~~~~~~~~~~~~~~~~~~~~~ + 22 | +" +`; + +exports[`resolveDescriptors should fail descriptors for contract-const-string-receiver-not-found2 1`] = ` +":21:5: Constant "self.struct2.s" not found +Line 21, col 5: + 20 | +> 21 | receive(self.struct2.s) { + ^~~~~~~~~~~~~~~~~~~~~~~~~ + 22 | +" +`; + +exports[`resolveDescriptors should fail descriptors for contract-const-string-receiver-not-found3 1`] = ` +":25:5: Constant "self.struct.s" not found +Line 25, col 5: + 24 | +> 25 | receive(self.struct.s) { + ^~~~~~~~~~~~~~~~~~~~~~~~ + 26 | +" +`; + exports[`resolveDescriptors should fail descriptors for contract-duplicate-bounced-fallback-receiver 1`] = ` ":24:3: Fallback bounce receive function already exists Line 24, col 3: @@ -91,12 +121,12 @@ Line 15, col 5: `; exports[`resolveDescriptors should fail descriptors for contract-duplicate-const-string-receiver5 1`] = ` -":25:5: Invalid comment receiver selector -Line 25, col 5: - 24 | -> 25 | receive(self.struct.s) { +":21:5: Receive function for string "string 1" already exists +Line 21, col 5: + 20 | +> 21 | receive(self.struct.s) { ^~~~~~~~~~~~~~~~~~~~~~~~ - 26 | + 22 | " `; @@ -1220,6 +1250,678 @@ exports[`resolveDescriptors should resolve descriptors for contract-bounced-too- exports[`resolveDescriptors should resolve descriptors for contract-bounced-too-small-not-detected 2`] = `{}`; +exports[`resolveDescriptors should resolve descriptors for contract-const-string-receiver 1`] = ` +{ + "BaseTrait": { + "ast": { + "attributes": [], + "declarations": [], + "id": 3, + "kind": "def_trait", + "name": "BaseTrait", + "origin": "user", + "ref": trait BaseTrait { + +}, + "traits": [], + }, + "constants": [], + "dependsOn": [], + "fields": [], + "functions": Map {}, + "header": null, + "init": null, + "interfaces": [], + "kind": "trait", + "name": "BaseTrait", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": null, + "tlb": null, + "traits": [], + "uid": 1020, + }, + "Int": { + "ast": { + "id": 1, + "kind": "primitive", + "name": "Int", + "origin": "user", + "ref": primitive Int;, + }, + "constants": [], + "dependsOn": [], + "fields": [], + "functions": Map {}, + "header": null, + "init": null, + "interfaces": [], + "kind": "primitive", + "name": "Int", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": null, + "tlb": null, + "traits": [], + "uid": 38154, + }, + "Main": { + "ast": { + "attributes": [], + "declarations": [ + { + "attributes": [], + "id": 11, + "kind": "def_constant", + "name": "struct", + "ref": const struct: MyStruct = MyStruct { + s: "string 1" + };, + "type": { + "id": 7, + "kind": "type_ref_simple", + "name": "MyStruct", + "optional": false, + "ref": MyStruct, + }, + "value": { + "args": [ + { + "exp": { + "id": 8, + "kind": "string", + "ref": "string 1", + "value": "string 1", + }, + "id": 9, + "kind": "new_parameter", + "name": "s", + "ref": s: "string 1", + }, + ], + "id": 10, + "kind": "op_new", + "ref": MyStruct { + s: "string 1" + }, + "type": "MyStruct", + }, + }, + { + "id": 15, + "kind": "def_receive", + "ref": receive(self.struct.s) { + + }, + "selector": { + "comment": [ + { + "id": 12, + "kind": "lvalue_ref", + "name": "self", + "ref": self., + }, + { + "id": 13, + "kind": "lvalue_ref", + "name": "struct", + "ref": struct., + }, + { + "id": 14, + "kind": "lvalue_ref", + "name": "s", + "ref": s, + }, + ], + "kind": "internal-const-comment", + }, + "statements": [], + }, + ], + "id": 16, + "kind": "def_contract", + "name": "Main", + "origin": "user", + "ref": contract Main { + const struct: MyStruct = MyStruct { + s: "string 1" + }; + + receive(self.struct.s) { + + } +}, + "traits": [], + }, + "constants": [ + { + "ast": { + "attributes": [], + "id": 11, + "kind": "def_constant", + "name": "struct", + "ref": const struct: MyStruct = MyStruct { + s: "string 1" + };, + "type": { + "id": 7, + "kind": "type_ref_simple", + "name": "MyStruct", + "optional": false, + "ref": MyStruct, + }, + "value": { + "args": [ + { + "exp": { + "id": 8, + "kind": "string", + "ref": "string 1", + "value": "string 1", + }, + "id": 9, + "kind": "new_parameter", + "name": "s", + "ref": s: "string 1", + }, + ], + "id": 10, + "kind": "op_new", + "ref": MyStruct { + s: "string 1" + }, + "type": "MyStruct", + }, + }, + "name": "struct", + "ref": const struct: MyStruct = MyStruct { + s: "string 1" + };, + "type": { + "kind": "ref", + "name": "MyStruct", + "optional": false, + }, + "value": { + "$tactStruct": "MyStruct", + "s": "string 1", + }, + }, + ], + "dependsOn": [], + "fields": [], + "functions": Map {}, + "header": null, + "init": { + "args": [], + "ast": { + "args": [], + "id": 18, + "kind": "def_init_function", + "ref": contract Main { + const struct: MyStruct = MyStruct { + s: "string 1" + }; + + receive(self.struct.s) { + + } +}, + "statements": [], + }, + }, + "interfaces": [], + "kind": "contract", + "name": "Main", + "origin": "user", + "partialFieldCount": 0, + "receivers": [ + { + "ast": { + "id": 15, + "kind": "def_receive", + "ref": receive(self.struct.s) { + + }, + "selector": { + "comment": [ + { + "id": 12, + "kind": "lvalue_ref", + "name": "self", + "ref": self., + }, + { + "id": 13, + "kind": "lvalue_ref", + "name": "struct", + "ref": struct., + }, + { + "id": 14, + "kind": "lvalue_ref", + "name": "s", + "ref": s, + }, + ], + "kind": "internal-const-comment", + }, + "statements": [], + }, + "selector": { + "comment": "string 1", + "kind": "internal-comment", + }, + }, + ], + "signature": null, + "tlb": null, + "traits": [ + { + "ast": { + "attributes": [], + "declarations": [], + "id": 3, + "kind": "def_trait", + "name": "BaseTrait", + "origin": "user", + "ref": trait BaseTrait { + +}, + "traits": [], + }, + "constants": [], + "dependsOn": [], + "fields": [], + "functions": Map {}, + "header": null, + "init": null, + "interfaces": [], + "kind": "trait", + "name": "BaseTrait", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": null, + "tlb": null, + "traits": [], + "uid": 1020, + }, + ], + "uid": 51099, + }, + "MyStruct": { + "ast": { + "fields": [ + { + "as": null, + "id": 5, + "init": null, + "kind": "def_field", + "name": "s", + "ref": s: String, + "type": { + "id": 4, + "kind": "type_ref_simple", + "name": "String", + "optional": false, + "ref": String, + }, + }, + ], + "id": 6, + "kind": "def_struct", + "message": false, + "name": "MyStruct", + "origin": "user", + "prefix": null, + "ref": struct MyStruct { + s: String; +}, + }, + "constants": [], + "dependsOn": [], + "fields": [ + { + "abi": { + "name": "s", + "type": { + "kind": "simple", + "optional": false, + "type": "string", + }, + }, + "as": null, + "ast": { + "as": null, + "id": 5, + "init": null, + "kind": "def_field", + "name": "s", + "ref": s: String, + "type": { + "id": 4, + "kind": "type_ref_simple", + "name": "String", + "optional": false, + "ref": String, + }, + }, + "default": undefined, + "index": 0, + "name": "s", + "ref": s: String, + "type": { + "kind": "ref", + "name": "String", + "optional": false, + }, + }, + ], + "functions": Map {}, + "header": null, + "init": null, + "interfaces": [], + "kind": "struct", + "name": "MyStruct", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": "MyStruct{s:^string}", + "tlb": "_ s:^string = MyStruct", + "traits": [], + "uid": 55650, + }, + "String": { + "ast": { + "id": 2, + "kind": "primitive", + "name": "String", + "origin": "user", + "ref": primitive String;, + }, + "constants": [], + "dependsOn": [], + "fields": [], + "functions": Map {}, + "header": null, + "init": null, + "interfaces": [], + "kind": "primitive", + "name": "String", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": null, + "tlb": null, + "traits": [], + "uid": 46199, + }, +} +`; + +exports[`resolveDescriptors should resolve descriptors for contract-const-string-receiver 2`] = `{}`; + +exports[`resolveDescriptors should resolve descriptors for contract-const-string-receiver2 1`] = ` +{ + "BaseTrait": { + "ast": { + "attributes": [], + "declarations": [], + "id": 6, + "kind": "def_trait", + "name": "BaseTrait", + "origin": "user", + "ref": trait BaseTrait { + +}, + "traits": [], + }, + "constants": [], + "dependsOn": [], + "fields": [], + "functions": Map {}, + "header": null, + "init": null, + "interfaces": [], + "kind": "trait", + "name": "BaseTrait", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": null, + "tlb": null, + "traits": [], + "uid": 1020, + }, + "Int": { + "ast": { + "id": 1, + "kind": "primitive", + "name": "Int", + "origin": "user", + "ref": primitive Int;, + }, + "constants": [], + "dependsOn": [], + "fields": [], + "functions": Map {}, + "header": null, + "init": null, + "interfaces": [], + "kind": "primitive", + "name": "Int", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": null, + "tlb": null, + "traits": [], + "uid": 38154, + }, + "Main": { + "ast": { + "attributes": [], + "declarations": [ + { + "id": 8, + "kind": "def_receive", + "ref": receive("string 1") { + + }, + "selector": { + "comment": { + "id": 7, + "kind": "string", + "ref": "string 1", + "value": "string 1", + }, + "kind": "internal-comment", + }, + "statements": [], + }, + { + "id": 10, + "kind": "def_receive", + "ref": receive(string2) { + + }, + "selector": { + "comment": [ + { + "id": 9, + "kind": "lvalue_ref", + "name": "string2", + "ref": string2, + }, + ], + "kind": "internal-const-comment", + }, + "statements": [], + }, + ], + "id": 11, + "kind": "def_contract", + "name": "Main", + "origin": "user", + "ref": contract Main { + receive("string 1") { + + } + + receive(string2) { + + } +}, + "traits": [], + }, + "constants": [], + "dependsOn": [], + "fields": [], + "functions": Map {}, + "header": null, + "init": { + "args": [], + "ast": { + "args": [], + "id": 13, + "kind": "def_init_function", + "ref": contract Main { + receive("string 1") { + + } + + receive(string2) { + + } +}, + "statements": [], + }, + }, + "interfaces": [], + "kind": "contract", + "name": "Main", + "origin": "user", + "partialFieldCount": 0, + "receivers": [ + { + "ast": { + "id": 8, + "kind": "def_receive", + "ref": receive("string 1") { + + }, + "selector": { + "comment": { + "id": 7, + "kind": "string", + "ref": "string 1", + "value": "string 1", + }, + "kind": "internal-comment", + }, + "statements": [], + }, + "selector": { + "comment": "string 1", + "kind": "internal-comment", + }, + }, + { + "ast": { + "id": 10, + "kind": "def_receive", + "ref": receive(string2) { + + }, + "selector": { + "comment": [ + { + "id": 9, + "kind": "lvalue_ref", + "name": "string2", + "ref": string2, + }, + ], + "kind": "internal-const-comment", + }, + "statements": [], + }, + "selector": { + "comment": "string 2", + "kind": "internal-comment", + }, + }, + ], + "signature": null, + "tlb": null, + "traits": [ + { + "ast": { + "attributes": [], + "declarations": [], + "id": 6, + "kind": "def_trait", + "name": "BaseTrait", + "origin": "user", + "ref": trait BaseTrait { + +}, + "traits": [], + }, + "constants": [], + "dependsOn": [], + "fields": [], + "functions": Map {}, + "header": null, + "init": null, + "interfaces": [], + "kind": "trait", + "name": "BaseTrait", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": null, + "tlb": null, + "traits": [], + "uid": 1020, + }, + ], + "uid": 51099, + }, + "String": { + "ast": { + "id": 2, + "kind": "primitive", + "name": "String", + "origin": "user", + "ref": primitive String;, + }, + "constants": [], + "dependsOn": [], + "fields": [], + "functions": Map {}, + "header": null, + "init": null, + "interfaces": [], + "kind": "primitive", + "name": "String", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": null, + "tlb": null, + "traits": [], + "uid": 46199, + }, +} +`; + +exports[`resolveDescriptors should resolve descriptors for contract-const-string-receiver2 2`] = `{}`; + exports[`resolveDescriptors should resolve descriptors for contract-external-fallback-receiver 1`] = ` { "A": { diff --git a/src/types/resolveDescriptors.ts b/src/types/resolveDescriptors.ts index 1af3b084f..ee1294474 100644 --- a/src/types/resolveDescriptors.ts +++ b/src/types/resolveDescriptors.ts @@ -3,6 +3,7 @@ import { ASTField, ASTFunction, ASTInitFunction, + ASTLvalueRef, ASTNativeFunction, ASTNode, ASTRef, @@ -25,6 +26,7 @@ import { TypeOrigin, TypeRef, typeRefEquals, + Value, } from "./types"; import { getRawAST } from "../grammar/store"; import { cloneNode } from "../grammar/clone"; @@ -34,6 +36,7 @@ import { resolveABIType } from "./resolveABITypeRef"; import { enabledExternals } from "../config/features"; import { isRuntimeType } from "./isRuntimeType"; import { GlobalFunctions } from "../abi/global"; +import { Issue74 } from "../test/codegen/output/codegen_Issue74"; const store = createContextStore(); const staticFunctionsStore = createContextStore(); @@ -1033,54 +1036,58 @@ export function resolveDescriptors(ctx: CompilerContext) { const internal = d.selector.kind === "internal-const-comment"; - if ( - d.selector.comment.length > 2 || - (d.selector.comment.length == 2 && - d.selector.comment[0].name !== "self") - ) { - // TEMPORARY - // to be reworked after #284 and #400 are resolved - throwSyntaxError( - "Invalid comment receiver selector", - d.ref, - ); - } + let path = d.selector.comment; - const isSelf = - d.selector.comment.length === 2 && - d.selector.comment[0].name === "self"; + const pathString = path.map((v) => v.name).join("."); - const commentId = - d.selector.comment[d.selector.comment.length - 1] - .name; - const commentConstant = isSelf - ? s.constants.find((v) => v.name === commentId) - : staticConstants.get(commentId); + const isSelf = + path.length >= 2 && path[0].name === "self"; - if (!commentConstant) { - throwSyntaxError( - `Constant "${commentId}" not found`, - d.ref, + let commentConstant: ConstantDescription | undefined = + undefined; + if (isSelf) { + commentConstant = s.constants.find( + (v) => v.name === path[1].name, ); + path = path.slice(2); + } else { + commentConstant = staticConstants.get(path[0].name); + path = path.slice(1); } - if (commentConstant.type.kind !== "ref") { + let commentValue: Value | undefined = + commentConstant?.value; + + while (commentValue && path.length > 0) { + if ( + commentValue == null || + typeof commentValue !== "object" || + !("$tactStruct" in commentValue) + ) { + throwSyntaxError( + `Cannot find constant "${path[0].name}" in ${pathString}`, + d.ref, + ); + } + commentValue = commentValue[path[0].name]; + path = path.slice(1); + } + + if (!commentValue) { throwSyntaxError( - `Constant "${commentId}" must be a reference type`, + `Constant "${pathString}" not found`, d.ref, ); } - if (commentConstant.type.name !== "String") { + if (typeof commentValue !== "string") { throwSyntaxError( - `Constant "${commentId}" must be of type String`, + `Constant "${pathString}" must be of type String`, d.ref, ); } - const c = commentConstant.value as string; - - if (c === "") { + if (commentValue === "") { throwSyntaxError( "To use empty comment receiver, just remove argument instead of passing empty string", d.ref, @@ -1094,11 +1101,11 @@ export function resolveDescriptors(ctx: CompilerContext) { (internal ? "internal-comment" : "external-comment") && - v.selector.comment === c, + v.selector.comment === commentValue, ) ) { throwSyntaxError( - `Receive function for string "${c}" already exists`, + `Receive function for string "${commentValue}" already exists`, d.ref, ); } @@ -1107,7 +1114,7 @@ export function resolveDescriptors(ctx: CompilerContext) { kind: internal ? "internal-comment" : "external-comment", - comment: c, + comment: commentValue, }, ast: d, }); diff --git a/src/types/test-failed/contract-const-string-receiver-not-found.tact b/src/types/test-failed/contract-const-string-receiver-not-found.tact new file mode 100644 index 000000000..1eb75e2aa --- /dev/null +++ b/src/types/test-failed/contract-const-string-receiver-not-found.tact @@ -0,0 +1,24 @@ +primitive Int; +primitive String; + +trait BaseTrait { + +} + +struct MyStruct { + s: String; +} + +contract Main { + const struct: MyStruct = MyStruct { + s: "string 1" + }; + + receive("string 1") { + + } + + receive(self.struct.s2) { + + } +} \ No newline at end of file diff --git a/src/types/test-failed/contract-const-string-receiver-not-found2.tact b/src/types/test-failed/contract-const-string-receiver-not-found2.tact new file mode 100644 index 000000000..8daa49efd --- /dev/null +++ b/src/types/test-failed/contract-const-string-receiver-not-found2.tact @@ -0,0 +1,24 @@ +primitive Int; +primitive String; + +trait BaseTrait { + +} + +struct MyStruct { + s: String; +} + +contract Main { + const struct: MyStruct = MyStruct { + s: "string 1" + }; + + receive("string 1") { + + } + + receive(self.struct2.s) { + + } +} \ No newline at end of file diff --git a/src/types/test-failed/contract-const-string-receiver-not-found3.tact b/src/types/test-failed/contract-const-string-receiver-not-found3.tact new file mode 100644 index 000000000..dd4ead50c --- /dev/null +++ b/src/types/test-failed/contract-const-string-receiver-not-found3.tact @@ -0,0 +1,28 @@ +primitive Int; +primitive String; + +trait BaseTrait { + +} + +struct MyStruct { + s: String; +} + +contract Main { + struct: MyStruct; + + init () { + self.struct = MyStruct { + s: "string 1" + }; + } + + receive("string 1") { + + } + + receive(self.struct.s) { + + } +} \ No newline at end of file diff --git a/src/types/test-failed/contract-duplicate-const-string-receiver5.tact b/src/types/test-failed/contract-duplicate-const-string-receiver5.tact index dd4ead50c..315f1618a 100644 --- a/src/types/test-failed/contract-duplicate-const-string-receiver5.tact +++ b/src/types/test-failed/contract-duplicate-const-string-receiver5.tact @@ -10,13 +10,9 @@ struct MyStruct { } contract Main { - struct: MyStruct; - - init () { - self.struct = MyStruct { - s: "string 1" - }; - } + const struct: MyStruct = MyStruct { + s: "string 1" + }; receive("string 1") { diff --git a/src/types/test/contract-const-string-receiver.tact b/src/types/test/contract-const-string-receiver.tact new file mode 100644 index 000000000..cd0404fc3 --- /dev/null +++ b/src/types/test/contract-const-string-receiver.tact @@ -0,0 +1,20 @@ +primitive Int; +primitive String; + +trait BaseTrait { + +} + +struct MyStruct { + s: String; +} + +contract Main { + const struct: MyStruct = MyStruct { + s: "string 1" + }; + + receive(self.struct.s) { + + } +} \ No newline at end of file diff --git a/src/types/test/contract-const-string-receiver2.tact b/src/types/test/contract-const-string-receiver2.tact new file mode 100644 index 000000000..01729115e --- /dev/null +++ b/src/types/test/contract-const-string-receiver2.tact @@ -0,0 +1,18 @@ +primitive Int; +primitive String; + +const string2: String = "string 2"; + +trait BaseTrait { + +} + +contract Main { + receive("string 1") { + + } + + receive(string2) { + + } +} \ No newline at end of file From f6f0d997c17cede11c9fa665252e1a2994d48238 Mon Sep 17 00:00:00 2001 From: Gusarich Date: Wed, 19 Jun 2024 13:50:12 +0300 Subject: [PATCH 8/8] fix eslint --- src/types/resolveDescriptors.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/types/resolveDescriptors.ts b/src/types/resolveDescriptors.ts index ee1294474..f00203316 100644 --- a/src/types/resolveDescriptors.ts +++ b/src/types/resolveDescriptors.ts @@ -3,7 +3,6 @@ import { ASTField, ASTFunction, ASTInitFunction, - ASTLvalueRef, ASTNativeFunction, ASTNode, ASTRef, @@ -36,7 +35,6 @@ import { resolveABIType } from "./resolveABITypeRef"; import { enabledExternals } from "../config/features"; import { isRuntimeType } from "./isRuntimeType"; import { GlobalFunctions } from "../abi/global"; -import { Issue74 } from "../test/codegen/output/codegen_Issue74"; const store = createContextStore(); const staticFunctionsStore = createContextStore();