diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d550f2f..924e60fc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `VarInt16`, `VarInt32`, `VarUint16`, `VarUint32` integer serialization types: PR [#1186](https://github.com/tact-lang/tact/pull/1186) - `unboc`: a standalone CLI utility to expose Tact's TVM disassembler: PR [#1259](https://github.com/tact-lang/tact/pull/1259) - Added alternative parser: PR [#1258](https://github.com/tact-lang/tact/pull/1258) +- Support for block statements: PR [#1334](https://github.com/tact-lang/tact/pull/1334) ### Changed diff --git a/src/ast/ast.ts b/src/ast/ast.ts index 34e25472f..6df09670b 100644 --- a/src/ast/ast.ts +++ b/src/ast/ast.ts @@ -207,7 +207,8 @@ export type AstStatement = | AstStatementTry | AstStatementTryCatch | AstStatementForEach - | AstStatementDestruct; + | AstStatementDestruct + | AstStatementBlock; export type AstStatementLet = { kind: "statement_let"; @@ -334,6 +335,13 @@ export type AstStatementDestruct = { loc: SrcInfo; }; +export type AstStatementBlock = { + kind: "statement_block"; + statements: AstStatement[]; + id: number; + loc: SrcInfo; +}; + // // Types // diff --git a/src/ast/clone.ts b/src/ast/clone.ts index 31b92d1fe..57991eb46 100644 --- a/src/ast/clone.ts +++ b/src/ast/clone.ts @@ -44,6 +44,11 @@ export function cloneNode( : null, elseif: src.elseif ? recurse(src.elseif) : null, }); + } else if (src.kind === "statement_block") { + return cloneNode({ + ...src, + statements: src.statements.map(recurse), + }); } else if (src.kind === "struct_field_initializer") { return cloneNode({ ...src, diff --git a/src/ast/compare.ts b/src/ast/compare.ts index 918d3bd95..b724d8920 100644 --- a/src/ast/compare.ts +++ b/src/ast/compare.ts @@ -54,6 +54,7 @@ import { AstAsmInstruction, AstDestructMapping, AstStatementDestruct, + AstStatementBlock, } from "./ast"; import { AstRenamer } from "./rename"; import { throwInternalCompilerError } from "../error/errors"; @@ -611,6 +612,12 @@ export class AstComparator { ); } + case "statement_block": { + const { statements: statements1 } = node1 as AstStatementBlock; + const { statements: statements2 } = node2 as AstStatementBlock; + return this.compareArray(statements1, statements2); + } + case "type_id": { const { text: typeIdText1 } = node1 as AstTypeId; const { text: typeIdText2 } = node2 as AstTypeId; diff --git a/src/ast/getAstSchema.ts b/src/ast/getAstSchema.ts index 69742cb51..1de3f6842 100644 --- a/src/ast/getAstSchema.ts +++ b/src/ast/getAstSchema.ts @@ -376,6 +376,15 @@ export const getAstSchema = ( statements, loc: toSrcInfo(loc), }), + StatementBlock: ( + statements: A.AstStatement[], + loc: Loc, + ): A.AstStatementBlock => + createNode({ + kind: "statement_block", + statements, + loc: toSrcInfo(loc), + }), TypeId: (text: string, loc: Loc): A.AstTypeId => createNode({ kind: "type_id", diff --git a/src/ast/hash.ts b/src/ast/hash.ts index 354f22431..9dbcb9559 100644 --- a/src/ast/hash.ts +++ b/src/ast/hash.ts @@ -131,6 +131,10 @@ export class AstHasher { return `${node.kind}|${this.hash(node.map)}|${this.hashStatements(node.statements)}`; case "statement_destruct": return `${node.kind}|${this.hash(node.type)}|${this.hashDestructIdentifiers(Array.from(node.identifiers.values()))}|${this.hash(node.expression)}`; + case "statement_block": { + const statementsHash = this.hashStatements(node.statements); + return `${node.kind}|${statementsHash}`; + } // Expressions case "op_binary": return `${node.kind}|${node.op}|${this.hash(node.left)}|${this.hash(node.right)}`; diff --git a/src/ast/iterators.ts b/src/ast/iterators.ts index 7dbe015f5..3f1f5b9fb 100644 --- a/src/ast/iterators.ts +++ b/src/ast/iterators.ts @@ -207,6 +207,11 @@ export function traverse(node: AstNode, callback: (node: AstNode) => void) { traverse(e, callback); }); break; + case "statement_block": + node.statements.forEach((e) => { + traverse(e, callback); + }); + break; case "destruct_mapping": traverse(node.field, callback); traverse(node.name, callback); diff --git a/src/generator/writers/writeFunction.ts b/src/generator/writers/writeFunction.ts index e399970a4..dc8cf7026 100644 --- a/src/generator/writers/writeFunction.ts +++ b/src/generator/writers/writeFunction.ts @@ -491,6 +491,12 @@ export function writeStatement( ); return; } + case "statement_block": { + for (const s of f.statements) { + writeStatement(s, self, returns, ctx); + } + return; + } } throw Error("Unknown statement kind"); diff --git a/src/grammar/next/__snapshots__/grammar.spec.ts.snap b/src/grammar/next/__snapshots__/grammar.spec.ts.snap index bf4aeee2e..3db8059b8 100644 --- a/src/grammar/next/__snapshots__/grammar.spec.ts.snap +++ b/src/grammar/next/__snapshots__/grammar.spec.ts.snap @@ -825,6 +825,84 @@ exports[`grammar should parse abstract-const 1`] = ` } `; +exports[`grammar should parse block-statements 1`] = ` +{ + "id": 10, + "imports": [], + "items": [ + { + "attributes": [], + "id": 9, + "kind": "function_def", + "loc": fun foo() { + let x = 100; + { + let y = 200; + } +}, + "name": { + "id": 1, + "kind": "id", + "loc": foo, + "text": "foo", + }, + "params": [], + "return": null, + "statements": [ + { + "expression": { + "base": 10, + "id": 3, + "kind": "number", + "loc": 100, + "value": 100n, + }, + "id": 4, + "kind": "statement_let", + "loc": let x = 100;, + "name": { + "id": 2, + "kind": "id", + "loc": x, + "text": "x", + }, + "type": null, + }, + { + "id": 8, + "kind": "statement_block", + "loc": { + let y = 200; + }, + "statements": [ + { + "expression": { + "base": 10, + "id": 6, + "kind": "number", + "loc": 200, + "value": 200n, + }, + "id": 7, + "kind": "statement_let", + "loc": let y = 200;, + "name": { + "id": 5, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": null, + }, + ], + }, + ], + }, + ], + "kind": "module", +} +`; + exports[`grammar should parse case-35 1`] = ` { "id": 7, @@ -6782,6 +6860,129 @@ exports[`grammar should parse literals-int-underscores-bin-dec-hex-oct 1`] = ` } `; +exports[`grammar should parse nested-block-statements 1`] = ` +{ + "id": 13, + "imports": [], + "items": [ + { + "attributes": [], + "id": 12, + "kind": "function_def", + "loc": fun foo() { + let x = 100; + { + { + { + { + let y = 200; + } + } + } + } +}, + "name": { + "id": 1, + "kind": "id", + "loc": foo, + "text": "foo", + }, + "params": [], + "return": null, + "statements": [ + { + "expression": { + "base": 10, + "id": 3, + "kind": "number", + "loc": 100, + "value": 100n, + }, + "id": 4, + "kind": "statement_let", + "loc": let x = 100;, + "name": { + "id": 2, + "kind": "id", + "loc": x, + "text": "x", + }, + "type": null, + }, + { + "id": 11, + "kind": "statement_block", + "loc": { + { + { + { + let y = 200; + } + } + } + }, + "statements": [ + { + "id": 10, + "kind": "statement_block", + "loc": { + { + { + let y = 200; + } + } + }, + "statements": [ + { + "id": 9, + "kind": "statement_block", + "loc": { + { + let y = 200; + } + }, + "statements": [ + { + "id": 8, + "kind": "statement_block", + "loc": { + let y = 200; + }, + "statements": [ + { + "expression": { + "base": 10, + "id": 6, + "kind": "number", + "loc": 200, + "value": 200n, + }, + "id": 7, + "kind": "statement_let", + "loc": let y = 200;, + "name": { + "id": 5, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": null, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + "kind": "module", +} +`; + exports[`grammar should parse stmt-augmented-assign-arith 1`] = ` { "id": 94, diff --git a/src/grammar/next/index.ts b/src/grammar/next/index.ts index 4967b9d81..3d42cf160 100644 --- a/src/grammar/next/index.ts +++ b/src/grammar/next/index.ts @@ -1,11 +1,8 @@ import * as $ from "@tonstudio/parser-runtime"; import * as A from "../../ast/ast"; import * as G from "./grammar"; -import type { $ast } from "./grammar"; -import { - TactCompilationError, - throwInternalCompilerError, -} from "../../error/errors"; +import { $ast } from "./grammar"; +import { TactCompilationError } from "../../error/errors"; import { SyntaxErrors, syntaxErrorSchema } from "../parser-error"; import { AstSchema, getAstSchema } from "../../ast/getAstSchema"; import { getSrcInfo, ItemOrigin } from "../src-info"; @@ -475,10 +472,9 @@ const parseStatementDestruct = }; const parseStatementBlock = - (_node: $ast.StatementBlock): Handler => - () => { - // TODO: process StatementBlock - throwInternalCompilerError("Block statements are not supported"); + ({ body, loc }: $ast.StatementBlock): Handler => + (ctx) => { + return ctx.ast.StatementBlock(parseStatements(body)(ctx), loc); }; const parseStatementReturn = diff --git a/src/grammar/prev/__snapshots__/grammar.spec.ts.snap b/src/grammar/prev/__snapshots__/grammar.spec.ts.snap index 4de0df7de..f94e2c7c0 100644 --- a/src/grammar/prev/__snapshots__/grammar.spec.ts.snap +++ b/src/grammar/prev/__snapshots__/grammar.spec.ts.snap @@ -877,6 +877,84 @@ exports[`grammar should parse abstract-const 1`] = ` } `; +exports[`grammar should parse block-statements 1`] = ` +{ + "id": 10, + "imports": [], + "items": [ + { + "attributes": [], + "id": 9, + "kind": "function_def", + "loc": fun foo() { + let x = 100; + { + let y = 200; + } +}, + "name": { + "id": 1, + "kind": "id", + "loc": foo, + "text": "foo", + }, + "params": [], + "return": null, + "statements": [ + { + "expression": { + "base": 10, + "id": 3, + "kind": "number", + "loc": 100, + "value": 100n, + }, + "id": 4, + "kind": "statement_let", + "loc": let x = 100;, + "name": { + "id": 2, + "kind": "id", + "loc": x, + "text": "x", + }, + "type": null, + }, + { + "id": 8, + "kind": "statement_block", + "loc": { + let y = 200; + }, + "statements": [ + { + "expression": { + "base": 10, + "id": 6, + "kind": "number", + "loc": 200, + "value": 200n, + }, + "id": 7, + "kind": "statement_let", + "loc": let y = 200;, + "name": { + "id": 5, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": null, + }, + ], + }, + ], + }, + ], + "kind": "module", +} +`; + exports[`grammar should parse case-35 1`] = ` { "id": 7, @@ -6849,6 +6927,129 @@ exports[`grammar should parse literals-int-underscores-bin-dec-hex-oct 1`] = ` } `; +exports[`grammar should parse nested-block-statements 1`] = ` +{ + "id": 13, + "imports": [], + "items": [ + { + "attributes": [], + "id": 12, + "kind": "function_def", + "loc": fun foo() { + let x = 100; + { + { + { + { + let y = 200; + } + } + } + } +}, + "name": { + "id": 1, + "kind": "id", + "loc": foo, + "text": "foo", + }, + "params": [], + "return": null, + "statements": [ + { + "expression": { + "base": 10, + "id": 3, + "kind": "number", + "loc": 100, + "value": 100n, + }, + "id": 4, + "kind": "statement_let", + "loc": let x = 100;, + "name": { + "id": 2, + "kind": "id", + "loc": x, + "text": "x", + }, + "type": null, + }, + { + "id": 11, + "kind": "statement_block", + "loc": { + { + { + { + let y = 200; + } + } + } + }, + "statements": [ + { + "id": 10, + "kind": "statement_block", + "loc": { + { + { + let y = 200; + } + } + }, + "statements": [ + { + "id": 9, + "kind": "statement_block", + "loc": { + { + let y = 200; + } + }, + "statements": [ + { + "id": 8, + "kind": "statement_block", + "loc": { + let y = 200; + }, + "statements": [ + { + "expression": { + "base": 10, + "id": 6, + "kind": "number", + "loc": 200, + "value": 200n, + }, + "id": 7, + "kind": "statement_let", + "loc": let y = 200;, + "name": { + "id": 5, + "kind": "id", + "loc": y, + "text": "y", + }, + "type": null, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + "kind": "module", +} +`; + exports[`grammar should parse stmt-augmented-assign-arith 1`] = ` { "id": 94, diff --git a/src/grammar/prev/grammar.ts b/src/grammar/prev/grammar.ts index 73e0d3e02..f3a847112 100644 --- a/src/grammar/prev/grammar.ts +++ b/src/grammar/prev/grammar.ts @@ -1053,6 +1053,13 @@ semantics.addOperation("astOfStatement", { loc: createRef(this), }); }, + StatementBlock(_lbrace, statements, _rbrace) { + return createNode({ + kind: "statement_block", + statements: statements.children.map((s) => s.astOfStatement()), + loc: createRef(this), + }); + }, }); semantics.addOperation("astOfType", { diff --git a/src/grammar/test/block-statements.tact b/src/grammar/test/block-statements.tact new file mode 100644 index 000000000..a72f1f000 --- /dev/null +++ b/src/grammar/test/block-statements.tact @@ -0,0 +1,6 @@ +fun foo() { + let x = 100; + { + let y = 200; + } +} diff --git a/src/grammar/test/nested-block-statements.tact b/src/grammar/test/nested-block-statements.tact new file mode 100644 index 000000000..0894e74d8 --- /dev/null +++ b/src/grammar/test/nested-block-statements.tact @@ -0,0 +1,12 @@ +fun foo() { + let x = 100; + { + { + { + { + let y = 200; + } + } + } + } +} diff --git a/src/optimizer/interpreter.ts b/src/optimizer/interpreter.ts index c810d331b..ee923781c 100644 --- a/src/optimizer/interpreter.ts +++ b/src/optimizer/interpreter.ts @@ -64,6 +64,7 @@ import { getAstFactory, idText, isSelfId, + AstStatementBlock, } from "../ast/ast"; import { AstUtil, divFloor, getAstUtil, modFloor } from "./util"; import { @@ -1589,6 +1590,9 @@ export class Interpreter { case "statement_while": this.interpretWhileStatement(ast); break; + case "statement_block": + this.interpretBlockStatement(ast); + break; } } @@ -1807,4 +1811,10 @@ export class Interpreter { } while (condition.value); }); } + + public interpretBlockStatement(ast: AstStatementBlock) { + this.envStack.executeInNewEnvironment(() => { + ast.statements.forEach(this.interpretStatement, this); + }); + } } diff --git a/src/prettyPrinter.ts b/src/prettyPrinter.ts index 53e56d22f..db8c64c96 100644 --- a/src/prettyPrinter.ts +++ b/src/prettyPrinter.ts @@ -819,6 +819,11 @@ export const ppAstStatementDestruct: Printer = ); }; +export const ppAstStatementBlock: Printer = + ({ statements }) => + (c) => + ppStatementBlock(statements)(c); + export const ppAstStatement: Printer = makeVisitor()({ statement_let: ppAstStatementLet, @@ -834,6 +839,7 @@ export const ppAstStatement: Printer = statement_try: ppAstStatementTry, statement_try_catch: ppAstStatementTryCatch, statement_destruct: ppAstStatementDestruct, + statement_block: ppAstStatementBlock, }); export const exprNode = @@ -904,6 +910,7 @@ export const ppAstNode: Printer = makeVisitor()({ statement_try: ppAstStatementTry, statement_try_catch: ppAstStatementTryCatch, statement_foreach: ppAstStatementForEach, + statement_block: ppAstStatementBlock, import: ppAstImport, func_id: exprNode(ppAstFuncId), statement_destruct: ppAstStatementDestruct, diff --git a/src/test/contracts/case-block-statements.tact b/src/test/contracts/case-block-statements.tact new file mode 100644 index 000000000..0624f9e2b --- /dev/null +++ b/src/test/contracts/case-block-statements.tact @@ -0,0 +1,12 @@ +contract Foo { + get fun foo(): Int { + let x1: Int = 42; + { + let x: Int = 42; + { + x1 += x; + } + } + return x1; + } +} diff --git a/src/test/contracts/renamer-expected/case-block-statements.tact b/src/test/contracts/renamer-expected/case-block-statements.tact new file mode 100644 index 000000000..bd5734be5 --- /dev/null +++ b/src/test/contracts/renamer-expected/case-block-statements.tact @@ -0,0 +1,12 @@ +contract contract_0 { + get fun function_def_1(): Int { + let x1: Int = 42; + { + let x: Int = 42; + { + x1 += x; + } + } + return x1; + } +} diff --git a/src/test/e2e-emulated/block-statements.spec.ts b/src/test/e2e-emulated/block-statements.spec.ts new file mode 100644 index 000000000..99c3ee64f --- /dev/null +++ b/src/test/e2e-emulated/block-statements.spec.ts @@ -0,0 +1,50 @@ +import { toNano } from "@ton/core"; +import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox"; +import { Test } from "./contracts/output/block-statements_Test"; +import "@ton/test-utils"; + +describe("block-statements", () => { + let blockchain: Blockchain; + let treasure: SandboxContract; + let contract: SandboxContract; + + beforeEach(async () => { + blockchain = await Blockchain.create(); + treasure = await blockchain.treasury("treasure"); + + contract = blockchain.openContract(await Test.fromInit()); + + const deployResult = await contract.send( + treasure.getSender(), + { value: toNano("10") }, + { $$type: "Deploy", queryId: 0n }, + ); + + expect(deployResult.transactions).toHaveTransaction({ + from: treasure.address, + to: contract.address, + success: true, + deploy: true, + }); + }); + + it("should work correctly with block statements", async () => { + expect(await contract.getA()).toEqual(84n); + }); + + it("should work correctly with variables from subsequent block statements", async () => { + expect(await contract.getB()).toEqual(1308n); + }); + + it("should work correctly with variables of different types from subsequent block statements", async () => { + expect(await contract.getC()).toEqual(557n); + }); + + it("should work correctly with variables of different types from subsequent block statements inside interpreter", async () => { + expect(await contract.getD()).toEqual(1308n); + }); + + it("should work correctly with variables of different types from nested block statements inside interpreter", async () => { + expect(await contract.getE()).toEqual(84n); + }); +}); diff --git a/src/test/e2e-emulated/contracts/block-statements.tact b/src/test/e2e-emulated/contracts/block-statements.tact new file mode 100644 index 000000000..4db6ba88e --- /dev/null +++ b/src/test/e2e-emulated/contracts/block-statements.tact @@ -0,0 +1,77 @@ +import "@stdlib/deploy"; + +fun computeWithSubsequentBlocks(): Int { + let b: Int = 100; + { + let a: Int = 456; + b += a; + } + { + let a: Int = 752; + b += a; + } + return b; +} + +fun computeWithNestedBlocks(): Int { + let x1: Int = 42; + { + let x: Int = 42; + { + x1 += x; + } + } + return x1; +} + +const SubsequentConstant: Int = computeWithSubsequentBlocks(); +const NestedConstant: Int = computeWithNestedBlocks(); + +contract Test with Deployable { + receive("test") {} + + get fun A(): Int { + let x1: Int = 42; + { + let x: Int = 42; + { + x1 += x; + } + } + return x1; + } + + get fun B(): Int { + let b: Int = 100; + { + let a: Int = 456; + b += a; + } + { + let a: Int = 752; + b += a; + } + return b; + } + + get fun C(): Int { + let b: Int = 100; + { + let a: Int = 456; + b += a; + } + { + let a: Slice = beginCell().storeBool(true).asSlice(); + b += a.bits(); + } + return b; + } + + get fun D(): Int { + return SubsequentConstant; + } + + get fun E(): Int { + return NestedConstant; + } +} diff --git a/src/test/tact.config.json b/src/test/tact.config.json index e8d6cd512..cf0b41b9a 100644 --- a/src/test/tact.config.json +++ b/src/test/tact.config.json @@ -315,6 +315,11 @@ "path": "./e2e-emulated/contracts/asm-functions.tact", "output": "./e2e-emulated/contracts/output" }, + { + "name": "block-statements", + "path": "./e2e-emulated/contracts/block-statements.tact", + "output": "./e2e-emulated/contracts/output" + }, { "name": "text-message-receivers", "path": "./e2e-emulated/contracts/text-message-receivers.tact", diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap index b0d5b83ef..e91313430 100644 --- a/src/types/__snapshots__/resolveStatements.spec.ts.snap +++ b/src/types/__snapshots__/resolveStatements.spec.ts.snap @@ -798,6 +798,26 @@ Line 6, col 5: " `; +exports[`resolveStatements should fail statements for return-analysis-if-inside-block-statement 1`] = ` +":6:5: Function does not always return a result. Adding 'return' statement(s) should fix the issue. +Line 6, col 5: + 5 | contract Foo { +> 6 | get fun notAlwaysReachableReturn(b: Bool): Int { + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 7 | { +" +`; + +exports[`resolveStatements should fail statements for return-analysis-if-with-block-statement 1`] = ` +":6:5: Function does not always return a result. Adding 'return' statement(s) should fix the issue. +Line 6, col 5: + 5 | contract Foo { +> 6 | get fun notAlwaysReachableReturn(b: Bool): Int { + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 7 | if b { +" +`; + exports[`resolveStatements should fail statements for return-analysis-non-void1 1`] = ` ":6:5: Function does not always return a result. Adding 'return' statement(s) should fix the issue. Line 6, col 5: @@ -1308,6 +1328,26 @@ Line 9, col 9: " `; +exports[`resolveStatements should fail statements for var-scope-block-statement 1`] = ` +":6:13: Variable already exists: "a" +Line 6, col 13: + 5 | { +> 6 | let a: Int = 456; + ^ + 7 | } +" +`; + +exports[`resolveStatements should fail statements for var-scope-block-statement-use-after-block 1`] = ` +":7:12: Unable to resolve id 'a' +Line 7, col 12: + 6 | } +> 7 | return a; + ^ + 8 | } +" +`; + exports[`resolveStatements should fail statements for var-scope-catch-does-not-shadow-outer-const 1`] = ` ":10:13: Variable "e" is trying to shadow an existing constant with the same name Line 10, col 13: @@ -1648,6 +1688,16 @@ Line 5, col 14: " `; +exports[`resolveStatements should fail statements for var-scope-nested-block-statement 1`] = ` +":9:25: Variable already exists: "a" +Line 9, col 25: + 8 | { +> 9 | let a: Int = 456; + ^ + 10 | } +" +`; + exports[`resolveStatements should fail statements for var-scope-no-toString-global-fun1 1`] = ` ":7:9: Static function "toString" does not exist Line 7, col 9: @@ -2938,6 +2988,15 @@ exports[`resolveStatements should resolve statements for init-vars-analysis-with ] `; +exports[`resolveStatements should resolve statements for return-analysis-block-statement-with-return 1`] = ` +[ + [ + "42", + "Int", + ], +] +`; + exports[`resolveStatements should resolve statements for return-analysis-if-elseif 1`] = ` [ [ @@ -3645,6 +3704,43 @@ exports[`resolveStatements should resolve statements for var-scope-repeat2 1`] = ] `; +exports[`resolveStatements should resolve statements for var-scope-subsequent-block-statements 1`] = ` +[ + [ + "100", + "Int", + ], + [ + "456", + "Int", + ], + [ + "b", + "Int", + ], + [ + "a", + "Int", + ], + [ + "752", + "Int", + ], + [ + "b", + "Int", + ], + [ + "a", + "Int", + ], + [ + "b", + "Int", + ], +] +`; + exports[`resolveStatements should resolve statements for var-scope-toString-non-method 1`] = ` [ [ diff --git a/src/types/resolveStatements.ts b/src/types/resolveStatements.ts index 1211f53c7..281517d27 100644 --- a/src/types/resolveStatements.ts +++ b/src/types/resolveStatements.ts @@ -761,6 +761,12 @@ function processStatements( break; } + case "statement_block": { + const r = processStatements(s.statements, sctx, ctx); + ctx = r.ctx; + returnAlwaysReachable ||= r.returnAlwaysReachable; + break; + } } } diff --git a/src/types/stmts-failed/return-analysis-if-inside-block-statement.tact b/src/types/stmts-failed/return-analysis-if-inside-block-statement.tact new file mode 100644 index 000000000..25cc1f78c --- /dev/null +++ b/src/types/stmts-failed/return-analysis-if-inside-block-statement.tact @@ -0,0 +1,14 @@ +primitive Int; +primitive Bool; +trait BaseTrait { } + +contract Foo { + get fun notAlwaysReachableReturn(b: Bool): Int { + { + if b { + return 42; + } + } + // ERROR: no return statement + } +} diff --git a/src/types/stmts-failed/return-analysis-if-with-block-statement.tact b/src/types/stmts-failed/return-analysis-if-with-block-statement.tact new file mode 100644 index 000000000..b9d7fb2ee --- /dev/null +++ b/src/types/stmts-failed/return-analysis-if-with-block-statement.tact @@ -0,0 +1,14 @@ +primitive Int; +primitive Bool; +trait BaseTrait { } + +contract Foo { + get fun notAlwaysReachableReturn(b: Bool): Int { + if b { + { + return 42; + } + } + // ERROR: no return statement + } +} diff --git a/src/types/stmts-failed/var-scope-block-statement-use-after-block.tact b/src/types/stmts-failed/var-scope-block-statement-use-after-block.tact new file mode 100644 index 000000000..aa83fd88b --- /dev/null +++ b/src/types/stmts-failed/var-scope-block-statement-use-after-block.tact @@ -0,0 +1,8 @@ +primitive Int; + +fun testFunction(): Int { + { + let a: Int = 456; + } + return a; +} diff --git a/src/types/stmts-failed/var-scope-block-statement.tact b/src/types/stmts-failed/var-scope-block-statement.tact new file mode 100644 index 000000000..ba3893e0b --- /dev/null +++ b/src/types/stmts-failed/var-scope-block-statement.tact @@ -0,0 +1,9 @@ +primitive Int; + +fun testFunction(): Int { + let a: Int = 123; + { + let a: Int = 456; + } + return a; +} diff --git a/src/types/stmts-failed/var-scope-nested-block-statement.tact b/src/types/stmts-failed/var-scope-nested-block-statement.tact new file mode 100644 index 000000000..3c7f97eb5 --- /dev/null +++ b/src/types/stmts-failed/var-scope-nested-block-statement.tact @@ -0,0 +1,15 @@ +primitive Int; + +fun testFunction(): Int { + let a: Int = 123; + { + { + { + { + let a: Int = 456; + } + } + } + } + return a; +} diff --git a/src/types/stmts/return-analysis-block-statement-with-return.tact b/src/types/stmts/return-analysis-block-statement-with-return.tact new file mode 100644 index 000000000..914c89508 --- /dev/null +++ b/src/types/stmts/return-analysis-block-statement-with-return.tact @@ -0,0 +1,11 @@ +primitive Int; +primitive Bool; +trait BaseTrait { } + +contract Foo { + get fun alwaysReachableReturn(b: Bool): Int { + { + return 42; + } + } +} diff --git a/src/types/stmts/var-scope-subsequent-block-statements.tact b/src/types/stmts/var-scope-subsequent-block-statements.tact new file mode 100644 index 000000000..0f3dd60cd --- /dev/null +++ b/src/types/stmts/var-scope-subsequent-block-statements.tact @@ -0,0 +1,14 @@ +primitive Int; + +fun testFunction(): Int { + let b: Int = 100; + { + let a: Int = 456; + b += a; + } + { + let a: Int = 752; + b += a; + } + return b; +}