diff --git a/CHANGELOG.md b/CHANGELOG.md index c7500d41e..f9c237f96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Optional types for `self` argument in `extends mutates` functions are now allowed: PR [#854](https://github.com/tact-lang/tact/pull/854) + ### Fixed - Collisions in getter method ids are now handled and reported properly: PR [#875](https://github.com/tact-lang/tact/pull/875) diff --git a/src/generator/writers/writeExpression.ts b/src/generator/writers/writeExpression.ts index 2ee49d62d..df1e33845 100644 --- a/src/generator/writers/writeExpression.ts +++ b/src/generator/writers/writeExpression.ts @@ -558,13 +558,6 @@ export function writeExpression(f: AstExpression, wCtx: WriterContext): string { // Reference type if (selfTyRef.kind === "ref") { - if (selfTyRef.optional) { - throwCompilationError( - `Cannot call function of non - direct type: "${printTypeRef(selfTyRef)}"`, - f.loc, - ); - } - // Render function call const selfTy = getType(wCtx.ctx, selfTyRef.name); diff --git a/src/generator/writers/writeFunction.ts b/src/generator/writers/writeFunction.ts index 15216d0c4..79cccaa43 100644 --- a/src/generator/writers/writeFunction.ts +++ b/src/generator/writers/writeFunction.ts @@ -503,7 +503,7 @@ function writeCondition( export function writeFunction(f: FunctionDescription, ctx: WriterContext) { // Resolve self - const self = f.self ? getType(ctx.ctx, f.self) : null; + const self = f.self?.kind === "ref" ? getType(ctx.ctx, f.self.name) : null; // Write function header let returns: string = resolveFuncType(f.returns, ctx); @@ -683,7 +683,7 @@ function writeNonMutatingFunction( export function writeGetter(f: FunctionDescription, ctx: WriterContext) { // Render tensors - const self = f.self !== null ? getType(ctx.ctx, f.self) : null; + const self = f.self?.kind === "ref" ? getType(ctx.ctx, f.self.name) : null; if (!self) { throw new Error(`No self type for getter ${idTextErr(f.name)}`); // Impossible } diff --git a/src/storage/__snapshots__/resolveAllocation.spec.ts.snap b/src/storage/__snapshots__/resolveAllocation.spec.ts.snap index 8fef304b6..274c24bff 100644 --- a/src/storage/__snapshots__/resolveAllocation.spec.ts.snap +++ b/src/storage/__snapshots__/resolveAllocation.spec.ts.snap @@ -2293,7 +2293,11 @@ exports[`resolveAllocation should write program 1`] = ` "returns": { "kind": "void", }, - "self": "Sample", + "self": { + "kind": "ref", + "name": "Sample", + "optional": false, + }, }, }, "header": null, diff --git a/src/test/e2e-emulated/contracts/mutating-method-chaining.tact b/src/test/e2e-emulated/contracts/mutating-methods.tact similarity index 73% rename from src/test/e2e-emulated/contracts/mutating-method-chaining.tact rename to src/test/e2e-emulated/contracts/mutating-methods.tact index d7e8b2627..aac3bf439 100644 --- a/src/test/e2e-emulated/contracts/mutating-method-chaining.tact +++ b/src/test/e2e-emulated/contracts/mutating-methods.tact @@ -1,8 +1,17 @@ +asm(value key self keySize) extends mutates fun nativeUdictStoreUint(self: Cell?, keySize: Int, key: Int, value: Slice) { DICTUSET } + extends mutates fun multiply(self: Int, x: Int): Int { self *= x; return self; } +extends fun multiplyExtends(self: Int?, x: Int): Int? { + if (self == null) { + return null; + } + return self!! * x; +} + struct Foo { s: Slice; } @@ -64,4 +73,15 @@ contract Tester { self.s.loadUint(1); return self.s.loadUint(3); } + + get fun test10(dict: Cell?): Cell? { + dict.nativeUdictStoreUint(8, 123, rawSlice("456")); + return dict; + } + + get fun test11(x: Int?): Int? { + x.multiplyExtends(2); + x.multiplyExtends(2).multiplyExtends(3); + return x.multiplyExtends(2).multiplyExtends(3); + } } \ No newline at end of file diff --git a/src/test/e2e-emulated/mutating-method-chaining.spec.ts b/src/test/e2e-emulated/mutating-methods.spec.ts similarity index 54% rename from src/test/e2e-emulated/mutating-method-chaining.spec.ts rename to src/test/e2e-emulated/mutating-methods.spec.ts index 9485a6588..b1e929510 100644 --- a/src/test/e2e-emulated/mutating-method-chaining.spec.ts +++ b/src/test/e2e-emulated/mutating-methods.spec.ts @@ -1,6 +1,6 @@ -import { toNano } from "@ton/core"; +import { beginCell, BitString, Dictionary, toNano } from "@ton/core"; import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox"; -import { Tester } from "./contracts/output/mutating-method-chaining_Tester"; +import { Tester } from "./contracts/output/mutating-methods_Tester"; import "@ton/test-utils"; describe("bugs", () => { @@ -52,5 +52,41 @@ describe("bugs", () => { expect(await contract.getTest7()).toBe(42n); expect(await contract.getTest8()).toBe(5n); expect(await contract.getTest9()).toBe(5n); + + // Test `extends mutates` function with optional self param + { + // Non-empty dictionary + const d = Dictionary.empty( + Dictionary.Keys.Uint(8), + Dictionary.Values.BitString(12), + ); + d.set(1, new BitString(Buffer.from("1234", "hex"), 0, 12)); + const c = beginCell().storeDictDirect(d).endCell(); + const c2 = await contract.getTest10(c); + const d2 = c2 + ?.beginParse() + .loadDictDirect( + Dictionary.Keys.Uint(8), + Dictionary.Values.BitString(12), + ); + expect(d2?.size).toBe(2); + expect(d2?.get(1)?.toString()).toBe("123"); + expect(d2?.get(123)?.toString()).toBe("456"); + } + { + // Empty dictionary + const c = await contract.getTest10(null); + const d = c + ?.beginParse() + .loadDictDirect( + Dictionary.Keys.Uint(8), + Dictionary.Values.BitString(12), + ); + expect(d?.size).toBe(1); + expect(d?.get(123)?.toString()).toBe("456"); + } + + expect(await contract.getTest11(1n)).toBe(6n); + expect(await contract.getTest11(2n)).toBe(12n); }); }); diff --git a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap index 30b43c1b0..dd0d9ca5d 100644 --- a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap +++ b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap @@ -769,7 +769,11 @@ exports[`resolveDescriptors should resolve descriptors for asm-extends-fun 1`] = "name": "Cell", "optional": false, }, - "self": "Slice", + "self": { + "kind": "ref", + "name": "Slice", + "optional": false, + }, }, }, "header": null, @@ -4505,7 +4509,11 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr "name": "Int", "optional": false, }, - "self": "T", + "self": { + "kind": "ref", + "name": "T", + "optional": false, + }, }, }, "header": null, @@ -4682,7 +4690,11 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr "name": "Int", "optional": false, }, - "self": "TestContract", + "self": { + "kind": "ref", + "name": "TestContract", + "optional": false, + }, }, }, "header": null, @@ -4832,7 +4844,11 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr "name": "Int", "optional": false, }, - "self": "T", + "self": { + "kind": "ref", + "name": "T", + "optional": false, + }, }, }, "header": null, @@ -5073,7 +5089,11 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr "name": "Int", "optional": false, }, - "self": "T", + "self": { + "kind": "ref", + "name": "T", + "optional": false, + }, }, }, "header": null, @@ -5250,7 +5270,11 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr "name": "Int", "optional": false, }, - "self": "TestContract", + "self": { + "kind": "ref", + "name": "TestContract", + "optional": false, + }, }, }, "header": null, @@ -5428,7 +5452,11 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr "name": "Int", "optional": false, }, - "self": "T", + "self": { + "kind": "ref", + "name": "T", + "optional": false, + }, }, }, "header": null, @@ -5485,7 +5513,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr exports[`resolveDescriptors should resolve descriptors for contract-getter-override-virtual 2`] = `[]`; -exports[`resolveDescriptors should resolve descriptors for init-vars-analysis-uninit-storage-vars 1`] = ` +exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self 1`] = ` [ { "ast": { @@ -5502,168 +5530,924 @@ exports[`resolveDescriptors should resolve descriptors for init-vars-analysis-un "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, - }, - { - "ast": { - "id": 4, - "kind": "primitive_type_decl", - "loc": primitive Bool;, - "name": { - "id": 3, - "kind": "type_id", - "loc": Bool, - "text": "Bool", - }, - }, - "constants": [], - "dependsOn": [], - "fields": [], - "functions": Map {}, - "header": null, - "init": null, - "interfaces": [], - "kind": "primitive_type_decl", - "name": "Bool", - "origin": "user", - "partialFieldCount": 0, - "receivers": [], - "signature": null, - "tlb": null, - "traits": [], - "uid": 33424, - }, - { - "ast": { - "attributes": [], - "declarations": [], - "id": 6, - "kind": "trait", - "loc": trait BaseTrait { - + "functions": Map { + "test_extends" => { + "ast": { + "attributes": [ + { + "loc": extends, + "type": "extends", + }, + ], + "id": 26, + "kind": "function_def", + "loc": extends fun test_extends(self: Int, y: Int?): Int { + return 123; }, - "name": { - "id": 5, - "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, - }, - { - "ast": { - "attributes": [], - "declarations": [ - { - "as": null, - "id": 10, - "initializer": null, - "kind": "field_decl", - "loc": a: Int, "name": { - "id": 8, + "id": 15, "kind": "id", - "loc": a, - "text": "a", + "loc": test_extends, + "text": "test_extends", }, - "type": { - "id": 9, + "params": [ + { + "id": 19, + "kind": "typed_parameter", + "loc": self: Int, + "name": { + "id": 17, + "kind": "id", + "loc": self, + "text": "self", + }, + "type": { + "id": 18, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + { + "id": 23, + "kind": "typed_parameter", + "loc": y: Int?, + "name": { + "id": 20, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": { + "id": 22, + "kind": "optional_type", + "loc": Int?, + "typeArg": { + "id": 21, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + }, + ], + "return": { + "id": 16, "kind": "type_id", "loc": Int, "text": "Int", }, + "statements": [ + { + "expression": { + "base": 10, + "id": 24, + "kind": "number", + "loc": 123, + "value": 123n, + }, + "id": 25, + "kind": "statement_return", + "loc": return 123;, + }, + ], }, - { - "as": null, - "id": 13, - "initializer": null, - "kind": "field_decl", - "loc": b: Bool, - "name": { - "id": 11, - "kind": "id", - "loc": b, - "text": "b", - }, - "type": { - "id": 12, - "kind": "type_id", - "loc": Bool, - "text": "Bool", + "isAbstract": false, + "isGetter": false, + "isInline": false, + "isMutating": false, + "isOverride": false, + "isVirtual": false, + "name": "test_extends", + "origin": "user", + "params": [ + { + "loc": y: Int?, + "name": { + "id": 20, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": { + "kind": "ref", + "name": "Int", + "optional": true, + }, }, + ], + "returns": { + "kind": "ref", + "name": "Int", + "optional": false, }, - { - "id": 14, - "kind": "contract_init", - "loc": init() { - - }, - "params": [], - "statements": [], - }, - { - "attributes": [], - "id": 16, - "kind": "function_def", - "loc": fun hello() { - - }, - "name": { - "id": 15, - "kind": "id", - "loc": hello, - "text": "hello", - }, - "params": [], - "return": null, - "statements": [], + "self": { + "kind": "ref", + "name": "Int", + "optional": false, }, - { - "attributes": [], - "id": 18, + }, + "test_extends_self" => { + "ast": { + "attributes": [ + { + "loc": extends, + "type": "extends", + }, + ], + "id": 38, "kind": "function_def", - "loc": fun hello2() { - - }, + "loc": extends fun test_extends_self(self: Int?, y: Int): Int { + return 123; +}, "name": { - "id": 17, + "id": 27, "kind": "id", - "loc": hello2, - "text": "hello2", + "loc": test_extends_self, + "text": "test_extends_self", }, - "params": [], - "return": null, - "statements": [], - }, + "params": [ + { + "id": 32, + "kind": "typed_parameter", + "loc": self: Int?, + "name": { + "id": 29, + "kind": "id", + "loc": self, + "text": "self", + }, + "type": { + "id": 31, + "kind": "optional_type", + "loc": Int?, + "typeArg": { + "id": 30, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + }, + { + "id": 35, + "kind": "typed_parameter", + "loc": y: Int, + "name": { + "id": 33, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": { + "id": 34, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + ], + "return": { + "id": 28, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + "statements": [ + { + "expression": { + "base": 10, + "id": 36, + "kind": "number", + "loc": 123, + "value": 123n, + }, + "id": 37, + "kind": "statement_return", + "loc": return 123;, + }, + ], + }, + "isAbstract": false, + "isGetter": false, + "isInline": false, + "isMutating": false, + "isOverride": false, + "isVirtual": false, + "name": "test_extends_self", + "origin": "user", + "params": [ + { + "loc": y: Int, + "name": { + "id": 33, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": { + "kind": "ref", + "name": "Int", + "optional": false, + }, + }, + ], + "returns": { + "kind": "ref", + "name": "Int", + "optional": false, + }, + "self": { + "kind": "ref", + "name": "Int", + "optional": true, + }, + }, + "test_mutates" => { + "ast": { + "attributes": [ + { + "loc": extends, + "type": "extends", + }, + { + "loc": mutates, + "type": "mutates", + }, + ], + "id": 50, + "kind": "function_def", + "loc": extends mutates fun test_mutates(self: Int, y: Int?): Int { + return 123; +}, + "name": { + "id": 39, + "kind": "id", + "loc": test_mutates, + "text": "test_mutates", + }, + "params": [ + { + "id": 43, + "kind": "typed_parameter", + "loc": self: Int, + "name": { + "id": 41, + "kind": "id", + "loc": self, + "text": "self", + }, + "type": { + "id": 42, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + { + "id": 47, + "kind": "typed_parameter", + "loc": y: Int?, + "name": { + "id": 44, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": { + "id": 46, + "kind": "optional_type", + "loc": Int?, + "typeArg": { + "id": 45, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + }, + ], + "return": { + "id": 40, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + "statements": [ + { + "expression": { + "base": 10, + "id": 48, + "kind": "number", + "loc": 123, + "value": 123n, + }, + "id": 49, + "kind": "statement_return", + "loc": return 123;, + }, + ], + }, + "isAbstract": false, + "isGetter": false, + "isInline": false, + "isMutating": true, + "isOverride": false, + "isVirtual": false, + "name": "test_mutates", + "origin": "user", + "params": [ + { + "loc": y: Int?, + "name": { + "id": 44, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": { + "kind": "ref", + "name": "Int", + "optional": true, + }, + }, + ], + "returns": { + "kind": "ref", + "name": "Int", + "optional": false, + }, + "self": { + "kind": "ref", + "name": "Int", + "optional": false, + }, + }, + "test_mutates_self" => { + "ast": { + "attributes": [ + { + "loc": extends, + "type": "extends", + }, + { + "loc": mutates, + "type": "mutates", + }, + ], + "id": 62, + "kind": "function_def", + "loc": extends mutates fun test_mutates_self(self: Int?, y: Int): Int { + return 123; +}, + "name": { + "id": 51, + "kind": "id", + "loc": test_mutates_self, + "text": "test_mutates_self", + }, + "params": [ + { + "id": 56, + "kind": "typed_parameter", + "loc": self: Int?, + "name": { + "id": 53, + "kind": "id", + "loc": self, + "text": "self", + }, + "type": { + "id": 55, + "kind": "optional_type", + "loc": Int?, + "typeArg": { + "id": 54, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + }, + { + "id": 59, + "kind": "typed_parameter", + "loc": y: Int, + "name": { + "id": 57, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": { + "id": 58, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + ], + "return": { + "id": 52, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + "statements": [ + { + "expression": { + "base": 10, + "id": 60, + "kind": "number", + "loc": 123, + "value": 123n, + }, + "id": 61, + "kind": "statement_return", + "loc": return 123;, + }, + ], + }, + "isAbstract": false, + "isGetter": false, + "isInline": false, + "isMutating": true, + "isOverride": false, + "isVirtual": false, + "name": "test_mutates_self", + "origin": "user", + "params": [ + { + "loc": y: Int, + "name": { + "id": 57, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": { + "kind": "ref", + "name": "Int", + "optional": false, + }, + }, + ], + "returns": { + "kind": "ref", + "name": "Int", + "optional": false, + }, + "self": { + "kind": "ref", + "name": "Int", + "optional": true, + }, + }, + "test_mutates_self_opt" => { + "ast": { + "attributes": [ + { + "loc": extends, + "type": "extends", + }, + { + "loc": mutates, + "type": "mutates", + }, + ], + "id": 75, + "kind": "function_def", + "loc": extends mutates fun test_mutates_self_opt(self: Int?, y: Int): Int? { + return null; +}, + "name": { + "id": 63, + "kind": "id", + "loc": test_mutates_self_opt, + "text": "test_mutates_self_opt", + }, + "params": [ + { + "id": 69, + "kind": "typed_parameter", + "loc": self: Int?, + "name": { + "id": 66, + "kind": "id", + "loc": self, + "text": "self", + }, + "type": { + "id": 68, + "kind": "optional_type", + "loc": Int?, + "typeArg": { + "id": 67, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + }, + { + "id": 72, + "kind": "typed_parameter", + "loc": y: Int, + "name": { + "id": 70, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": { + "id": 71, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + ], + "return": { + "id": 65, + "kind": "optional_type", + "loc": Int?, + "typeArg": { + "id": 64, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + "statements": [ + { + "expression": { + "id": 73, + "kind": "null", + "loc": null, + }, + "id": 74, + "kind": "statement_return", + "loc": return null;, + }, + ], + }, + "isAbstract": false, + "isGetter": false, + "isInline": false, + "isMutating": true, + "isOverride": false, + "isVirtual": false, + "name": "test_mutates_self_opt", + "origin": "user", + "params": [ + { + "loc": y: Int, + "name": { + "id": 70, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": { + "kind": "ref", + "name": "Int", + "optional": false, + }, + }, + ], + "returns": { + "kind": "ref", + "name": "Int", + "optional": true, + }, + "self": { + "kind": "ref", + "name": "Int", + "optional": true, + }, + }, + }, + "header": null, + "init": null, + "interfaces": [], + "kind": "primitive_type_decl", + "name": "Int", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": null, + "tlb": null, + "traits": [], + "uid": 38154, + }, +] +`; + +exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self 2`] = ` +[ + { + "ast": { + "attributes": [], + "id": 14, + "kind": "function_def", + "loc": fun test(x: Int, y: Int?): Int { + return 123; +}, + "name": { + "id": 3, + "kind": "id", + "loc": test, + "text": "test", + }, + "params": [ + { + "id": 7, + "kind": "typed_parameter", + "loc": x: Int, + "name": { + "id": 5, + "kind": "id", + "loc": x, + "text": "x", + }, + "type": { + "id": 6, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + { + "id": 11, + "kind": "typed_parameter", + "loc": y: Int?, + "name": { + "id": 8, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": { + "id": 10, + "kind": "optional_type", + "loc": Int?, + "typeArg": { + "id": 9, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + }, + }, + ], + "return": { + "id": 4, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + "statements": [ + { + "expression": { + "base": 10, + "id": 12, + "kind": "number", + "loc": 123, + "value": 123n, + }, + "id": 13, + "kind": "statement_return", + "loc": return 123;, + }, + ], + }, + "isAbstract": false, + "isGetter": false, + "isInline": false, + "isMutating": false, + "isOverride": false, + "isVirtual": false, + "name": "test", + "origin": "user", + "params": [ + { + "loc": x: Int, + "name": { + "id": 5, + "kind": "id", + "loc": x, + "text": "x", + }, + "type": { + "kind": "ref", + "name": "Int", + "optional": false, + }, + }, + { + "loc": y: Int?, + "name": { + "id": 8, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": { + "kind": "ref", + "name": "Int", + "optional": true, + }, + }, + ], + "returns": { + "kind": "ref", + "name": "Int", + "optional": false, + }, + "self": null, + }, +] +`; + +exports[`resolveDescriptors should resolve descriptors for init-vars-analysis-uninit-storage-vars 1`] = ` +[ + { + "ast": { + "id": 2, + "kind": "primitive_type_decl", + "loc": primitive Int;, + "name": { + "id": 1, + "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, + }, + { + "ast": { + "id": 4, + "kind": "primitive_type_decl", + "loc": primitive Bool;, + "name": { + "id": 3, + "kind": "type_id", + "loc": Bool, + "text": "Bool", + }, + }, + "constants": [], + "dependsOn": [], + "fields": [], + "functions": Map {}, + "header": null, + "init": null, + "interfaces": [], + "kind": "primitive_type_decl", + "name": "Bool", + "origin": "user", + "partialFieldCount": 0, + "receivers": [], + "signature": null, + "tlb": null, + "traits": [], + "uid": 33424, + }, + { + "ast": { + "attributes": [], + "declarations": [], + "id": 6, + "kind": "trait", + "loc": trait BaseTrait { + +}, + "name": { + "id": 5, + "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, + }, + { + "ast": { + "attributes": [], + "declarations": [ + { + "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: Bool, + "name": { + "id": 11, + "kind": "id", + "loc": b, + "text": "b", + }, + "type": { + "id": 12, + "kind": "type_id", + "loc": Bool, + "text": "Bool", + }, + }, + { + "id": 14, + "kind": "contract_init", + "loc": init() { + + }, + "params": [], + "statements": [], + }, + { + "attributes": [], + "id": 16, + "kind": "function_def", + "loc": fun hello() { + + }, + "name": { + "id": 15, + "kind": "id", + "loc": hello, + "text": "hello", + }, + "params": [], + "return": null, + "statements": [], + }, + { + "attributes": [], + "id": 18, + "kind": "function_def", + "loc": fun hello2() { + + }, + "name": { + "id": 17, + "kind": "id", + "loc": hello2, + "text": "hello2", + }, + "params": [], + "return": null, + "statements": [], + }, ], "id": 19, "kind": "contract", @@ -5805,7 +6589,11 @@ exports[`resolveDescriptors should resolve descriptors for init-vars-analysis-un "returns": { "kind": "void", }, - "self": "HelloWorld", + "self": { + "kind": "ref", + "name": "HelloWorld", + "optional": false, + }, }, "hello2" => { "ast": { @@ -5837,7 +6625,11 @@ exports[`resolveDescriptors should resolve descriptors for init-vars-analysis-un "returns": { "kind": "void", }, - "self": "HelloWorld", + "self": { + "kind": "ref", + "name": "HelloWorld", + "optional": false, + }, }, }, "header": null, @@ -6682,7 +7474,11 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors "name": "Int", "optional": false, }, - "self": "Main", + "self": { + "kind": "ref", + "name": "Main", + "optional": false, + }, }, "hello2" => { "ast": { @@ -6739,7 +7535,11 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors "name": "Point", "optional": false, }, - "self": "Main", + "self": { + "kind": "ref", + "name": "Main", + "optional": false, + }, }, }, "header": null, @@ -6972,7 +7772,11 @@ exports[`resolveDescriptors should resolve descriptors for item-method 1`] = ` "name": "Int", "optional": false, }, - "self": "Int", + "self": { + "kind": "ref", + "name": "Int", + "optional": false, + }, }, }, "header": null, @@ -7320,7 +8124,11 @@ mutates extends native inc(self: Int): Int;, "name": "Int", "optional": false, }, - "self": "Int", + "self": { + "kind": "ref", + "name": "Int", + "optional": false, + }, }, }, "header": null, @@ -8090,7 +8898,11 @@ exports[`resolveDescriptors should resolve descriptors for map-value-as-coins 1` "name": "Int", "optional": false, }, - "self": "Main", + "self": { + "kind": "ref", + "name": "Main", + "optional": false, + }, }, }, "header": null, diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap index 6d4ca594c..6e92c641e 100644 --- a/src/types/__snapshots__/resolveStatements.spec.ts.snap +++ b/src/types/__snapshots__/resolveStatements.spec.ts.snap @@ -2268,6 +2268,223 @@ exports[`resolveStatements should resolve statements for expr-struct-constructio ] `; +exports[`resolveStatements should resolve statements for fun-extends-optional 1`] = ` +[ + [ + "y", + "Int?", + ], + [ + "null", + "", + ], + [ + "y == null", + "Bool", + ], + [ + "x", + "Int", + ], + [ + "x", + "Int", + ], + [ + "y", + "Int?", + ], + [ + "y!!", + "Int", + ], + [ + "x + y!!", + "Int", + ], + [ + "y", + "Int?", + ], + [ + "null", + "", + ], + [ + "y == null", + "Bool", + ], + [ + "self", + "Int", + ], + [ + "self", + "Int", + ], + [ + "y", + "Int?", + ], + [ + "y!!", + "Int", + ], + [ + "self + y!!", + "Int", + ], + [ + "self", + "Int?", + ], + [ + "null", + "", + ], + [ + "self == null", + "Bool", + ], + [ + "y", + "Int", + ], + [ + "self", + "Int?", + ], + [ + "self!!", + "Int", + ], + [ + "y", + "Int", + ], + [ + "self!! + y", + "Int", + ], + [ + "y", + "Int?", + ], + [ + "null", + "", + ], + [ + "y == null", + "Bool", + ], + [ + "self", + "Int", + ], + [ + "self", + "Int", + ], + [ + "y", + "Int?", + ], + [ + "y!!", + "Int", + ], + [ + "self", + "Int", + ], + [ + "self", + "Int?", + ], + [ + "null", + "", + ], + [ + "self == null", + "Bool", + ], + [ + "y", + "Int", + ], + [ + "self", + "Int?", + ], + [ + "self", + "Int?", + ], + [ + "self!!", + "Int", + ], + [ + "y", + "Int", + ], + [ + "self!! + y", + "Int", + ], + [ + "self", + "Int?", + ], + [ + "self!!", + "Int", + ], + [ + "self", + "Int?", + ], + [ + "null", + "", + ], + [ + "self == null", + "Bool", + ], + [ + "null", + "", + ], + [ + "self", + "Int?", + ], + [ + "self", + "Int?", + ], + [ + "self!!", + "Int", + ], + [ + "y", + "Int", + ], + [ + "self!! + y", + "Int", + ], + [ + "self", + "Int?", + ], +] +`; + exports[`resolveStatements should resolve statements for init-vars-analysis-with-if 1`] = ` [ [ diff --git a/src/types/resolveDescriptors.ts b/src/types/resolveDescriptors.ts index 8940dfa04..1247ba6be 100644 --- a/src/types/resolveDescriptors.ts +++ b/src/types/resolveDescriptors.ts @@ -626,7 +626,7 @@ export function resolveDescriptors(ctx: CompilerContext) { // function resolveFunctionDescriptor( - optSelf: string | null, + optSelf: TypeRef | null, a: | AstFunctionDef | AstNativeFunctionDecl @@ -746,7 +746,13 @@ export function resolveDescriptors(ctx: CompilerContext) { // Check virtual if (isVirtual) { - const t = types.get(self!)!; + if (self?.kind !== "ref") { + throwInternalCompilerError( + "Virtual functions must have a self parameter", + isVirtual.loc, + ); + } + const t = types.get(self.name!)!; if (t.kind !== "trait") { throwCompilationError( "Virtual functions must be defined within a trait", @@ -757,7 +763,13 @@ export function resolveDescriptors(ctx: CompilerContext) { // Check abstract if (isAbstract) { - const t = types.get(self!)!; + if (self?.kind !== "ref") { + throwInternalCompilerError( + "Abstract functions must have a self parameter", + isAbstract.loc, + ); + } + const t = types.get(self.name!)!; if (t.kind !== "trait") { throwCompilationError( "Abstract functions must be defined within a trait", @@ -767,7 +779,13 @@ export function resolveDescriptors(ctx: CompilerContext) { } if (isOverride) { - const t = types.get(self!)!; + if (self?.kind !== "ref") { + throwInternalCompilerError( + "Override functions must have a self parameter", + isOverride.loc, + ); + } + const t = types.get(self.name!)!; if (!["contract", "trait"].includes(t.kind)) { throwCompilationError( "Overridden functions must be defined within a contract or a trait", @@ -818,12 +836,6 @@ export function resolveDescriptors(ctx: CompilerContext) { firstParam.loc, ); } - if (firstParam.type.optional) { - throwCompilationError( - "Extend functions must have a non-optional type as the first parameter", - firstParam.loc, - ); - } if (!types.has(firstParam.type.name)) { throwCompilationError( "Type " + firstParam.type.name + " not found", @@ -832,7 +844,7 @@ export function resolveDescriptors(ctx: CompilerContext) { } // Update self and remove first parameter - self = firstParam.type.name; + self = firstParam.type; params = params.slice(1); } @@ -1031,8 +1043,16 @@ export function resolveDescriptors(ctx: CompilerContext) { d.kind === "function_decl" || d.kind === "asm_function_def" ) { - const f = resolveFunctionDescriptor(s.name, d, s.origin); - if (f.self !== s.name) { + const f = resolveFunctionDescriptor( + { + kind: "ref", + name: s.name, + optional: false, + }, + d, + s.origin, + ); + if (f.self?.kind !== "ref" || f.self.name !== s.name) { throwInternalCompilerError( `Function self must be ${s.name}`, ); // Impossible @@ -1614,7 +1634,11 @@ export function resolveDescriptors(ctx: CompilerContext) { // Register function contractOrTrait.functions.set(traitFunction.name, { ...traitFunction, - self: contractOrTrait.name, + self: { + kind: "ref", + name: contractOrTrait.name, + optional: false, + }, ast: cloneNode(traitFunction.ast), }); } @@ -1870,13 +1894,19 @@ export function resolveDescriptors(ctx: CompilerContext) { for (const a of ast.functions) { const r = resolveFunctionDescriptor(null, a, a.loc.origin); if (r.self) { - if (types.get(r.self)!.functions.has(r.name)) { + if (r.self.kind !== "ref") { + throwCompilationError( + `Wrong self type "${r.name}" for static function`, + r.ast.loc, + ); + } + if (types.get(r.self.name)!.functions.has(r.name)) { throwCompilationError( - `Function "${r.name}" already exists in type "${r.self}"`, + `Function "${r.name}" already exists in type "${r.self.name}"`, r.ast.loc, ); } - types.get(r.self)!.functions.set(r.name, r); + types.get(r.self.name)!.functions.set(r.name, r); } else { if (staticFunctions.has(r.name) || GlobalFunctions.has(r.name)) { throwCompilationError( diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts index 95190249c..702523f0f 100644 --- a/src/types/resolveExpression.ts +++ b/src/types/resolveExpression.ts @@ -562,13 +562,6 @@ function resolveCall( // Handle ref if (src.kind === "ref") { - if (src.optional) { - throwCompilationError( - `Invalid type "${printTypeRef(src)}" for function call`, - exp.loc, - ); - } - // Register return type const srcT = getType(ctx, src.name); diff --git a/src/types/resolveStatements.ts b/src/types/resolveStatements.ts index 5093b2463..91ff414dd 100644 --- a/src/types/resolveStatements.ts +++ b/src/types/resolveStatements.ts @@ -864,12 +864,12 @@ export function resolveStatements(ctx: CompilerContext) { ) { // Build statement context let sctx = emptyContext(f.ast.loc, f.name, f.returns); - sctx = addVariable( - selfId, - { kind: "ref", name: t.name, optional: false }, - ctx, - sctx, - ); + if (f.self === null) { + throwInternalCompilerError( + "Self is null where it should not be", + ); + } + sctx = addVariable(selfId, f.self, ctx, sctx); for (const a of f.params) { sctx = addVariable(a.name, a.type, ctx, sctx); } diff --git a/src/types/stmts/fun-extends-optional.tact b/src/types/stmts/fun-extends-optional.tact new file mode 100644 index 000000000..4a13d2e05 --- /dev/null +++ b/src/types/stmts/fun-extends-optional.tact @@ -0,0 +1,46 @@ +primitive Int; + +fun test(x: Int, y: Int?): Int { + if (y == null) { + return x; + } + return x + y!!; +} + +extends fun test_extends(self: Int, y: Int?): Int { + if (y == null) { + return self; + } + return self + y!!; +} + +extends fun test_extends_self(self: Int?, y: Int): Int { + if (self == null) { + return y; + } + return self!! + y; +} + +extends mutates fun test_mutates(self: Int, y: Int?): Int { + if (y == null) { + return self; + } + self += y!!; + return self; +} + +extends mutates fun test_mutates_self(self: Int?, y: Int): Int { + if (self == null) { + return y; + } + self = self!! + y; + return self!!; +} + +extends mutates fun test_mutates_self_opt(self: Int?, y: Int): Int? { + if (self == null) { + return null; + } + self = self!! + y; + return self; +} \ No newline at end of file diff --git a/src/types/test/fun-extends-opt-self.tact b/src/types/test/fun-extends-opt-self.tact new file mode 100644 index 000000000..57cd29b75 --- /dev/null +++ b/src/types/test/fun-extends-opt-self.tact @@ -0,0 +1,25 @@ +primitive Int; + +fun test(x: Int, y: Int?): Int { + return 123; +} + +extends fun test_extends(self: Int, y: Int?): Int { + return 123; +} + +extends fun test_extends_self(self: Int?, y: Int): Int { + return 123; +} + +extends mutates fun test_mutates(self: Int, y: Int?): Int { + return 123; +} + +extends mutates fun test_mutates_self(self: Int?, y: Int): Int { + return 123; +} + +extends mutates fun test_mutates_self_opt(self: Int?, y: Int): Int? { + return null; +} \ No newline at end of file diff --git a/src/types/types.ts b/src/types/types.ts index e7f6860e4..f82fbc70e 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -149,7 +149,7 @@ export type FunctionDescription = { isVirtual: boolean; isAbstract: boolean; isInline: boolean; - self: string | null; + self: TypeRef | null; returns: TypeRef; params: FunctionParameter[]; ast: diff --git a/tact.config.json b/tact.config.json index fd858093e..946c32d2f 100644 --- a/tact.config.json +++ b/tact.config.json @@ -95,8 +95,8 @@ "output": "./src/test/e2e-emulated/contracts/output" }, { - "name": "mutating-method-chaining", - "path": "./src/test/e2e-emulated/contracts/mutating-method-chaining.tact", + "name": "mutating-methods", + "path": "./src/test/e2e-emulated/contracts/mutating-methods.tact", "output": "./src/test/e2e-emulated/contracts/output" }, {