From 3de0b0af51c7ac1d9ae9134f5ee584b96fa8209e Mon Sep 17 00:00:00 2001 From: Miguel Branco Date: Wed, 12 Jul 2023 15:05:30 +0200 Subject: [PATCH] RD-9239 Implement Neq operator != (#35) --- .../rql2/truffle/Rql2TruffleCompiler.scala | 1 + .../rql2/tests/spec/TruffleSpecTest.scala | 1 + .../rql2/FrontendSyntaxAnalyzer.scala | 1 + .../raw/compiler/rql2/ImplicitCasts.scala | 2 +- .../raw/compiler/rql2/SemanticAnalyzer.scala | 2 +- .../raw/compiler/rql2/source/SourceTree.scala | 2 + .../rql2/tests/spec/BinaryExpNeqTest.scala | 168 ++++++++++++++++++ 7 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 raw-compiler-rql2/src/test/scala/raw/compiler/rql2/tests/spec/BinaryExpNeqTest.scala diff --git a/raw-compiler-rql2-truffle/src/main/scala/raw/compiler/rql2/truffle/Rql2TruffleCompiler.scala b/raw-compiler-rql2-truffle/src/main/scala/raw/compiler/rql2/truffle/Rql2TruffleCompiler.scala index e7a5e8324..eec403c7b 100644 --- a/raw-compiler-rql2-truffle/src/main/scala/raw/compiler/rql2/truffle/Rql2TruffleCompiler.scala +++ b/raw-compiler-rql2-truffle/src/main/scala/raw/compiler/rql2/truffle/Rql2TruffleCompiler.scala @@ -434,6 +434,7 @@ class TruffleEmitterImpl(tree: Tree)(implicit programContext: ProgramContext) case BinaryExp(Gt(), e1, e2) => new GtNode(recurseExp(e1), recurseExp(e2)) case BinaryExp(Ge(), e1, e2) => new GeNode(recurseExp(e1), recurseExp(e2)) case BinaryExp(Eq(), e1, e2) => new EqNode(recurseExp(e1), recurseExp(e2)) + case BinaryExp(Neq(), e1, e2) => NotNodeGen.create(new EqNode(recurseExp(e1), recurseExp(e2))) case BinaryExp(Lt(), e1, e2) => new LtNode(recurseExp(e1), recurseExp(e2)) case BinaryExp(Le(), e1, e2) => new LeNode(recurseExp(e1), recurseExp(e2)) case BinaryConst(bytes) => new BinaryConstNode(bytes) diff --git a/raw-compiler-rql2-truffle/src/test/scala/raw/compiler/rql2/tests/spec/TruffleSpecTest.scala b/raw-compiler-rql2-truffle/src/test/scala/raw/compiler/rql2/tests/spec/TruffleSpecTest.scala index 798a9057d..54305b196 100644 --- a/raw-compiler-rql2-truffle/src/test/scala/raw/compiler/rql2/tests/spec/TruffleSpecTest.scala +++ b/raw-compiler-rql2-truffle/src/test/scala/raw/compiler/rql2/tests/spec/TruffleSpecTest.scala @@ -40,6 +40,7 @@ import raw.testing.tags.TruffleTests @TruffleTests class ClosureTruffleTest extends TruffleCompilerTestContext with ClosureTest @TruffleTests class BinaryExpOrTruffleTest extends TruffleCompilerTestContext with BinaryExpOrTest @TruffleTests class BinaryExpEqTruffleTest extends TruffleCompilerTestContext with BinaryExpEqTest +@TruffleTests class BinaryExpNeqTruffleTest extends TruffleCompilerTestContext with BinaryExpNeqTest @TruffleTests class ImplicitCastTruffleTest extends TruffleCompilerTestContext with ImplicitCastTest @TruffleTests class BinaryExpModTruffleTest extends TruffleCompilerTestContext with BinaryExpModTest @TruffleTests class BinaryExpSubTruffleTest extends TruffleCompilerTestContext with BinaryExpSubTest diff --git a/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/FrontendSyntaxAnalyzer.scala b/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/FrontendSyntaxAnalyzer.scala index 4b61d998a..f457008de 100644 --- a/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/FrontendSyntaxAnalyzer.scala +++ b/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/FrontendSyntaxAnalyzer.scala @@ -256,6 +256,7 @@ class FrontendSyntaxAnalyzer(val positions: Positions) } | exp5 final private lazy val compOp: Parser[ComparableOp] = "==" ^^^ Eq() | + "!=" ^^^ Neq() | "<=" ^^^ Le() | "<" ^^^ Lt() | ">=" ^^^ Ge() | diff --git a/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/ImplicitCasts.scala b/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/ImplicitCasts.scala index 115e17a6c..4541ce5f3 100644 --- a/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/ImplicitCasts.scala +++ b/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/ImplicitCasts.scala @@ -392,7 +392,7 @@ class ImplicitCasts(protected val parent: Phase[SourceProgram], protected val ph val ne2 = cast(re2, t2, t).getOrElse(re2) BinaryExp(op, ne1, ne2) } - case _: Ge | _: Gt | _: Le | _: Lt | _: Eq => congruence(id, s, s) <* rule[BinaryExp] { + case _: Ge | _: Gt | _: Le | _: Lt | _: Eq | _: Neq => congruence(id, s, s) <* rule[BinaryExp] { case BinaryExp(_, re1, re2) => val te1 = analyzer.tipe(e1) val te2 = analyzer.tipe(e2) diff --git a/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/SemanticAnalyzer.scala b/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/SemanticAnalyzer.scala index 3811cd7c1..4adc623ff 100644 --- a/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/SemanticAnalyzer.scala +++ b/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/SemanticAnalyzer.scala @@ -1782,7 +1782,7 @@ class SemanticAnalyzer(val tree: SourceTree.SourceTree)(implicit programContext: } op match { - case _: Eq => expected(rql2numericsTemporalsStringsBools, None) + case _: Eq | _: Neq => expected(rql2numericsTemporalsStringsBools, None) case _: ComparableOp => expected(rql2numericsTemporalsString, None) case _: BooleanOp => expected(rql2bool, Some(rql2bool)) case _: Plus => expected(rql2numbersAndString, None) diff --git a/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/source/SourceTree.scala b/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/source/SourceTree.scala index 5abdc58c2..9a1497fc7 100644 --- a/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/source/SourceTree.scala +++ b/raw-compiler-rql2/src/main/scala/raw/compiler/rql2/source/SourceTree.scala @@ -386,6 +386,8 @@ final case class Lt() extends ComparableOp("<", 7) final case class Eq() extends ComparableOp("==", 8) +final case class Neq() extends ComparableOp("!=", 8) + sealed abstract class BooleanOp(op: String, priority: Int) extends BinaryOp(op, priority) final case class And() extends BooleanOp("and", 12) diff --git a/raw-compiler-rql2/src/test/scala/raw/compiler/rql2/tests/spec/BinaryExpNeqTest.scala b/raw-compiler-rql2/src/test/scala/raw/compiler/rql2/tests/spec/BinaryExpNeqTest.scala new file mode 100644 index 000000000..d3bac990e --- /dev/null +++ b/raw-compiler-rql2/src/test/scala/raw/compiler/rql2/tests/spec/BinaryExpNeqTest.scala @@ -0,0 +1,168 @@ +/* + * Copyright 2023 RAW Labs S.A. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.txt. + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0, included in the file + * licenses/APL.txt. + */ + +package raw.compiler.rql2.tests.spec + +import raw.TestData +import raw.compiler.rql2.tests.CompilerTestContext + +trait BinaryExpNeqTest extends CompilerTestContext with CombinationSpecTestHelper { + + test("1 != 1")(it => it should evaluateTo("false")) + test("1 != 2")(it => it should evaluateTo("true")) + test("2 != 1")(it => it should evaluateTo("true")) + + test("Time.Build(9, 30) != Time.Build(9, 30)")(it => it should evaluateTo("false")) + test("Time.Build(9, 30) != Time.Build(10, 30)")(it => it should evaluateTo("true")) + test("Time.Build(9, 30) != 123")(it => it should typeErrorAs("time but got int")) + + test("Date.Build(2022, 1, 15) != Date.Build(2022, 1, 15)")(it => it should evaluateTo("false")) + test("Date.Build(2022, 1, 15) != Timestamp.Build(2022, 1, 15, 0, 0)")(it => it should evaluateTo("false")) + test("Date.Build(2022, 1, 15) != Date.Build(2010, 1, 15)")(it => it should evaluateTo("true")) + test("Date.Build(2022, 1, 15) != Time.Build(10, 15)")(it => + it should typeErrorAs("expected compatible with date but got time") + ) + + test("""Nullable.Build(1) != 1""")(_ should evaluateTo("false")) + test("""Nullable.Build(1) != 2""")(_ should evaluateTo("true")) + test("""Nullable.Build(1) != Nullable.Build(1)""")(_ should evaluateTo("false")) + test("""Nullable.Build(1) != Nullable.Build(2)""")(_ should evaluateTo("true")) + + test("""1 != null""")(_ should evaluateTo("null")) + test("""null != 1""")(_ should evaluateTo("null")) + test("""(1 + null) != (1 + null)""")(_ should evaluateTo("null")) + test("""(1 + null) != Nullable.Build(1)""")(_ should evaluateTo("null")) + test("""Nullable.Build(1) != (1 + null)""")(_ should evaluateTo("null")) + + test("""null != null""")(_ should (typeAs("bool") and evaluateTo("null"))) + + test("let t: time = Time.Build(10, 0) in t != Time.Build(10, 0)")(it => it should evaluateTo("false")) + + // try support. Error propagate in all cases. + test("""1 != Success.Build(1)""")(_ should evaluateTo("false")) + test("""1 != Success.Build(2)""")(_ should evaluateTo("true")) + test("""Success.Build(1) != 1""")(_ should evaluateTo("false")) + test("""Success.Build(1) != 2""")(_ should evaluateTo("true")) + test("""Success.Build(1) != Success.Build(1)""")(_ should evaluateTo("false")) + test("""Success.Build(1) != Success.Build(2)""")(_ should evaluateTo("true")) + + test("""1 + Error.Build("argh!") != 1 + Error.Build("gasp!")""")(_ should runErrorAs("argh!")) + test("""1 + Error.Build("gasp!") != 1 + Error.Build("argh!")""")(_ should runErrorAs("gasp!")) + test("""Error.Build("argh!") != Error.Build("argh!")""")(_ should (typeAs("bool") and runErrorAs("argh!"))) + test("""Error.Build("gasp!") != Error.Build("argh!")""")(_ should (typeAs("bool") and runErrorAs("gasp!"))) + test("""1 != Error.Build("argh!")""")(_ should runErrorAs("argh!")) + test("""Error.Build("argh!") != 1""")(_ should runErrorAs("argh!")) + + test("""let x: int = Success.Build(1) in 1 != x""")(_ should evaluateTo("false")) + test("""let x: int = Success.Build(2) in 1 != x""")(_ should evaluateTo("true")) + test("""let x: int = Success.Build(1) in x != 1""")(_ should evaluateTo("false")) + test("""let x: int = Success.Build(1) in x != 2""")(_ should evaluateTo("true")) + test("""let x: int = Success.Build(1), y: int = Success.Build(1) in y != x""")(_ should evaluateTo("false")) + test("""let x: int = Success.Build(1), y: int = Success.Build(2) in x != y""")(_ should evaluateTo("true")) + test("""let x: int = Success.Build(1), y: int = Error.Build("argh!") in y != x""")(_ should runErrorAs("argh!")) + test("""let x: int = Success.Build(1), y: int = Error.Build("argh!") in x != y""")(_ should runErrorAs("argh!")) + test("""let x: int = Error.Build("argh!"), y: int = Error.Build("gasp!") in x != y""")(_ should runErrorAs("argh!")) + test("""let x: int = Error.Build("argh!"), y: int = Error.Build("gasp!") in y != x""")(_ should runErrorAs("gasp!")) + test("""let x: int = Error.Build("argh!") in 1 != x""")(_ should runErrorAs("argh!")) + test("""let x: int = Error.Build("argh!") in x != 1""")(_ should runErrorAs("argh!")) + + val numbers = Table( + "numbers", + TestValue("byte", "1b", "2b"), + TestValue("short", "Short.From(1)", "Short.From(2)"), + TestValue("int", "1", "2"), + TestValue("long", "1l", "2l"), + TestValue("float", "1f", "2f"), + TestValue("double", "1d", "2d"), + TestValue("decimal", "Decimal.From(1)", "Decimal.From(2)") + ) + + val nonComparable = Table( + "non-comparable", + TestValue("record(a: int)", "{a: 1}", "{a: 2}"), + TestValue("collection(int)", "Collection.Build(1, 2, 3)", "Collection.Build(4, 5, 6)"), + TestValue("list(int)", "[1, 2, 3]", "[4, 5, 6]"), + TestValue("binary", """Binary.FromString("Hello") """, """Binary.FromString("World") """) + ) + + val dateTimestamp = Table( + "date-timestamp", + TestValue("date", "Date.Build(2023, 2, 6)", "Date.Build(2022, 1, 5)"), + TestValue("timestamp", "Timestamp.Build(2023, 2, 6, 0, 0)", "Timestamp.Build(2022, 1, 5, 0, 0)") + ) + + val nonNumbers = Table( + "non-numbers", + TestValue("string", """ "hello!" """, """ "world!" """), + TestValue("bool", "true", "false"), + TestValue("time", "Time.Build(10, 0)", "Time.Build(9, 0)"), + TestValue("interval", "Interval.Build(years=1, months=2, days=3)", "Interval.Build(hours=3, minutes=2, seconds=1)") + ) ++ dateTimestamp + + test(" number != number") { _ => + forAll(combinations(numbers, numbers)) { + case (n1, n2) => + TestData(s"${n1.v1} != ${n2.v1}") should evaluateTo("false") + TestData(s"${n1.v1} != ${n2.v2}") should evaluateTo("true") + } + } + + test("date timestamp != date timestamp") { _ => + forAll(combinations(dateTimestamp, dateTimestamp)) { + case (n1, n2) => + TestData(s"${n1.v1} != ${n2.v1}") should evaluateTo("false") + TestData(s"${n1.v1} != ${n2.v2}") should evaluateTo("true") + } + } + + test("number != non-number") { _ => + forAll(combinations(numbers, nonNumbers)) { + case (n, x) => + TestData(s"${n.v1} != ${x.v1}") should typeErrorAs(s"expected compatible with ${n.tipe} but got ${x.tipe}") + TestData(s"${x.v1} != ${n.v1}") should typeErrorAs(s"expected compatible with ${x.tipe} but got ${n.tipe}") + } + } + + test("number != non-comparable") { _ => + forAll(combinations(numbers, nonComparable)) { + case (n, x) => + TestData(s"${n.v1} != ${x.v1}") should typeErrorAs(s"expected compatible with ${n.tipe} but got ${x.tipe}") + TestData(s"${x.v1} != ${n.v1}") should typeErrorAs(s"expected compatible with ${x.tipe} but got ${n.tipe}") + } + } + + test("non-number != non-number") { _ => + forAll(nonNumbers) { x => + TestData(s"${x.v1} != ${x.v1}") should evaluateTo("false") + TestData(s"${x.v1} != ${x.v2}") should evaluateTo("true") + } + } + + test("non-comparable != non-comparable") { _ => + forAll(nonComparable) { x => + TestData(s"${x.v1} != ${x.v1}") should typeErrorAs( + s"expected either number or temporal or bool or string but got ${x.tipe}" + ) + } + } + + test("error != value") { _ => + forAll(nonNumbers ++ numbers)(x => TestData(s"""${x.v1} != Error.Build("argh!")""") should runErrorAs("argh!")) + } + + test("success comparable != comparable") { _ => + forAll(nonNumbers ++ numbers) { x => + TestData(s"""let x: ${x.tipe} = ${x.v1} in x != ${x.v1}""") should evaluateTo("false") + TestData(s"""let x: ${x.tipe} = ${x.v1} in x != ${x.v2}""") should evaluateTo("true") + } + } +}