Skip to content

Commit

Permalink
Forbid remainder fields in the middle of the struct (#697)
Browse files Browse the repository at this point in the history
* feat: forbid `remaining` fields in the middle of the struct

* chore: update changelog

* chore(changelog): move from changed to fixed
  • Loading branch information
Gusarich authored Aug 16, 2024
1 parent deed501 commit a07f9d6
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
322 changes: 322 additions & 0 deletions src/types/__snapshots__/resolveDescriptors.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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`] = `
"<unknown>:4:8: Self-referencing types are not supported: type "A" refers to itself in its definition
Line 4, col 8:
Expand Down Expand Up @@ -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<cell>}",
"tlb": "_ a:int257 b:int257 s:remainder<cell> = Test",
"traits": [],
"uid": 44104,
},
}
`;
exports[`resolveDescriptors should resolve descriptors for struct-decl-remainder 2`] = `{}`;
exports[`resolveDescriptors should resolve descriptors for trait-base 1`] = `
{
"BaseTrait": {
Expand Down
7 changes: 4 additions & 3 deletions src/types/resolveDescriptors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ describe("resolveDescriptors", () => {
[],
);
ctx = featureEnable(ctx, "external");
expect(() =>
resolveDescriptors(ctx),
).toThrowErrorMatchingSnapshot();
expect(() => {
ctx = resolveDescriptors(ctx);
ctx = resolveSignatures(ctx);
}).toThrowErrorMatchingSnapshot();
});
}
});
10 changes: 10 additions & 0 deletions src/types/resolveSignatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions src/types/test-failed/struct-decl-remainder-in-the-middle.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
trait BaseTrait { }

primitive Int;
primitive Cell;

struct Test {
a: Int;
s: Cell as remaining;
b: Int;
}
10 changes: 10 additions & 0 deletions src/types/test/struct-decl-remainder.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
trait BaseTrait { }

primitive Int;
primitive Cell;

struct Test {
a: Int;
b: Int;
s: Cell as remaining;
}

0 comments on commit a07f9d6

Please sign in to comment.