From ef7165e1dd60d4d11eec743f1fb88b632b474676 Mon Sep 17 00:00:00 2001 From: Daniil Sedov <42098239+Gusarich@users.noreply.github.com> Date: Fri, 16 Feb 2024 15:22:15 +0300 Subject: [PATCH] feat: Slices and Strings equality comparisons (#105) We actually compare the hashes for both slices and strings, essentially using the `HASHSU` TVM instruction under-the-hood. --------- Co-authored-by: Anton Trunov --- CHANGELOG.md | 1 + .../writeSerialization.spec.ts.snap | 264 ++++++++++++++++++ src/generator/writers/writeExpression.ts | 24 ++ src/generator/writers/writeStdlib.ts | 74 +++++ src/test/feature-math.spec.ts | 106 +++++++ src/test/features/math.tact | 72 +++++ src/types/resolveExpression.ts | 2 +- 7 files changed, 542 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99c753e46..1cc7a17d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Decimal and hexadecimal literals now allow underscores as numerical separators: PR [#99](https://github.com/tact-lang/tact/pull/99) +- The equality and non-equality operators (`==` and `!=`) now support slices and strings by comparing the hashes of the left-hand and right-hand sides : PR [#105](https://github.com/tact-lang/tact/pull/105) ### Fixed - Relative imports from parent directories: PR [#125](https://github.com/tact-lang/tact/pull/125) diff --git a/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap b/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap index c6dc23c0c..22d73653d 100644 --- a/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap +++ b/src/generator/writers/__snapshots__/writeSerialization.spec.ts.snap @@ -1068,6 +1068,94 @@ return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_nu "name": "__tact_cell_neq_nullable", "signature": "int __tact_cell_neq_nullable(cell a, cell b)", }, + { + "code": { + "code": "return (a.slice_hash() == b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq", + "signature": "int __tact_slice_eq(slice a, slice b)", + }, + { + "code": { + "code": "return (a.slice_hash() != b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq", + "signature": "int __tact_slice_neq(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (a.slice_hash() == b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_nullable_one", + "signature": "int __tact_slice_eq_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (true) : (a.slice_hash() != b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq_nullable_one", + "signature": "int __tact_slice_neq_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() == b.slice_hash() ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_nullable", + "signature": "int __tact_slice_eq_nullable(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() != b.slice_hash() ) : ( true ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq_nullable", + "signature": "int __tact_slice_neq_nullable(slice a, slice b)", + }, { "code": { "code": "return udict_set_ref(dict, 16, id, code);", @@ -4803,6 +4891,94 @@ return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_nu "name": "__tact_cell_neq_nullable", "signature": "int __tact_cell_neq_nullable(cell a, cell b)", }, + { + "code": { + "code": "return (a.slice_hash() == b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq", + "signature": "int __tact_slice_eq(slice a, slice b)", + }, + { + "code": { + "code": "return (a.slice_hash() != b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq", + "signature": "int __tact_slice_neq(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (a.slice_hash() == b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_nullable_one", + "signature": "int __tact_slice_eq_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (true) : (a.slice_hash() != b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq_nullable_one", + "signature": "int __tact_slice_neq_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() == b.slice_hash() ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_nullable", + "signature": "int __tact_slice_eq_nullable(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() != b.slice_hash() ) : ( true ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq_nullable", + "signature": "int __tact_slice_neq_nullable(slice a, slice b)", + }, { "code": { "code": "return udict_set_ref(dict, 16, id, code);", @@ -8538,6 +8714,94 @@ return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_nu "name": "__tact_cell_neq_nullable", "signature": "int __tact_cell_neq_nullable(cell a, cell b)", }, + { + "code": { + "code": "return (a.slice_hash() == b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq", + "signature": "int __tact_slice_eq(slice a, slice b)", + }, + { + "code": { + "code": "return (a.slice_hash() != b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq", + "signature": "int __tact_slice_neq(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (false) : (a.slice_hash() == b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_nullable_one", + "signature": "int __tact_slice_eq_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "return (null?(a)) ? (true) : (a.slice_hash() != b.slice_hash());", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq_nullable_one", + "signature": "int __tact_slice_neq_nullable_one(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() == b.slice_hash() ) : ( false ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_eq_nullable", + "signature": "int __tact_slice_eq_nullable(slice a, slice b)", + }, + { + "code": { + "code": "var a_is_null = null?(a); +var b_is_null = null?(b); +return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() != b.slice_hash() ) : ( true ) );", + "kind": "generic", + }, + "comment": null, + "context": "stdlib", + "depends": Set {}, + "flags": Set { + "inline", + }, + "name": "__tact_slice_neq_nullable", + "signature": "int __tact_slice_neq_nullable(slice a, slice b)", + }, { "code": { "code": "return udict_set_ref(dict, 16, id, code);", diff --git a/src/generator/writers/writeExpression.ts b/src/generator/writers/writeExpression.ts index 584aad764..a8b70305c 100644 --- a/src/generator/writers/writeExpression.ts +++ b/src/generator/writers/writeExpression.ts @@ -261,6 +261,30 @@ export function writeExpression(f: ASTExpression, ctx: WriterContext): string { return `__tact_cell_${op}(${writeExpression(f.right, ctx)}, ${writeExpression(f.left, ctx)})`; } + // Case for slices and strings equality + if ( + lt.kind === 'ref' && + rt.kind === 'ref' && + lt.name === rt.name && + (lt.name === 'Slice' || lt.name === 'String') + ) { + let op = f.op === '==' ? 'eq' : 'neq'; + if (lt.optional && rt.optional) { + ctx.used(`__tact_slice_${op}_nullable`); + return `__tact_slice_${op}_nullable(${writeExpression(f.left, ctx)}, ${writeExpression(f.right, ctx)})`; + } + if (lt.optional && !rt.optional) { + ctx.used(`__tact_slice_${op}_nullable_one`); + return `__tact_slice_${op}_nullable_one(${writeExpression(f.left, ctx)}, ${writeExpression(f.right, ctx)})`; + } + if (!lt.optional && rt.optional) { + ctx.used(`__tact_slice_${op}_nullable_one`); + return `__tact_slice_${op}_nullable_one(${writeExpression(f.right, ctx)}, ${writeExpression(f.left, ctx)})`; + } + ctx.used(`__tact_slice_${op}`); + return `__tact_slice_${op}(${writeExpression(f.right, ctx)}, ${writeExpression(f.left, ctx)})`; + } + // Case for maps eqality if (lt.kind === 'map' && rt.kind === 'map') { let op = f.op === '==' ? 'eq' : 'neq'; diff --git a/src/generator/writers/writeStdlib.ts b/src/generator/writers/writeStdlib.ts index d3c5ce96c..439bb06cf 100644 --- a/src/generator/writers/writeStdlib.ts +++ b/src/generator/writers/writeStdlib.ts @@ -862,6 +862,80 @@ export function writeStdlib(ctx: WriterContext) { }); }); + // + // Slice Eq + // + + ctx.fun(`__tact_slice_eq`, () => { + ctx.signature(`int __tact_slice_eq(slice a, slice b)`); + ctx.flag('inline'); + ctx.context('stdlib'); + ctx.body(() => { + ctx.write(` + return (a.slice_hash() == b.slice_hash()); + `); + }); + }); + + ctx.fun(`__tact_slice_neq`, () => { + ctx.signature(`int __tact_slice_neq(slice a, slice b)`); + ctx.flag('inline'); + ctx.context('stdlib'); + ctx.body(() => { + ctx.write(` + return (a.slice_hash() != b.slice_hash()); + `); + }); + }); + + ctx.fun(`__tact_slice_eq_nullable_one`, () => { + ctx.signature(`int __tact_slice_eq_nullable_one(slice a, slice b)`); + ctx.flag('inline'); + ctx.context('stdlib'); + ctx.body(() => { + ctx.write(` + return (null?(a)) ? (false) : (a.slice_hash() == b.slice_hash()); + `); + }); + }); + + ctx.fun(`__tact_slice_neq_nullable_one`, () => { + ctx.signature(`int __tact_slice_neq_nullable_one(slice a, slice b)`); + ctx.flag('inline'); + ctx.context('stdlib'); + ctx.body(() => { + ctx.write(` + return (null?(a)) ? (true) : (a.slice_hash() != b.slice_hash()); + `); + }); + }); + + ctx.fun(`__tact_slice_eq_nullable`, () => { + ctx.signature(`int __tact_slice_eq_nullable(slice a, slice b)`); + ctx.flag('inline'); + ctx.context('stdlib'); + ctx.body(() => { + ctx.write(` + var a_is_null = null?(a); + var b_is_null = null?(b); + return ( a_is_null & b_is_null ) ? ( true ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() == b.slice_hash() ) : ( false ) ); + `); + }); + }); + + ctx.fun(`__tact_slice_neq_nullable`, () => { + ctx.signature(`int __tact_slice_neq_nullable(slice a, slice b)`); + ctx.flag('inline'); + ctx.context('stdlib'); + ctx.body(() => { + ctx.write(` + var a_is_null = null?(a); + var b_is_null = null?(b); + return ( a_is_null & b_is_null ) ? ( false ) : ( ( ( ~ a_is_null ) & ( ~ b_is_null ) ) ? ( a.slice_hash() != b.slice_hash() ) : ( true ) ); + `); + }); + }); + // // Sys Dict // diff --git a/src/test/feature-math.spec.ts b/src/test/feature-math.spec.ts index 434e32438..5c7dbaa5f 100644 --- a/src/test/feature-math.spec.ts +++ b/src/test/feature-math.spec.ts @@ -17,6 +17,16 @@ describe('feature-math', () => { let addressB = randomAddress('b'); let cellA = beginCell().storeUint(0, 32).endCell(); let cellB = beginCell().storeUint(1, 32).endCell(); + let sliceA = beginCell() + .storeBit(0) + .storeRef(beginCell().storeBit(1).endCell()) + .endCell(); + let sliceB = beginCell() + .storeBit(1) + .storeRef(beginCell().storeBit(1).endCell()) + .endCell(); + let stringA = "foo"; + let stringB = "bar"; let dictA = Dictionary.empty().set(0n, 0n); let dictB = Dictionary.empty().set(0n, 2n); await contract.send(treasure, { value: toNano('10') }, { $$type: 'Deploy', queryId: 0n }); @@ -222,6 +232,102 @@ describe('feature-math', () => { expect(await contract.getCompare26(null, null)).toBe(false); expect(await contract.getCompare26(null, null)).toBe(false); + // Slice equals + expect(await contract.getCompare29(sliceA, sliceB)).toBe(false); + expect(await contract.getCompare29(sliceB, sliceA)).toBe(false); + expect(await contract.getCompare29(sliceA, sliceA)).toBe(true); + expect(await contract.getCompare30(sliceA, sliceB)).toBe(false); + expect(await contract.getCompare30(sliceB, sliceA)).toBe(false); + expect(await contract.getCompare30(sliceA, sliceA)).toBe(true); + expect(await contract.getCompare30(sliceB, null)).toBe(false); + expect(await contract.getCompare30(sliceA, null)).toBe(false); + expect(await contract.getCompare31(sliceA, sliceB)).toBe(false); + expect(await contract.getCompare31(sliceB, sliceA)).toBe(false); + expect(await contract.getCompare31(sliceA, sliceA)).toBe(true); + expect(await contract.getCompare31(null, sliceB)).toBe(false); + expect(await contract.getCompare31(null, sliceA)).toBe(false); + expect(await contract.getCompare32(sliceA, sliceB)).toBe(false); + expect(await contract.getCompare32(sliceB, sliceA)).toBe(false); + expect(await contract.getCompare32(sliceA, sliceA)).toBe(true); + expect(await contract.getCompare32(null, sliceB)).toBe(false); + expect(await contract.getCompare32(null, sliceA)).toBe(false); + expect(await contract.getCompare32(sliceB, null)).toBe(false); + expect(await contract.getCompare32(sliceA, null)).toBe(false); + expect(await contract.getCompare32(null, null)).toBe(true); + expect(await contract.getCompare32(null, null)).toBe(true); + + // Slice not equals + expect(await contract.getCompare33(sliceA, sliceB)).toBe(true); + expect(await contract.getCompare33(sliceB, sliceA)).toBe(true); + expect(await contract.getCompare33(sliceA, sliceA)).toBe(false); + expect(await contract.getCompare34(sliceA, sliceB)).toBe(true); + expect(await contract.getCompare34(sliceB, sliceA)).toBe(true); + expect(await contract.getCompare34(sliceA, sliceA)).toBe(false); + expect(await contract.getCompare34(sliceB, null)).toBe(true); + expect(await contract.getCompare34(sliceA, null)).toBe(true); + expect(await contract.getCompare35(sliceA, sliceB)).toBe(true); + expect(await contract.getCompare35(sliceB, sliceA)).toBe(true); + expect(await contract.getCompare35(sliceA, sliceA)).toBe(false); + expect(await contract.getCompare35(null, sliceB)).toBe(true); + expect(await contract.getCompare35(null, sliceA)).toBe(true); + expect(await contract.getCompare36(sliceA, sliceB)).toBe(true); + expect(await contract.getCompare36(sliceB, sliceA)).toBe(true); + expect(await contract.getCompare36(sliceA, sliceA)).toBe(false); + expect(await contract.getCompare36(null, sliceB)).toBe(true); + expect(await contract.getCompare36(null, sliceA)).toBe(true); + expect(await contract.getCompare36(sliceB, null)).toBe(true); + expect(await contract.getCompare36(sliceA, null)).toBe(true); + expect(await contract.getCompare36(null, null)).toBe(false); + expect(await contract.getCompare36(null, null)).toBe(false); + + // string equals + expect(await contract.getCompare37(stringA, stringB)).toBe(false); + expect(await contract.getCompare37(stringB, stringA)).toBe(false); + expect(await contract.getCompare37(stringA, stringA)).toBe(true); + expect(await contract.getCompare38(stringA, stringB)).toBe(false); + expect(await contract.getCompare38(stringB, stringA)).toBe(false); + expect(await contract.getCompare38(stringA, stringA)).toBe(true); + expect(await contract.getCompare38(stringB, null)).toBe(false); + expect(await contract.getCompare38(stringA, null)).toBe(false); + expect(await contract.getCompare39(stringA, stringB)).toBe(false); + expect(await contract.getCompare39(stringB, stringA)).toBe(false); + expect(await contract.getCompare39(stringA, stringA)).toBe(true); + expect(await contract.getCompare39(null, stringB)).toBe(false); + expect(await contract.getCompare39(null, stringA)).toBe(false); + expect(await contract.getCompare40(stringA, stringB)).toBe(false); + expect(await contract.getCompare40(stringB, stringA)).toBe(false); + expect(await contract.getCompare40(stringA, stringA)).toBe(true); + expect(await contract.getCompare40(null, stringB)).toBe(false); + expect(await contract.getCompare40(null, stringA)).toBe(false); + expect(await contract.getCompare40(stringB, null)).toBe(false); + expect(await contract.getCompare40(stringA, null)).toBe(false); + expect(await contract.getCompare40(null, null)).toBe(true); + expect(await contract.getCompare40(null, null)).toBe(true); + + // string not equals + expect(await contract.getCompare41(stringA, stringB)).toBe(true); + expect(await contract.getCompare41(stringB, stringA)).toBe(true); + expect(await contract.getCompare41(stringA, stringA)).toBe(false); + expect(await contract.getCompare42(stringA, stringB)).toBe(true); + expect(await contract.getCompare42(stringB, stringA)).toBe(true); + expect(await contract.getCompare42(stringA, stringA)).toBe(false); + expect(await contract.getCompare42(stringB, null)).toBe(true); + expect(await contract.getCompare42(stringA, null)).toBe(true); + expect(await contract.getCompare43(stringA, stringB)).toBe(true); + expect(await contract.getCompare43(stringB, stringA)).toBe(true); + expect(await contract.getCompare43(stringA, stringA)).toBe(false); + expect(await contract.getCompare43(null, stringB)).toBe(true); + expect(await contract.getCompare43(null, stringA)).toBe(true); + expect(await contract.getCompare44(stringA, stringB)).toBe(true); + expect(await contract.getCompare44(stringB, stringA)).toBe(true); + expect(await contract.getCompare44(stringA, stringA)).toBe(false); + expect(await contract.getCompare44(null, stringB)).toBe(true); + expect(await contract.getCompare44(null, stringA)).toBe(true); + expect(await contract.getCompare44(stringB, null)).toBe(true); + expect(await contract.getCompare44(stringA, null)).toBe(true); + expect(await contract.getCompare44(null, null)).toBe(false); + expect(await contract.getCompare44(null, null)).toBe(false); + // Test maps expect(await contract.getCompare27(dictA, dictB)).toBe(false); expect(await contract.getCompare27(dictB, dictA)).toBe(false); diff --git a/src/test/features/math.tact b/src/test/features/math.tact index b5bebcaae..a66d497e9 100644 --- a/src/test/features/math.tact +++ b/src/test/features/math.tact @@ -196,6 +196,78 @@ contract MathTester with Deployable { return a != b; } + // + // Slice compare + // + + get fun compare29(a: Slice, b: Slice): Bool { + return a == b; + } + + get fun compare30(a: Slice, b: Slice?): Bool { + return a == b; + } + + get fun compare31(a: Slice?, b: Slice): Bool { + return a == b; + } + + get fun compare32(a: Slice?, b: Slice?): Bool { + return a == b; + } + + get fun compare33(a: Slice, b: Slice): Bool { + return a != b; + } + + get fun compare34(a: Slice, b: Slice?): Bool { + return a != b; + } + + get fun compare35(a: Slice?, b: Slice): Bool { + return a != b; + } + + get fun compare36(a: Slice?, b: Slice?): Bool { + return a != b; + } + + // + // String compare + // + + get fun compare37(a: String, b: String): Bool { + return a == b; + } + + get fun compare38(a: String, b: String?): Bool { + return a == b; + } + + get fun compare39(a: String?, b: String): Bool { + return a == b; + } + + get fun compare40(a: String?, b: String?): Bool { + return a == b; + } + + get fun compare41(a: String, b: String): Bool { + return a != b; + } + + get fun compare42(a: String, b: String?): Bool { + return a != b; + } + + get fun compare43(a: String?, b: String): Bool { + return a != b; + } + + get fun compare44(a: String?, b: String?): Bool { + return a != b; + } + // // IsNull/IsNotNull // diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts index f86b1cca8..02a7a902e 100644 --- a/src/types/resolveExpression.ts +++ b/src/types/resolveExpression.ts @@ -138,7 +138,7 @@ function resolveBinaryOp(exp: ASTOpBinary, sctx: StatementContext, ctx: Compiler if (l.name !== r.name) { throwError(`Incompatible types "${printTypeRef(le)}" and "${printTypeRef(re)}" for binary operator "${exp.op}"`, exp.ref); } - if (r.name !== 'Int' && r.name !== 'Bool' && r.name !== 'Address' && r.name !== 'Cell') { + if (r.name !== 'Int' && r.name !== 'Bool' && r.name !== 'Address' && r.name !== 'Cell' && r.name !== 'Slice' && r.name !== 'String') { throwError(`Invalid type "${r.name}" for binary operator "${exp.op}"`, exp.ref); } }