From 0f65cfabb31c84211a4cefd33e49b807af859721 Mon Sep 17 00:00:00 2001 From: Daniil Sedov <42098239+Gusarich@users.noreply.github.com> Date: Sun, 9 Jun 2024 13:01:11 +0300 Subject: [PATCH] feat: bitwise NOT operator `~` (#337) --- CHANGELOG.md | 1 + src/generator/writers/writeExpression.ts | 4 ++ src/grammar/ast.ts | 2 +- src/grammar/grammar.ohm | 1 + src/grammar/grammar.ts | 8 ++++ .../feature-constants.spec.ts.snap | 42 ++++++++++++++----- src/test/feature-constants.spec.ts | 1 + src/test/feature-math.spec.ts | 9 ++++ src/test/features/constants.tact | 5 +++ src/test/features/math.tact | 20 +++++++++ .../resolveDescriptors.spec.ts.snap | 18 ++++++++ .../resolveStatements.spec.ts.snap | 10 +++++ src/types/resolveConstantValue.ts | 2 + src/types/resolveExpression.ts | 2 +- src/types/stmts-failed/case-53.tact | 6 +++ src/types/test-failed/case-28.tact | 4 ++ src/types/test-failed/case-29.tact | 4 ++ 17 files changed, 126 insertions(+), 13 deletions(-) create mode 100644 src/types/stmts-failed/case-53.tact create mode 100644 src/types/test-failed/case-28.tact create mode 100644 src/types/test-failed/case-29.tact diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d5e91e75..371b54033 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- The bitwise NOT operation (`~`): PR [#337](https://github.com/tact-lang/tact/pull/337) - Augmented assignment bitwise operators `|=`, `&=`, `^=`: PR [#350](https://github.com/tact-lang/tact/pull/350) - Traversing maps from contract storage and structs is now allowed: PR [#389](https://github.com/tact-lang/tact/pull/389) diff --git a/src/generator/writers/writeExpression.ts b/src/generator/writers/writeExpression.ts index 46ba07d83..76f6d6cb5 100644 --- a/src/generator/writers/writeExpression.ts +++ b/src/generator/writers/writeExpression.ts @@ -453,6 +453,10 @@ export function writeExpression(f: ASTExpression, ctx: WriterContext): string { return "(~ " + writeExpression(f.right, ctx) + ")"; } + if (f.op === "~") { + return "(~ " + writeExpression(f.right, ctx) + ")"; + } + if (f.op === "-") { return "(- " + writeExpression(f.right, ctx) + ")"; } diff --git a/src/grammar/ast.ts b/src/grammar/ast.ts index ed7b2003d..7b08d3066 100644 --- a/src/grammar/ast.ts +++ b/src/grammar/ast.ts @@ -153,7 +153,7 @@ export type ASTOpBinary = { ref: ASTRef; }; -export type ASTUnaryOperation = "+" | "-" | "!" | "!!"; +export type ASTUnaryOperation = "+" | "-" | "!" | "!!" | "~"; export type ASTOpUnary = { kind: "op_unary"; diff --git a/src/grammar/grammar.ohm b/src/grammar/grammar.ohm index afe7a1f39..ec0c68d7d 100644 --- a/src/grammar/grammar.ohm +++ b/src/grammar/grammar.ohm @@ -174,6 +174,7 @@ Tact { ExpressionUnary = "-" ExpressionUnary --minus | "+" ExpressionUnary --plus | "!" ExpressionUnary --not + | "~" ExpressionUnary --bitwiseNot | ExpressionPrimary // Order is important diff --git a/src/grammar/grammar.ts b/src/grammar/grammar.ts index 05629db9b..cdb81c855 100644 --- a/src/grammar/grammar.ts +++ b/src/grammar/grammar.ts @@ -1095,6 +1095,14 @@ semantics.addOperation("astOfExpression", { ref: createRef(this), }); }, + ExpressionUnary_bitwiseNot(_tilde, operand) { + return createNode({ + kind: "op_unary", + op: "~", + right: operand.astOfExpression(), + ref: createRef(this), + }); + }, ExpressionParens(_lparen, expression, _rparen) { return expression.astOfExpression(); }, diff --git a/src/test/__snapshots__/feature-constants.spec.ts.snap b/src/test/__snapshots__/feature-constants.spec.ts.snap index cdadac1e5..4eb3077ca 100644 --- a/src/test/__snapshots__/feature-constants.spec.ts.snap +++ b/src/test/__snapshots__/feature-constants.spec.ts.snap @@ -265,6 +265,16 @@ ConstantTester { "type": "int", }, }, + { + "arguments": [], + "name": "something20", + "returnType": { + "format": 257, + "kind": "simple", + "optional": false, + "type": "int", + }, + }, { "arguments": [], "name": "minInt1", @@ -442,7 +452,7 @@ ConstantTester { }, ], }, - "address": kQBLPCFB6hZTJVoIVoSLp-zwDPnb5aQG3LnI_S3IHwDre99T, + "address": kQC0jy5E0g2P09qLQNz8948lt5CqpJwDVKDhilWC_jJpsHMo, "init": { "code": x{FF00F4A413F4BCF2C80B} x{62_} @@ -508,10 +518,15 @@ ConstantTester { x{85FF} x{2_} x{5} - x{A975DB3CDB3C31} - x{ED44D0D401F863D20030916DE0F828D70B0A8309BAF2E089DB3C} - x{6D} - x{7A} + x{2_} + x{A6EBB679B67863_} + x{ED44D0D401F863D20030916DE0F828D70B0A8309BAF2E089DB3C} + x{6D} + x{7A} + x{A6A9B679B67863_} + x{ED44D0D401F863D20030916DE0F828D70B0A8309BAF2E089DB3C} + x{6D} + x{80FA} x{AAA2DB3CDB3C31} x{ED44D0D401F863D20030916DE0F828D70B0A8309BAF2E089DB3C} x{6D} @@ -546,7 +561,7 @@ ConstantTester { x{ED44D0D401F863D20030916DE0F828D70B0A8309BAF2E089DB3C} x{6D} x{800B} - x{AEE3435697066733A2F2F516D5952584D7874336E78397331376A3874474D64315751526D574D6A556764756E75365536685A4C764758627182_} + x{AEE3435697066733A2F2F516D665575695850684879515A4C34525831794B71716D7243473374424E7572564E736A39463957696B4D48655182_} x{AA45DB3CDB3C31} x{ED44D0D401F863D20030916DE0F828D70B0A8309BAF2E089DB3C} x{6D} @@ -637,10 +652,15 @@ ConstantTester { x{85FF} x{2_} x{5} - x{A975DB3CDB3C31} - x{ED44D0D401F863D20030916DE0F828D70B0A8309BAF2E089DB3C} - x{6D} - x{7A} + x{2_} + x{A6EBB679B67863_} + x{ED44D0D401F863D20030916DE0F828D70B0A8309BAF2E089DB3C} + x{6D} + x{7A} + x{A6A9B679B67863_} + x{ED44D0D401F863D20030916DE0F828D70B0A8309BAF2E089DB3C} + x{6D} + x{80FA} x{AAA2DB3CDB3C31} x{ED44D0D401F863D20030916DE0F828D70B0A8309BAF2E089DB3C} x{6D} @@ -675,7 +695,7 @@ ConstantTester { x{ED44D0D401F863D20030916DE0F828D70B0A8309BAF2E089DB3C} x{6D} x{800B} - x{AEE3435697066733A2F2F516D5952584D7874336E78397331376A3874474D64315751526D574D6A556764756E75365536685A4C764758627182_} + x{AEE3435697066733A2F2F516D665575695850684879515A4C34525831794B71716D7243473374424E7572564E736A39463957696B4D48655182_} x{AA45DB3CDB3C31} x{ED44D0D401F863D20030916DE0F828D70B0A8309BAF2E089DB3C} x{6D} diff --git a/src/test/feature-constants.spec.ts b/src/test/feature-constants.spec.ts index 77ff7fbe7..5b58ab5a7 100644 --- a/src/test/feature-constants.spec.ts +++ b/src/test/feature-constants.spec.ts @@ -51,6 +51,7 @@ describe("feature-constants", () => { expect(await contract.getMinInt1()).toEqual( -115792089237316195423570985008687907853269984665640564039457584007913129639936n, ); + expect(await contract.getSomething20()).toEqual(-6n); expect(await contract.getGlobalConst()).toEqual(100n); }); }); diff --git a/src/test/feature-math.spec.ts b/src/test/feature-math.spec.ts index f1b916226..a17883265 100644 --- a/src/test/feature-math.spec.ts +++ b/src/test/feature-math.spec.ts @@ -51,6 +51,9 @@ describe("feature-math", () => { expect(await contract.getXor(2n, -3n)).toBe(-1n); expect(await contract.getXor(-2n, 3n)).toBe(-3n); expect(await contract.getXor(-2n, -3n)).toBe(3n); + expect(await contract.getBitwiseNot(2n)).toBe(-3n); + expect(await contract.getBitwiseNot(-2n)).toBe(1n); + expect(await contract.getBitwiseNot(6n)).toBe(-7n); // Augmented Assign expect(await contract.getAddAug(1n, 2n)).toBe(3n); @@ -464,5 +467,11 @@ describe("feature-math", () => { expect(await contract.getPrecedence10()).toBe(3n); expect(await contract.getPrecedence11()).toBe(3n); expect(await contract.getPrecedence12()).toBe(5n); + + // Test multiple unary operations in a row + expect(await contract.getBitwiseNot1(5n)).toBe(5n); + expect(await contract.getBitwiseNot2(5n)).toBe(-6n); + expect(await contract.getBitwiseNot3(5n)).toBe(4n); + expect(await contract.getBitwiseNot4(5n)).toBe(6n); }); }); diff --git a/src/test/features/constants.tact b/src/test/features/constants.tact index 87682154e..de0ec911c 100644 --- a/src/test/features/constants.tact +++ b/src/test/features/constants.tact @@ -20,6 +20,7 @@ contract ConstantTester { const something17: Int = 115792089237316195423570985008687907853269984665640564039457584007913129639935; const something18: Int = -(pow2(255) - 1 + pow2(255)); const something19: Int = -(pow2(255) - 1 + pow2(255)) - 1; + const something20: Int = ~5; init() { @@ -104,6 +105,10 @@ contract ConstantTester { get fun something19(): Int { return self.something19; } + + get fun something20(): Int { + return self.something20; + } get fun minInt1(): Int { return -115792089237316195423570985008687907853269984665640564039457584007913129639936; diff --git a/src/test/features/math.tact b/src/test/features/math.tact index 99a5c4ddb..214c57513 100644 --- a/src/test/features/math.tact +++ b/src/test/features/math.tact @@ -44,6 +44,10 @@ contract MathTester with Deployable { get fun xor(a: Int, b: Int): Int { return a ^ b; } + + get fun bitwise_not(a: Int): Int { + return ~a; + } // // Augmented assignment @@ -386,4 +390,20 @@ contract MathTester with Deployable { get fun precedence12(): Int { return 5 ^ (6 | 7) & 8; } + + get fun bitwiseNot1(x: Int): Int { + return ~~x; + } + + get fun bitwiseNot2(x: Int): Int { + return ~~~x; + } + + get fun bitwiseNot3(x: Int): Int { + return ~-x; + } + + get fun bitwiseNot4(x: Int): Int { + return -~x; + } } diff --git a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap index ee38e2019..682894398 100644 --- a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap +++ b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap @@ -276,6 +276,24 @@ Line 1, col 1: " `; +exports[`resolveDescriptors should fail descriptors for case-28 1`] = ` +":4:18: Cannot reduce expression to a constant integer +Line 4, col 18: + 3 | +> 4 | const a: Int = ~ true; + ^~~~ +" +`; + +exports[`resolveDescriptors should fail descriptors for case-29 1`] = ` +":4:17: Cannot reduce expression to a constant boolean +Line 4, col 17: + 3 | +> 4 | const a: Bool = ~ true; + ^~~~~~ +" +`; + exports[`resolveDescriptors should resolve descriptors for case-0 1`] = ` { "BaseTrait": { diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap index e9d62394c..d597fbaae 100644 --- a/src/types/__snapshots__/resolveStatements.spec.ts.snap +++ b/src/types/__snapshots__/resolveStatements.spec.ts.snap @@ -530,6 +530,16 @@ Line 5, col 5: " `; +exports[`resolveStatements should fail statements for case-53 1`] = ` +":5:12: Invalid type "Bool" for unary operator "~" +Line 5, col 12: + 4 | fun test(a: Bool): Int { +> 5 | return ~a; + ^~ + 6 | } +" +`; + exports[`resolveStatements should resolve statements for case-0 1`] = ` [ [ diff --git a/src/types/resolveConstantValue.ts b/src/types/resolveConstantValue.ts index e3ea6f862..f29edf9b2 100644 --- a/src/types/resolveConstantValue.ts +++ b/src/types/resolveConstantValue.ts @@ -37,6 +37,8 @@ function reduceIntImpl(ast: ASTExpression): bigint { return -reduceInt(ast.right); } else if (ast.op === "+") { return reduceInt(ast.right); + } else if (ast.op === "~") { + return ~reduceInt(ast.right); } } else if (ast.kind === "op_static_call") { if (ast.name === "ton") { diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts index d2e28491a..dc45a1ae6 100644 --- a/src/types/resolveExpression.ts +++ b/src/types/resolveExpression.ts @@ -311,7 +311,7 @@ function resolveUnaryOp( // Check right type dependent on operator let resolvedType = getExpType(ctx, exp.right); - if (exp.op === "-" || exp.op === "+") { + if (exp.op === "-" || exp.op === "+" || exp.op === "~") { if ( resolvedType.kind !== "ref" || resolvedType.optional || diff --git a/src/types/stmts-failed/case-53.tact b/src/types/stmts-failed/case-53.tact new file mode 100644 index 000000000..59cd48ad3 --- /dev/null +++ b/src/types/stmts-failed/case-53.tact @@ -0,0 +1,6 @@ +primitive Int; +primitive Bool; + +fun test(a: Bool): Int { + return ~a; +} \ No newline at end of file diff --git a/src/types/test-failed/case-28.tact b/src/types/test-failed/case-28.tact new file mode 100644 index 000000000..af7cb9b62 --- /dev/null +++ b/src/types/test-failed/case-28.tact @@ -0,0 +1,4 @@ +primitive Int; +primitive Bool; + +const a: Int = ~ true; \ No newline at end of file diff --git a/src/types/test-failed/case-29.tact b/src/types/test-failed/case-29.tact new file mode 100644 index 000000000..a6101c5ff --- /dev/null +++ b/src/types/test-failed/case-29.tact @@ -0,0 +1,4 @@ +primitive Int; +primitive Bool; + +const a: Bool = ~ true; \ No newline at end of file