From f9e004682fc9cc794fcb1150a653e246c61287db Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Tue, 3 Dec 2024 21:01:39 +0400 Subject: [PATCH 1/7] feat: clarify error message for bounced types from which accessed a field that does not fit in 224 bytes --- .../resolveStatements.spec.ts.snap | 32 ++++++++++++++++++- src/types/resolveExpression.ts | 20 ++++++++++++ ...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 ++++++++ 5 files changed, 109 insertions(+), 1 deletion(-) 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/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap index 7f6b8009a..9d15d5f72 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 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 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 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 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..6ac4862a8 100644 --- a/src/types/resolveExpression.ts +++ b/src/types/resolveExpression.ts @@ -450,6 +450,26 @@ function resolveFieldAccess( } } + // If it is bounced, check all fields of T + if (src.kind === "ref_bounced") { + const field = srcT.fields.find((v) => eqNames(v.name, exp.field)); + + // If it has field, it means that this field doesn't fit into bounced message + if (field !== undefined) { + if (srcT.fields.length == 1) { + throwCompilationError( + `Maximum size of the bounced message is 224 bytes, but the ${idTextErr(exp.field)} field 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 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, + ); + } + } + throwCompilationError( `Type ${typeStr} does not have a field named ${idTextErr(exp.field)}`, exp.field.loc, 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; + } +} From 367cecc5509982dcf31ce71054e87a475cfe05cf Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Tue, 3 Dec 2024 21:14:40 +0400 Subject: [PATCH 2/7] include type name to error message --- src/types/__snapshots__/resolveStatements.spec.ts.snap | 8 ++++---- src/types/resolveExpression.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap index 9d15d5f72..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: Maximum size of the bounced message is 224 bytes, but the "c" field 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 +":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; @@ -1239,7 +1239,7 @@ 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 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 +":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; @@ -1249,7 +1249,7 @@ Line 14, col 29: `; 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 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 +":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; @@ -1259,7 +1259,7 @@ Line 26, col 29: `; 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 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 +":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; diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts index 6ac4862a8..dd8106ffa 100644 --- a/src/types/resolveExpression.ts +++ b/src/types/resolveExpression.ts @@ -458,13 +458,13 @@ function resolveFieldAccess( if (field !== undefined) { if (srcT.fields.length == 1) { throwCompilationError( - `Maximum size of the bounced message is 224 bytes, but the ${idTextErr(exp.field)} field 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`, + `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 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`, + `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, ); } From 7e10f91ba2551fa23e74f6d85372c57dd824c158 Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Tue, 3 Dec 2024 22:27:40 +0400 Subject: [PATCH 3/7] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d00388b5..03ee70e6a 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) ### Fixed From 8c22840ae0fd8af999aa228053e6e91cc919685a Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:44:30 +0400 Subject: [PATCH 4/7] remove unnecessary `fields.slice(0, srcT.partialFieldCount)` and use indexes --- src/types/resolveExpression.ts | 53 ++++++++++++++-------------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts index dd8106ffa..1e89c4cad 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 = srcT.fields.at(fieldIndex); + + // 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 = @@ -450,26 +459,6 @@ function resolveFieldAccess( } } - // If it is bounced, check all fields of T - if (src.kind === "ref_bounced") { - const field = srcT.fields.find((v) => eqNames(v.name, exp.field)); - - // If it has field, it means that this field doesn't fit into bounced message - if (field !== undefined) { - 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, - ); - } - } - throwCompilationError( `Type ${typeStr} does not have a field named ${idTextErr(exp.field)}`, exp.field.loc, From 2521d4069b4d9e5666317250519af2d9da811149 Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:01:10 +0400 Subject: [PATCH 5/7] add extra check before `srcT.fields.at(fieldIndex)` --- src/types/resolveExpression.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts index 1e89c4cad..5993c0f35 100644 --- a/src/types/resolveExpression.ts +++ b/src/types/resolveExpression.ts @@ -417,7 +417,7 @@ function resolveFieldAccess( const srcT = getType(ctx, src.name); const fieldIndex = srcT.fields.findIndex((v) => eqNames(v.name, exp.field)); - const field = srcT.fields.at(fieldIndex); + const field = fieldIndex != -1 ? srcT.fields.at(fieldIndex) : undefined; // If we found a field of bounced, check if the field doesn't fit in 224 bytes and cannot be accessed if ( From fda0fdb1b0450294e9b38d53457e441d042fc667 Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:02:15 +0400 Subject: [PATCH 6/7] use square bracket to access element instead `at` method --- src/types/resolveExpression.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts index 5993c0f35..34760dc93 100644 --- a/src/types/resolveExpression.ts +++ b/src/types/resolveExpression.ts @@ -417,7 +417,7 @@ function resolveFieldAccess( const srcT = getType(ctx, src.name); const fieldIndex = srcT.fields.findIndex((v) => eqNames(v.name, exp.field)); - const field = fieldIndex != -1 ? srcT.fields.at(fieldIndex) : undefined; + 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 ( From 588cb6f80837a3d376f7d31b420dc1034cc24d17 Mon Sep 17 00:00:00 2001 From: i582 <51853996+i582@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:02:45 +0400 Subject: [PATCH 7/7] use !== instead != --- src/types/resolveExpression.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts index 34760dc93..f3d3e9c6e 100644 --- a/src/types/resolveExpression.ts +++ b/src/types/resolveExpression.ts @@ -417,7 +417,7 @@ function resolveFieldAccess( const srcT = getType(ctx, src.name); const fieldIndex = srcT.fields.findIndex((v) => eqNames(v.name, exp.field)); - const field = fieldIndex != -1 ? srcT.fields[fieldIndex] : undefined; + 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 (