From a07f9d6bcf5d7da15fa2a395b1e8ac92a3137b12 Mon Sep 17 00:00:00 2001 From: Daniil Sedov Date: Fri, 16 Aug 2024 13:40:55 +0300 Subject: [PATCH] Forbid `remainder` fields in the middle of the struct (#697) * feat: forbid `remaining` fields in the middle of the struct * chore: update changelog * chore(changelog): move from changed to fixed --- CHANGELOG.md | 2 + .../resolveDescriptors.spec.ts.snap | 322 ++++++++++++++++++ src/types/resolveDescriptors.spec.ts | 7 +- src/types/resolveSignatures.ts | 10 + .../struct-decl-remainder-in-the-middle.tact | 10 + src/types/test/struct-decl-remainder.tact | 10 + 6 files changed, 358 insertions(+), 3 deletions(-) create mode 100644 src/types/test-failed/struct-decl-remainder-in-the-middle.tact create mode 100644 src/types/test/struct-decl-remainder.tact diff --git a/CHANGELOG.md b/CHANGELOG.md index 243ae77fd..8558b555d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Remainder fields in the middle of a struct are now forbidden: PR [#697](https://github.com/tact-lang/tact/pull/697) + ## [1.4.3] - 2024-08-16 ### Fixed diff --git a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap index 6cc94d1d8..ba5ec2543 100644 --- a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap +++ b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap @@ -454,6 +454,8 @@ Line 4, col 8: " `; +exports[`resolveDescriptors should fail descriptors for struct-decl-remainder-in-the-middle 1`] = `"The "remainder" field can only be the last field of the struct"`; + exports[`resolveDescriptors should fail descriptors for struct-decl-self-reference 1`] = ` ":4:8: Self-referencing types are not supported: type "A" refers to itself in its definition Line 4, col 8: @@ -8296,6 +8298,326 @@ exports[`resolveDescriptors should resolve descriptors for struct-decl-non-rec-t exports[`resolveDescriptors should resolve descriptors for struct-decl-non-rec-types 2`] = `{}`; +exports[`resolveDescriptors should resolve descriptors for struct-decl-remainder 1`] = ` +{ + "BaseTrait": { + "ast": { + "attributes": [], + "declarations": [], + "id": 2, + "kind": "trait", + "loc": trait BaseTrait { }, + "name": { + "id": 1, + "kind": "id", + "loc": BaseTrait, + "text": "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, + }, + "Cell": { + "ast": { + "id": 6, + "kind": "primitive_type_decl", + "loc": primitive Cell;, + "name": { + "id": 5, + "kind": "type_id", + "loc": Cell, + "text": "Cell", + }, + }, + "constants": [], + "dependsOn": [], + "fields": [], + "functions": Map {}, + "header": null, + "init": null, + "interfaces": [], + "kind": "primitive_type_decl", + "name": "Cell", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": null, + "tlb": null, + "traits": [], + "uid": 26294, + }, + "Int": { + "ast": { + "id": 4, + "kind": "primitive_type_decl", + "loc": primitive Int;, + "name": { + "id": 3, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + "constants": [], + "dependsOn": [], + "fields": [], + "functions": Map {}, + "header": null, + "init": null, + "interfaces": [], + "kind": "primitive_type_decl", + "name": "Int", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": null, + "tlb": null, + "traits": [], + "uid": 38154, + }, + "Test": { + "ast": { + "fields": [ + { + "as": null, + "id": 10, + "initializer": null, + "kind": "field_decl", + "loc": a: Int, + "name": { + "id": 8, + "kind": "id", + "loc": a, + "text": "a", + }, + "type": { + "id": 9, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + { + "as": null, + "id": 13, + "initializer": null, + "kind": "field_decl", + "loc": b: Int, + "name": { + "id": 11, + "kind": "id", + "loc": b, + "text": "b", + }, + "type": { + "id": 12, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + { + "as": { + "id": 16, + "kind": "id", + "loc": remaining, + "text": "remaining", + }, + "id": 17, + "initializer": null, + "kind": "field_decl", + "loc": s: Cell as remaining, + "name": { + "id": 14, + "kind": "id", + "loc": s, + "text": "s", + }, + "type": { + "id": 15, + "kind": "type_id", + "loc": Cell, + "text": "Cell", + }, + }, + ], + "id": 18, + "kind": "struct_decl", + "loc": struct Test { + a: Int; + b: Int; + s: Cell as remaining; +}, + "name": { + "id": 7, + "kind": "type_id", + "loc": Test, + "text": "Test", + }, + }, + "constants": [], + "dependsOn": [], + "fields": [ + { + "abi": { + "name": "a", + "type": { + "format": 257, + "kind": "simple", + "optional": false, + "type": "int", + }, + }, + "as": null, + "ast": { + "as": null, + "id": 10, + "initializer": null, + "kind": "field_decl", + "loc": a: Int, + "name": { + "id": 8, + "kind": "id", + "loc": a, + "text": "a", + }, + "type": { + "id": 9, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + "default": undefined, + "index": 0, + "loc": a: Int, + "name": "a", + "type": { + "kind": "ref", + "name": "Int", + "optional": false, + }, + }, + { + "abi": { + "name": "b", + "type": { + "format": 257, + "kind": "simple", + "optional": false, + "type": "int", + }, + }, + "as": null, + "ast": { + "as": null, + "id": 13, + "initializer": null, + "kind": "field_decl", + "loc": b: Int, + "name": { + "id": 11, + "kind": "id", + "loc": b, + "text": "b", + }, + "type": { + "id": 12, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + "default": undefined, + "index": 1, + "loc": b: Int, + "name": "b", + "type": { + "kind": "ref", + "name": "Int", + "optional": false, + }, + }, + { + "abi": { + "name": "s", + "type": { + "format": "remainder", + "kind": "simple", + "optional": false, + "type": "cell", + }, + }, + "as": "remaining", + "ast": { + "as": { + "id": 16, + "kind": "id", + "loc": remaining, + "text": "remaining", + }, + "id": 17, + "initializer": null, + "kind": "field_decl", + "loc": s: Cell as remaining, + "name": { + "id": 14, + "kind": "id", + "loc": s, + "text": "s", + }, + "type": { + "id": 15, + "kind": "type_id", + "loc": Cell, + "text": "Cell", + }, + }, + "default": undefined, + "index": 2, + "loc": s: Cell as remaining, + "name": "s", + "type": { + "kind": "ref", + "name": "Cell", + "optional": false, + }, + }, + ], + "functions": Map {}, + "header": null, + "init": null, + "interfaces": [], + "kind": "struct", + "name": "Test", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": "Test{a:int257,b:int257,s:remainder}", + "tlb": "_ a:int257 b:int257 s:remainder = Test", + "traits": [], + "uid": 44104, + }, +} +`; + +exports[`resolveDescriptors should resolve descriptors for struct-decl-remainder 2`] = `{}`; + exports[`resolveDescriptors should resolve descriptors for trait-base 1`] = ` { "BaseTrait": { diff --git a/src/types/resolveDescriptors.spec.ts b/src/types/resolveDescriptors.spec.ts index 923fbf57f..74a34e0c2 100644 --- a/src/types/resolveDescriptors.spec.ts +++ b/src/types/resolveDescriptors.spec.ts @@ -41,9 +41,10 @@ describe("resolveDescriptors", () => { [], ); ctx = featureEnable(ctx, "external"); - expect(() => - resolveDescriptors(ctx), - ).toThrowErrorMatchingSnapshot(); + expect(() => { + ctx = resolveDescriptors(ctx); + ctx = resolveSignatures(ctx); + }).toThrowErrorMatchingSnapshot(); }); } }); diff --git a/src/types/resolveSignatures.ts b/src/types/resolveSignatures.ts index b6fc5b314..083ef8653 100644 --- a/src/types/resolveSignatures.ts +++ b/src/types/resolveSignatures.ts @@ -165,6 +165,16 @@ export function resolveSignatures(ctx: CompilerContext) { if (t.kind !== "struct") { throwInternalCompilerError(`Unsupported type: ${name}`); } + + // Check for no "remainder" in the middle of the struct + for (const field of t.fields.slice(0, -1)) { + if (field.as === "remaining") { + throwCompilationError( + `The "remainder" field can only be the last field of the struct`, + ); + } + } + const fields = t.fields.map((v) => createTLBField(v.abi)); // Calculate signature and method id diff --git a/src/types/test-failed/struct-decl-remainder-in-the-middle.tact b/src/types/test-failed/struct-decl-remainder-in-the-middle.tact new file mode 100644 index 000000000..6a1dcfe52 --- /dev/null +++ b/src/types/test-failed/struct-decl-remainder-in-the-middle.tact @@ -0,0 +1,10 @@ +trait BaseTrait { } + +primitive Int; +primitive Cell; + +struct Test { + a: Int; + s: Cell as remaining; + b: Int; +} \ No newline at end of file diff --git a/src/types/test/struct-decl-remainder.tact b/src/types/test/struct-decl-remainder.tact new file mode 100644 index 000000000..d5b36c557 --- /dev/null +++ b/src/types/test/struct-decl-remainder.tact @@ -0,0 +1,10 @@ +trait BaseTrait { } + +primitive Int; +primitive Cell; + +struct Test { + a: Int; + b: Int; + s: Cell as remaining; +} \ No newline at end of file