diff --git a/build.gradle.kts b/build.gradle.kts index f492b8e..82945a6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ repositories { mavenCentral() } -version = "1.3.1-SNAPSHOT" +version = "1.4.0-SNAPSHOT" group = "org.hisp.dhis.lib.expression" if (project.hasProperty("removeSnapshotSuffix")) { diff --git a/src/commonMain/kotlin/org/hisp/dhis/lib/expression/ast/BinaryOperator.kt b/src/commonMain/kotlin/org/hisp/dhis/lib/expression/ast/BinaryOperator.kt index c4848ae..3866bfe 100644 --- a/src/commonMain/kotlin/org/hisp/dhis/lib/expression/ast/BinaryOperator.kt +++ b/src/commonMain/kotlin/org/hisp/dhis/lib/expression/ast/BinaryOperator.kt @@ -201,16 +201,19 @@ enum class BinaryOperator(val symbol: String, private val returnType: ValueType, if (left == null || right == null) { return left == null && right == null } - if (left is Boolean || right is Boolean) { - return Typed.toBooleanTypeCoercion(left) == Typed.toBooleanTypeCoercion(right) + if (left is Number || right is Number) { + val leftNum = tryToNumber(left) ?: return false + val rightNum = tryToNumber(right) ?: return false + return leftNum == rightNum } - return if (left is Number || right is Number) { - Typed.toNumberTypeCoercion(left) == Typed.toNumberTypeCoercion( - right - ) + return if (left is Boolean || right is Boolean) { + Typed.toBooleanTypeCoercion(left) == Typed.toBooleanTypeCoercion(right) } else left == right } + private fun tryToNumber(value: Any?): Double? { + return try { Typed.toNumberTypeCoercion(value) } catch (_: Exception) { null } + } fun notEqual(left: Any?, right: Any?): Boolean { return !equal(left, right) diff --git a/src/commonTest/kotlin/org/hisp/dhis/lib/expression/operator/CompareExpressionTest.kt b/src/commonTest/kotlin/org/hisp/dhis/lib/expression/operator/CompareExpressionTest.kt index 49626be..6ef26a0 100644 --- a/src/commonTest/kotlin/org/hisp/dhis/lib/expression/operator/CompareExpressionTest.kt +++ b/src/commonTest/kotlin/org/hisp/dhis/lib/expression/operator/CompareExpressionTest.kt @@ -87,6 +87,28 @@ internal class CompareExpressionTest { assertEquals(true, evaluate("'hi' == \"hi\"")) assertEquals(true, evaluate("'true' == true")) assertEquals(true, evaluate("true == 'true'")) + + // Any value that cannot be coerced to a number is not equal to any number + assertEquals(false, evaluate("2 == ''")) + assertEquals(false, evaluate("2 == 'any string that is not a number'")) + + // Booleans are coerced to numbers (true→1, false→0) when compared with a number + assertEquals(true, evaluate("1 == true")) + assertEquals(true, evaluate("0 == false")) + assertEquals(false, evaluate("0 == true")) + assertEquals(false, evaluate("1 == false")) + assertEquals(true, evaluate("1.0 == true")) + assertEquals(true, evaluate("0.0 == false")) + assertEquals(false, evaluate("2.5 == true")) + assertEquals(false, evaluate("2.5 == false")) + assertEquals(false, evaluate("2 == true")) + assertEquals(false, evaluate("2 == false")) + + // Any string other than 'true' is coerced to false + assertEquals(true, evaluate("false == ''")) + assertEquals(true, evaluate("false == 'any string that is not a boolean'")) + assertEquals(false, evaluate("true == ''")) + assertEquals(false, evaluate("true == 'any string that is not a boolean'")) } @Test @@ -94,12 +116,6 @@ internal class CompareExpressionTest { assertEquals(false, evaluate("2 == 2 / 0")) } - @Test - fun testIncompatibleTypes() { - val ex = assertFailsWith(IllegalArgumentException::class) { evaluate("2.1 == false") } - assertEquals("Could not coerce Double '2.1' to Boolean", ex.message) - } - companion object { private fun evaluate(expression: String): Any? { return Expression(expression).evaluate()