Align EQ null semantics

This commit is contained in:
Paul Masurel
2026-08-18 14:51:15 +02:00
parent 2e018ea46e
commit 5a7d9fc96e
+19 -26
View File
@@ -1,7 +1,8 @@
// EQ compares two values of any type.
//
// Values of the same type compare directly. Numerical values also compare across
// i64, u64, and f64. Values of unrelated types are not equal.
// i64, u64, and f64. Values of unrelated present types are not equal. If either
// operand is absent, the result is absent.
//
// In other words:
// 1u64 == 1i64 ==> true
@@ -105,13 +106,11 @@ impl FnCall for EqFnCall {
let rhs_type = self.args[1].return_type;
let lhs = context.compile_expr(&self.args[0], builder)?;
let rhs = context.compile_expr(&self.args[1], builder)?;
let is_present = builder.ins().iconst(types::I8, 1);
let is_present = builder.ins().band(lhs.is_present, rhs.is_present);
let string_len = builder.ins().iconst(types::I64, 0);
if lhs_type == VarType::None || rhs_type == VarType::None {
let value = builder
.ins()
.icmp(IntCC::Equal, lhs.is_present, rhs.is_present);
let value = builder.ins().iconst(types::I8, 0);
return Ok(LoweredValue {
value,
is_present,
@@ -135,7 +134,7 @@ impl FnCall for EqFnCall {
}
if !types_are_comparable(lhs_type, rhs_type) {
let value = both_absent(lhs.is_present, rhs.is_present, builder);
let value = builder.ins().iconst(types::I8, 0);
return Ok(LoweredValue {
value,
is_present,
@@ -168,10 +167,7 @@ impl FnCall for EqFnCall {
}
_ => unreachable!("the operand types were checked above"),
};
let both_present = builder.ins().band(lhs.is_present, rhs.is_present);
let present_and_equal = builder.ins().band(both_present, values_equal);
let both_absent = both_absent(lhs.is_present, rhs.is_present, builder);
let value = builder.ins().bor(both_absent, present_and_equal);
let value = builder.ins().band(is_present, values_equal);
Ok(LoweredValue {
value,
is_present,
@@ -180,16 +176,6 @@ impl FnCall for EqFnCall {
}
}
fn both_absent(
lhs_is_present: CraneliftValue,
rhs_is_present: CraneliftValue,
builder: &mut FunctionBuilder<'_>,
) -> CraneliftValue {
let lhs_absent = builder.ins().bxor_imm_u(lhs_is_present, 1);
let rhs_absent = builder.ins().bxor_imm_u(rhs_is_present, 1);
builder.ins().band(lhs_absent, rhs_absent)
}
fn types_are_comparable(lhs: VarType, rhs: VarType) -> bool {
lhs == rhs || (is_numerical(lhs) && is_numerical(rhs))
}
@@ -313,6 +299,13 @@ mod tests {
unsafe { output.as_bool() }.unwrap()
}
fn eval_nullable(expression: &str) -> Option<bool> {
let expression = ast::deserialize(expression).unwrap();
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
// SAFETY: The expression has no inputs and returns a nullable boolean.
unsafe { compiled.call(&[]).as_bool() }
}
#[test]
fn test_infer_types_accepts_any_operand_types() {
let expression = ast::deserialize(r#"(EQ value "hello")"#).unwrap();
@@ -429,13 +422,13 @@ mod tests {
let none_input = [VariableValue::none(), VariableValue::none()];
let output = unsafe { compiled.call(&none_input) };
assert_eq!(unsafe { output.as_bool() }, Some(true));
assert_eq!(unsafe { output.as_bool() }, None);
}
#[test]
fn test_compile_none_equality() {
assert!(eval("(EQ none none)"));
assert!(!eval("(EQ none false)"));
assert_eq!(eval_nullable("(EQ none none)"), None);
assert_eq!(eval_nullable("(EQ none false)"), None);
}
#[test]
@@ -444,10 +437,10 @@ mod tests {
let variable_types = HashMap::from([("left", VarType::U64), ("right", VarType::U64)]);
let mut compiled = compile(&expression, &variable_types).unwrap();
let output = unsafe { compiled.call(&[VariableValue::none(), VariableValue::none()]) };
assert_eq!(unsafe { output.as_bool() }, Some(true));
assert_eq!(unsafe { output.as_bool() }, None);
let output = unsafe { compiled.call(&[VariableValue::none(), VariableValue::some(0u64)]) };
assert_eq!(unsafe { output.as_bool() }, Some(false));
assert_eq!(unsafe { output.as_bool() }, None);
}
#[test]
@@ -457,7 +450,7 @@ mod tests {
let mut compiled = compile(&expression, &variable_types).unwrap();
let output = unsafe { compiled.call(&[VariableValue::none()]) };
assert_eq!(unsafe { output.as_bool() }, Some(true));
assert_eq!(unsafe { output.as_bool() }, None);
}
#[test]