From ecd873805c40156eed3db7ca4cce7852a22b9117 Mon Sep 17 00:00:00 2001 From: Petr Makhnev <51853996+i582@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:17:33 +0400 Subject: [PATCH] fix: clarify error message for a field access in bounced types that does not fit in 224 bytes (#1111) --- CHANGELOG.md | 1 + .../resolveStatements.spec.ts.snap | 32 +++++++++++++++++- src/types/resolveExpression.ts | 33 ++++++++++++------- ...unced-field-in-type-that-is-too-big-2.tact | 28 ++++++++++++++++ ...bounced-field-in-type-that-is-too-big.tact | 16 +++++++++ ...sage-of-bounced-field-that-is-too-big.tact | 14 ++++++++ 6 files changed, 111 insertions(+), 13 deletions(-) create mode 100644 src/types/stmts-failed/usage-of-bounced-field-in-type-that-is-too-big-2.tact create mode 100644 src/types/stmts-failed/usage-of-bounced-field-in-type-that-is-too-big.tact create mode 100644 src/types/stmts-failed/usage-of-bounced-field-that-is-too-big.tact diff --git a/CHANGELOG.md b/CHANGELOG.md index 41d5e7e0d..3151732d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Docs: complete overhaul of the exit codes page: PR [#978](https://github.com/tact-lang/tact/pull/978) - Docs: enhanced Jettons Cookbook page: PR [#944](https://github.com/tact-lang/tact/pull/944) - Error codes in the report are now formatted as a list: PR [#1051](https://github.com/tact-lang/tact/pull/1051) +- Clarify error message for bounced types from which accessed a field that does not fit in 224 bytes: PR [#1111](https://github.com/tact-lang/tact/pull/1111) - Docs: note that `compilables/` can sometimes be used over `wrappers/` in Blueprint projects: PR [#1112](https://github.com/tact-lang/tact/pull/1112) ### Fixed diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap index 7f6b8009a..b0d5b83ef 100644 --- a/src/types/__snapshots__/resolveStatements.spec.ts.snap +++ b/src/types/__snapshots__/resolveStatements.spec.ts.snap @@ -81,7 +81,7 @@ Line 10, col 5: `; exports[`resolveStatements should fail statements for bounced-type-is-smaller 1`] = ` -":23:22: Type bounced<"A"> does not have a field named "c" +":23:22: Maximum size of the bounced message is 224 bytes, but the "c" field of type "A" cannot fit into it due to the size of previous fields or its own size, so it cannot be accessed. Make the type of the fields before this one smaller, or reduce the type of this field so that it fits into 224 bytes Line 23, col 22: 22 | let y: Bool = src.b; > 23 | let z: Int = src.c; @@ -1238,6 +1238,36 @@ Line 9, col 5: " `; +exports[`resolveStatements should fail statements for usage-of-bounced-field-in-type-that-is-too-big 1`] = ` +":14:29: Maximum size of the bounced message is 224 bytes, but the "amount" field of type "Withdraw" cannot fit into it due to the size of previous fields or its own size, so it cannot be accessed. Make the type of the fields before this one smaller, or reduce the type of this field so that it fits into 224 bytes +Line 14, col 29: + 13 | self.balance += msg.data; // ok +> 14 | self.balance += msg.amount; + ^~~~~~ + 15 | } +" +`; + +exports[`resolveStatements should fail statements for usage-of-bounced-field-in-type-that-is-too-big-2 1`] = ` +":26:29: Maximum size of the bounced message is 224 bytes, but the "amount" field of type "Withdraw" cannot fit into it due to the size of previous fields or its own size, so it cannot be accessed. Make the type of the fields before this one smaller, or reduce the type of this field so that it fits into 224 bytes +Line 26, col 29: + 25 | +> 26 | self.balance += msg.amount; + ^~~~~~ + 27 | } +" +`; + +exports[`resolveStatements should fail statements for usage-of-bounced-field-that-is-too-big 1`] = ` +":12:29: Maximum size of the bounced message is 224 bytes, but the "amount" field of type "Withdraw" cannot fit into it because its too big, so it cannot be accessed. Reduce the type of this field so that it fits into 224 bytes +Line 12, col 29: + 11 | bounced(msg: bounced){ +> 12 | self.balance += msg.amount; + ^~~~~~ + 13 | } +" +`; + exports[`resolveStatements should fail statements for var-does-not-exist 1`] = ` ":5:9: Unable to resolve id 'nonExistentVariable' Line 5, col 9: diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts index 2500cffca..f3d3e9c6e 100644 --- a/src/types/resolveExpression.ts +++ b/src/types/resolveExpression.ts @@ -26,12 +26,7 @@ import { hasStaticConstant, hasStaticFunction, } from "./resolveDescriptors"; -import { - FieldDescription, - printTypeRef, - TypeRef, - typeRefEquals, -} from "./types"; +import { printTypeRef, TypeRef, typeRefEquals } from "./types"; import { StatementContext } from "./resolveStatements"; import { MapFunctions } from "../abi/map"; import { GlobalFunctions } from "../abi/global"; @@ -419,16 +414,30 @@ function resolveFieldAccess( } // Find field - let fields: FieldDescription[]; - const srcT = getType(ctx, src.name); - fields = srcT.fields; - if (src.kind === "ref_bounced") { - fields = fields.slice(0, srcT.partialFieldCount); + const fieldIndex = srcT.fields.findIndex((v) => eqNames(v.name, exp.field)); + const field = fieldIndex !== -1 ? srcT.fields[fieldIndex] : undefined; + + // If we found a field of bounced, check if the field doesn't fit in 224 bytes and cannot be accessed + if ( + src.kind === "ref_bounced" && + field && + fieldIndex >= srcT.partialFieldCount + ) { + if (srcT.fields.length === 1) { + throwCompilationError( + `Maximum size of the bounced message is 224 bytes, but the ${idTextErr(exp.field)} field of type ${idTextErr(src.name)} cannot fit into it because its too big, so it cannot be accessed. Reduce the type of this field so that it fits into 224 bytes`, + exp.field.loc, + ); + } + + throwCompilationError( + `Maximum size of the bounced message is 224 bytes, but the ${idTextErr(exp.field)} field of type ${idTextErr(src.name)} cannot fit into it due to the size of previous fields or its own size, so it cannot be accessed. Make the type of the fields before this one smaller, or reduce the type of this field so that it fits into 224 bytes`, + exp.field.loc, + ); } - const field = fields.find((v) => eqNames(v.name, exp.field)); const cst = srcT.constants.find((v) => eqNames(v.name, exp.field)); if (!field && !cst) { const typeStr = diff --git a/src/types/stmts-failed/usage-of-bounced-field-in-type-that-is-too-big-2.tact b/src/types/stmts-failed/usage-of-bounced-field-in-type-that-is-too-big-2.tact new file mode 100644 index 000000000..cbfb1267c --- /dev/null +++ b/src/types/stmts-failed/usage-of-bounced-field-in-type-that-is-too-big-2.tact @@ -0,0 +1,28 @@ +primitive Int; +trait BaseTrait { } + +message Withdraw { + data128: Int as uint128; // 128 + data64: Int as uint64; // 192 + data16: Int as uint16; // 208 + data8: Int as uint8; // 216 + data4: Int as uint4; // 220 + data4_2: Int as uint4; // 224 + + amount: Int as uint128; +} + +contract Fund { + balance: Int as uint256 = 0; + + bounced(msg: bounced){ + self.balance += msg.data128; // ok + self.balance += msg.data64; // ok + self.balance += msg.data16; // ok + self.balance += msg.data8; // ok + self.balance += msg.data4; // ok + self.balance += msg.data4_2; // ok + + self.balance += msg.amount; + } +} diff --git a/src/types/stmts-failed/usage-of-bounced-field-in-type-that-is-too-big.tact b/src/types/stmts-failed/usage-of-bounced-field-in-type-that-is-too-big.tact new file mode 100644 index 000000000..1632e8ecc --- /dev/null +++ b/src/types/stmts-failed/usage-of-bounced-field-in-type-that-is-too-big.tact @@ -0,0 +1,16 @@ +primitive Int; +trait BaseTrait { } + +message Withdraw { + data: Int as uint128; + amount: Int as uint128; // exceeds 224 bytes +} + +contract Fund { + balance: Int as uint256 = 0; + + bounced(msg: bounced){ + self.balance += msg.data; // ok + self.balance += msg.amount; + } +} diff --git a/src/types/stmts-failed/usage-of-bounced-field-that-is-too-big.tact b/src/types/stmts-failed/usage-of-bounced-field-that-is-too-big.tact new file mode 100644 index 000000000..ed45f8e0f --- /dev/null +++ b/src/types/stmts-failed/usage-of-bounced-field-that-is-too-big.tact @@ -0,0 +1,14 @@ +primitive Int; +trait BaseTrait { } + +message Withdraw { + amount: Int as uint256; // too big +} + +contract Fund { + balance: Int as uint256 = 0; + + bounced(msg: bounced){ + self.balance += msg.amount; + } +}