diff --git a/CHANGELOG.md b/CHANGELOG.md index 96296a660..c3f4dedad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Update the `dump` function to handle addresses: PR [#175](https://github.com/tact-lang/tact/pull/175) - The implicit empty `init` function is now present by default in the contract if not declared: PR [#167](https://github.com/tact-lang/tact/pull/167) +- Improved `Bool` reduction in constant expressions: PR [#197](https://github.com/tact-lang/tact/pull/197) ### Fixed diff --git a/src/test/features/constants.tact b/src/test/features/constants.tact index 7d7f0c89a..4026e4026 100644 --- a/src/test/features/constants.tact +++ b/src/test/features/constants.tact @@ -9,6 +9,22 @@ contract ConstantTester { const something6: Int = 10 * 1; const something7: Int = 10 >> 1; const something8: Int = (2 + 4) & 4; + const something9: Bool = true && false; + const something10: Bool = true || false; + const something11: Bool = !true; + const something12: Bool = true == false; + const something13: Bool = true != false; + const something14: Bool = !(true == false); + const something15: Bool = 5 > 5; + const something16: Bool = 5 >= 5; + const something17: Bool = "Hello" == "Hello"; + const something18: Bool = "Hello" != "Hello"; + const something19: Bool = "Hello".asCell() == "Hello".asCell(); + const something20: Bool = "Hello".asCell() != "Hello".asCell(); + const something21: Bool = newAddress(0, 0x606813c5f6a76175eae668630c6d8ffe229543610e3d204db245dd51f9ba0503) == newAddress(0, 0x606813c5f6a76175eae668630c6d8ffe229543610e3d204db245dd51f9ba0503); + const something22: Bool = newAddress(0, 0x606813c5f6a76175eae668630c6d8ffe229543610e3d204db245dd51f9ba0503) != newAddress(0, 0x606813c5f6a76175eae668630c6d8ffe229543610e3d204db245dd51f9ba0503); + const something23: Bool = "Hello".asCell() == "Hello".asCell(); + const something24: Bool = "Hello".asCell() != "Hello".asCell(); init() { @@ -49,4 +65,68 @@ contract ConstantTester { get fun globalConst(): Int { return someGlobalConst; } + + get fun something9(): Bool { + return self.something9; + } + + get fun something10(): Bool { + return self.something10; + } + + get fun something11(): Bool { + return self.something11; + } + + get fun something12(): Bool { + return self.something12; + } + + get fun something13(): Bool { + return self.something13; + } + + get fun something14(): Bool { + return self.something14; + } + + get fun something15(): Bool { + return self.something15; + } + + get fun something16(): Bool { + return self.something16; + } + + get fun something17(): Bool { + return self.something17; + } + + get fun something18(): Bool { + return self.something18; + } + + get fun something19(): Bool { + return self.something19; + } + + get fun something20(): Bool { + return self.something20; + } + + get fun something21(): Bool { + return self.something21; + } + + get fun something22(): Bool { + return self.something22; + } + + get fun something23(): Bool { + return self.something23; + } + + get fun something24(): Bool { + return self.something24; + } } \ No newline at end of file diff --git a/src/types/resolveConstantValue.ts b/src/types/resolveConstantValue.ts index 5482c0799..da35dfe64 100644 --- a/src/types/resolveConstantValue.ts +++ b/src/types/resolveConstantValue.ts @@ -1,9 +1,22 @@ -import { Address, Cell, toNano } from "@ton/core"; +import { Address, Cell, Slice, toNano } from "@ton/core"; import { enabledMasterchain } from "../config/features"; import { CompilerContext } from "../context"; -import { ASTExpression, throwError } from "../grammar/ast"; +import { ASTExpression, ASTOpCallStatic, throwError } from "../grammar/ast"; import { printTypeRef, TypeRef } from "./types"; import { sha256_sync } from "@ton/crypto"; +import { getExpType } from "./resolveExpression"; + +function isSlice(ast: ASTExpression): boolean { + return (ast.kind === 'op_static_call') && (ast.name === 'slice') && (ast.args.length === 1); +} + +function isCell(ast: ASTExpression): boolean { + return (ast.kind === 'op_static_call') && (ast.name === 'cell') && (ast.args.length === 1); +} + +function isAddress(ast: ASTExpression): boolean { + return (ast.kind === 'op_static_call') && (ast.name === 'address') && (ast.args.length === 1); +} function reduceInt(ast: ASTExpression): bigint { if (ast.kind === 'number') { @@ -59,22 +72,70 @@ function reduceInt(ast: ASTExpression): bigint { throwError('Cannot reduce expression to a constant integer', ast.ref); } -function reduceBool(ast: ASTExpression): boolean { +function reduceBool(ast: ASTExpression, ctx: CompilerContext): boolean { if (ast.kind === 'boolean') { return ast.value; } if (ast.kind === 'op_unary') { if (ast.op === '!') { - return !reduceBool(ast.right); + return !reduceBool(ast.right, ctx); } } if (ast.kind === 'op_binary') { if (ast.op === '&&') { - return reduceBool(ast.left) && reduceBool(ast.right); + return reduceBool(ast.left, ctx) && reduceBool(ast.right, ctx); } else if (ast.op === '||') { - return reduceBool(ast.left) || reduceBool(ast.right); + return reduceBool(ast.left, ctx) || reduceBool(ast.right, ctx); + } else { + const leftType = getExpType(ctx, ast.left); + const rightType = getExpType(ctx, ast.right); + if (ast.op === '>') { + return reduceInt(ast.left) > reduceInt(ast.right); + } else if (ast.op === '<') { + return reduceInt(ast.left) < reduceInt(ast.right); + } else if (ast.op === '>=') { + return reduceInt(ast.left) >= reduceInt(ast.right); + } else if (ast.op === '<=') { + return reduceInt(ast.left) <= reduceInt(ast.right); + } else if (ast.op === '==') { + if (leftType.kind === 'ref' && rightType.kind === 'ref') { + if (leftType.name === 'Address' && rightType.name === 'Address') { + return reduceAddress(ast.left, ctx).equals(reduceAddress(ast.right, ctx)); + } else if (leftType.name === 'Cell' && rightType.name === 'Cell') { + return reduceCell(ast.left).equals(reduceCell(ast.right)); + } else if (leftType.name === 'String' && rightType.name === 'String') { + return reduceString(ast.left) === reduceString(ast.right); + } else if (leftType.name === 'Int' && rightType.name === 'Int') { + return reduceInt(ast.left) === reduceInt(ast.right); + } else if (leftType.name === 'Bool' && rightType.name === 'Bool') { + return reduceBool(ast.left, ctx) === reduceBool(ast.right, ctx); + } else if (leftType.name === 'Slice' && rightType.name === 'Slice') { + return reduceSlice(ast.left).asCell().equals(reduceSlice(ast.right).asCell()); + } + } else if (leftType.kind === 'null' && rightType.kind === 'null') { + return true; + } + } else if (ast.op === '!=') { + if (leftType.kind === 'ref' && rightType.kind === 'ref') { + if (leftType.name === 'Address' && rightType.name === 'Address') { + return !reduceAddress(ast.left, ctx).equals(reduceAddress(ast.right, ctx)); + } else if (leftType.name === 'Cell' && rightType.name === 'Cell') { + return !reduceCell(ast.left).equals(reduceCell(ast.right)); + } else if (leftType.name === 'String' && rightType.name === 'String') { + return reduceString(ast.left) !== reduceString(ast.right); + } else if (leftType.name === 'Int' && rightType.name === 'Int') { + return reduceInt(ast.left) !== reduceInt(ast.right); + } else if (leftType.name === 'Bool' && rightType.name === 'Bool') { + return reduceBool(ast.left, ctx) !== reduceBool(ast.right, ctx); + } else if (leftType.name === 'Slice' && rightType.name === 'Slice') { + return !reduceSlice(ast.left).asCell().equals(reduceSlice(ast.right).asCell()); + } + } else if (leftType.kind === 'null' && rightType.kind === 'null') { + return false; + } + } + return true; } - // TODO: More cases } throwError('Cannot reduce expression to a constant boolean', ast.ref); @@ -88,44 +149,57 @@ function reduceString(ast: ASTExpression): string { } function reduceAddress(ast: ASTExpression, ctx: CompilerContext): Address { - if (ast.kind === 'op_static_call') { - if (ast.name === 'address') { - if (ast.args.length === 1) { - const str = reduceString(ast.args[0]); - const address = Address.parse(str); - if (address.workChain !== 0 && address.workChain !== -1) { - throwError(`Address ${str} invalid address`, ast.ref); - } - if (!enabledMasterchain(ctx)) { - if (address.workChain !== 0) { - throwError(`Address ${str} from masterchain are not enabled for this contract`, ast.ref); - } - } - return address; + if (isAddress(ast)) { + ast = ast as ASTOpCallStatic; + const str = reduceString(ast.args[0]); + const address = Address.parse(str); + if (address.workChain !== 0 && address.workChain !== -1) { + throwError(`Address ${str} invalid address`, ast.ref); + } + if (!enabledMasterchain(ctx)) { + if (address.workChain !== 0) { + throwError( + `Address ${str} from masterchain are not enabled for this contract`, + ast.ref + ); } } + return address; } throwError('Cannot reduce expression to a constant Address', ast.ref); } function reduceCell(ast: ASTExpression): Cell { - if (ast.kind === 'op_static_call') { - if (ast.name === 'cell') { - if (ast.args.length === 1) { - const str = reduceString(ast.args[0]); - let c: Cell; - try { - c = Cell.fromBase64(str); - } catch (e) { - throwError(`Invalid cell ${str}`, ast.ref); - } - return c; - } + if (isCell(ast)) { + ast = ast as ASTOpCallStatic; + const str = reduceString(ast.args[0]); + let c: Cell; + try { + c = Cell.fromBase64(str); + } catch (e) { + throwError(`Invalid cell ${str}`, ast.ref); } + return c; } + throwError('Cannot reduce expression to a constant Cell', ast.ref); } +function reduceSlice(ast: ASTExpression): Slice { + if (isSlice(ast)) { + ast = ast as ASTOpCallStatic; + const str = reduceString(ast.args[0]); + let c: Cell; + try { + c = Cell.fromBase64(str); + } catch (e) { + throwError(`Invalid cell ${str}`, ast.ref); + } + return c.asSlice(); + } + throwError('Cannot reduce expression to a constant Slice', ast.ref); +} + export function resolveConstantValue(type: TypeRef, ast: ASTExpression | null, ctx: CompilerContext) { if (ast === null) { return undefined; @@ -149,7 +223,7 @@ export function resolveConstantValue(type: TypeRef, ast: ASTExpression | null, c // Handle bool if (type.name === 'Bool') { - return reduceBool(ast); + return reduceBool(ast, ctx); } // Handle string