diff --git a/docs/php/operators.md b/docs/php/operators.md index 40b83cda1f..1b8a3ed573 100644 --- a/docs/php/operators.md +++ b/docs/php/operators.md @@ -17,6 +17,13 @@ sidebar: | `**` | `$a ** $b` | Exponentiation (right-associative, returns float) | | `-$x` | `-$x` | Unary negation | +Numeric strings are accepted as arithmetic operands and coerced at runtime, +matching PHP semantics. Pure integer-form strings (e.g. `"123"`) coerce to +`int`; float-form strings (e.g. `"1.5"`) coerce to `float`. Leading-numeric +strings (e.g. `"12abc"`) emit a compile-time `Warning: A non-numeric value +encountered` and use the numeric prefix. The result type is `Mixed` when either +operand is a string, since the runtime type depends on the string's content. + ## Comparison | Operator | Example | Notes | diff --git a/examples/arithmetic/main.php b/examples/arithmetic/main.php index b5fc5a5af2..b6953835a2 100644 --- a/examples/arithmetic/main.php +++ b/examples/arithmetic/main.php @@ -10,3 +10,9 @@ echo "b % a = " . ($b % $a) . "\n"; echo "2 + 3 * 4 = " . (2 + 3 * 4) . "\n"; echo "(2 + 3) * 4 = " . ((2 + 3) * 4) . "\n"; + +// Numeric strings are coerced at runtime, matching PHP semantics. +echo '"123" + 3 = ' . ("123" + 3) . "\n"; +echo '"1.5" + 3 = ' . ("1.5" + 3) . "\n"; +echo '"100" - 30 = ' . ("100" - 30) . "\n"; +echo '"10" / 3 = ' . ("10" / 3) . "\n"; diff --git a/src/codegen/runtime/arrays/mixed_numeric_binops.rs b/src/codegen/runtime/arrays/mixed_numeric_binops.rs index 2e9e4f015b..caceef38b2 100644 --- a/src/codegen/runtime/arrays/mixed_numeric_binops.rs +++ b/src/codegen/runtime/arrays/mixed_numeric_binops.rs @@ -56,7 +56,36 @@ pub fn emit_mixed_numeric_binops(emitter: &mut Emitter) { emitter.instruction("cmp x9, #2"); // does the right operand hold a double payload? emitter.instruction("b.eq __rt_mixed_numeric_float_path"); // any double payload makes the whole operation double-valued + // -- string operands may be float-form (e.g. "1.5"); check and route to the float path -- + emitter.instruction("ldr x9, [sp, #24]"); // reload the left runtime value tag + emitter.instruction("cmp x9, #1"); // does the left operand hold a string payload? + emitter.instruction("b.eq __rt_mixed_numeric_check_str_float_left"); // check if the left string is float-form + emitter.instruction("ldr x9, [sp, #32]"); // reload the right runtime value tag + emitter.instruction("cmp x9, #1"); // does the right operand hold a string payload? + emitter.instruction("b.eq __rt_mixed_numeric_check_str_float_right"); // check if the right string is float-form + emitter.instruction("b __rt_mixed_numeric_int_path"); // neither operand is a string; proceed with integer arithmetic + + emitter.label("__rt_mixed_numeric_check_str_float_left"); + emitter.instruction("ldr x0, [sp, #0]"); // load the boxed left operand pointer + emitter.instruction("bl __rt_mixed_unbox"); // unbox: x0=tag, x1=string ptr, x2=string length + emitter.instruction("bl __rt_str_is_float_form"); // check if the left string is float-form (x1/x2 preserved) + emitter.instruction("cmp x0, #0"); // is the left string float-form? + emitter.instruction("b.ne __rt_mixed_numeric_float_path"); // yes: route to the float path for correct float arithmetic + emitter.instruction("ldr x9, [sp, #32]"); // reload the right runtime value tag + emitter.instruction("cmp x9, #1"); // does the right operand hold a string payload? + emitter.instruction("b.eq __rt_mixed_numeric_check_str_float_right"); // check if the right string is float-form + emitter.instruction("b __rt_mixed_numeric_int_path"); // right is not a string; proceed with integer arithmetic + + emitter.label("__rt_mixed_numeric_check_str_float_right"); + emitter.instruction("ldr x0, [sp, #8]"); // load the boxed right operand pointer + emitter.instruction("bl __rt_mixed_unbox"); // unbox: x0=tag, x1=string ptr, x2=string length + emitter.instruction("bl __rt_str_is_float_form"); // check if the right string is float-form (x1/x2 preserved) + emitter.instruction("cmp x0, #0"); // is the right string float-form? + emitter.instruction("b.ne __rt_mixed_numeric_float_path"); // yes: route to the float path for correct float arithmetic + emitter.instruction("b __rt_mixed_numeric_int_path"); // right string is int-form; proceed with integer arithmetic + // -- integer path with PHP overflow promotion -- + emitter.label("__rt_mixed_numeric_int_path"); emitter.instruction("ldr x0, [sp, #0]"); // reload the boxed left operand before casting to integer emitter.instruction("bl __rt_mixed_cast_int"); // coerce the left operand using the current integer numeric rules emitter.instruction("str x0, [sp, #40]"); // save the left integer payload across the right cast @@ -190,7 +219,35 @@ fn emit_mixed_numeric_binops_linux_x86_64(emitter: &mut Emitter) { emitter.instruction("cmp QWORD PTR [rbp - 40], 2"); // does the right operand hold a double payload? emitter.instruction("je __rt_mixed_numeric_float_path_linux_x86_64"); // any double payload makes the whole operation double-valued + // -- string operands may be float-form (e.g. "1.5"); check and route to the float path -- + emitter.instruction("cmp QWORD PTR [rbp - 32], 1"); // does the left operand hold a string payload? + emitter.instruction("je __rt_mixed_numeric_check_str_float_left_x86_64"); // check if the left string is float-form + emitter.instruction("cmp QWORD PTR [rbp - 40], 1"); // does the right operand hold a string payload? + emitter.instruction("je __rt_mixed_numeric_check_str_float_right_x86_64"); // check if the right string is float-form + emitter.instruction("jmp __rt_mixed_numeric_int_path_linux_x86_64"); // neither operand is a string; proceed with integer arithmetic + + emitter.label("__rt_mixed_numeric_check_str_float_left_x86_64"); + emitter.instruction("mov rax, QWORD PTR [rbp - 8]"); // load the boxed left operand pointer + emitter.instruction("call __rt_mixed_unbox"); // unbox: rax=tag, rdi=string ptr, rdx=string length + emitter.instruction("mov rax, rdi"); // move string pointer to rax for __rt_str_is_float_form + emitter.instruction("call __rt_str_is_float_form"); // check if the left string is float-form + emitter.instruction("test rax, rax"); // is the left string float-form? + emitter.instruction("jne __rt_mixed_numeric_float_path_linux_x86_64"); // yes: route to the float path for correct float arithmetic + emitter.instruction("cmp QWORD PTR [rbp - 40], 1"); // does the right operand hold a string payload? + emitter.instruction("je __rt_mixed_numeric_check_str_float_right_x86_64"); // check if the right string is float-form + emitter.instruction("jmp __rt_mixed_numeric_int_path_linux_x86_64"); // right is not a string; proceed with integer arithmetic + + emitter.label("__rt_mixed_numeric_check_str_float_right_x86_64"); + emitter.instruction("mov rax, QWORD PTR [rbp - 16]"); // load the boxed right operand pointer + emitter.instruction("call __rt_mixed_unbox"); // unbox: rax=tag, rdi=string ptr, rdx=string length + emitter.instruction("mov rax, rdi"); // move string pointer to rax for __rt_str_is_float_form + emitter.instruction("call __rt_str_is_float_form"); // check if the right string is float-form + emitter.instruction("test rax, rax"); // is the right string float-form? + emitter.instruction("jne __rt_mixed_numeric_float_path_linux_x86_64"); // yes: route to the float path for correct float arithmetic + emitter.instruction("jmp __rt_mixed_numeric_int_path_linux_x86_64"); // right string is int-form; proceed with integer arithmetic + // -- integer path with PHP overflow promotion -- + emitter.label("__rt_mixed_numeric_int_path_linux_x86_64"); emitter.instruction("mov rax, QWORD PTR [rbp - 8]"); // reload the boxed left operand before casting to integer emitter.instruction("call __rt_mixed_cast_int"); // coerce the left operand using the current integer numeric rules emitter.instruction("mov QWORD PTR [rbp - 48], rax"); // save the left integer payload across the right cast diff --git a/src/codegen/runtime/emitters.rs b/src/codegen/runtime/emitters.rs index 246bee71b2..0ac963963f 100644 --- a/src/codegen/runtime/emitters.rs +++ b/src/codegen/runtime/emitters.rs @@ -44,6 +44,7 @@ pub(crate) fn emit_runtime(emitter: &mut Emitter, features: RuntimeFeatures) { strings::emit_str_eq(emitter); strings::emit_str_to_number(emitter); strings::emit_str_to_int(emitter); + strings::emit_str_is_float_form(emitter); strings::emit_str_loose_eq(emitter); strings::emit_number_format(emitter); strings::emit_strcopy(emitter); diff --git a/src/codegen/runtime/strings/mod.rs b/src/codegen/runtime/strings/mod.rs index 731599e127..bcf9aec641 100644 --- a/src/codegen/runtime/strings/mod.rs +++ b/src/codegen/runtime/strings/mod.rs @@ -15,6 +15,7 @@ mod str_eq; mod str_loose_eq; mod str_to_number; mod str_to_int; +mod str_is_float_form; mod number_format; mod atoi; mod grapheme_strrev; @@ -93,6 +94,8 @@ pub use str_to_number::emit_str_to_number; /// Emit string-to-number conversion helper. pub use str_to_int::emit_str_to_int; /// Emit PHP string-to-integer cast helper. +pub use str_is_float_form::emit_str_is_float_form; +/// Emit string float-form detection helper. pub use number_format::emit_number_format; /// Emit number formatting helper. pub use atoi::emit_atoi; diff --git a/src/codegen/runtime/strings/str_is_float_form.rs b/src/codegen/runtime/strings/str_is_float_form.rs new file mode 100644 index 0000000000..264d392d50 --- /dev/null +++ b/src/codegen/runtime/strings/str_is_float_form.rs @@ -0,0 +1,112 @@ +//! Purpose: +//! Emits the `__rt_str_is_float_form` runtime helper that reports whether a PHP +//! string is float-form (contains a `.` or exponent that `strtod` consumes beyond +//! what `strtoll` parses). Used by the mixed numeric dispatch to route string +//! operands to the correct arithmetic path. +//! +//! Called from: +//! - `crate::codegen::runtime::arrays::mixed_numeric_binops` during operand classification. +//! +//! Key details: +//! - Returns 1 in the integer result register when the string is float-form, 0 otherwise. +//! - The input string follows the active string-result convention (AArch64 x1/x2, x86_64 rax/rdx). + +use crate::codegen::emit::Emitter; +use crate::codegen::platform::Arch; + +/// Emits `__rt_str_is_float_form` for both supported targets. +/// +/// Input follows the active string-result convention: +/// AArch64 uses `x1`/`x2`; x86_64 uses `rax`/`rdx`. +/// Output: integer result register holds 1 (float-form) or 0 (int-form). +pub fn emit_str_is_float_form(emitter: &mut Emitter) { + if emitter.target.arch == Arch::X86_64 { + emit_str_is_float_form_linux_x86_64(emitter); + return; + } + + emitter.blank(); + emitter.comment("--- runtime: str_is_float_form ---"); + emitter.label_global("__rt_str_is_float_form"); + + // -- set up the helper frame (slots: end_i=[sp,#0], end_d=[sp,#8], cstr=[sp,#16]) -- + emitter.instruction("sub sp, sp, #32"); // allocate slots for both end pointers and the C-string pointer + emitter.instruction("stp x29, x30, [sp, #16]"); // save frame pointer and return address across the libc calls + emitter.instruction("add x29, sp, #16"); // establish a stable helper frame pointer + + // -- copy the PHP string into the C-string scratch buffer -- + emitter.instruction("bl __rt_cstr"); // copy the bounded PHP string into the C-string scratch buffer + emitter.instruction("str x0, [sp, #16]"); // save the C-string pointer for the second parse + + // -- integer parse: strtoll(cstr, &end_i, 10) reports where the integer prefix ends -- + emitter.instruction("add x1, sp, #0"); // pass &end_i so strtoll reports where the integer prefix ended + emitter.instruction("mov x2, #10"); // parse in base 10 like PHP string-to-int + emitter.bl_c("strtoll"); + + // -- float parse: strtod(cstr, &end_d) reports where the numeric value ended -- + emitter.instruction("ldr x0, [sp, #16]"); // reload the C-string pointer for strtod + emitter.instruction("add x1, sp, #8"); // pass &end_d so strtod reports where the numeric value ended + emitter.bl_c("strtod"); + + // -- if strtod consumed more bytes than strtoll, the string is float-form -- + emitter.instruction("ldr x9, [sp, #8]"); // load the end pointer returned by strtod + emitter.instruction("ldr x10, [sp, #0]"); // load the end pointer returned by strtoll + emitter.instruction("cmp x9, x10"); // did strtod consume more bytes than strtoll? + emitter.instruction("b.hi __rt_str_is_float_form_true"); // yes: the string is float-form + emitter.instruction("mov x0, #0"); // no: the string is int-form + emitter.instruction("b __rt_str_is_float_form_done"); // skip the true path + + emitter.label("__rt_str_is_float_form_true"); + emitter.instruction("mov x0, #1"); // report that the string is float-form + + emitter.label("__rt_str_is_float_form_done"); + emitter.instruction("ldp x29, x30, [sp, #16]"); // restore caller frame pointer and return address + emitter.instruction("add sp, sp, #32"); // release the helper frame + emitter.instruction("ret"); // return the float-form flag in x0 +} + +/// Emits the Linux x86_64 `__rt_str_is_float_form` runtime helper. +/// +/// The input string arrives in the elephc string-result registers (`rax`/`rdx`). +/// Parses with `strtoll` and `strtod`, returning 1 in `rax` if `strtod` consumed +/// more bytes (float-form), 0 otherwise. +fn emit_str_is_float_form_linux_x86_64(emitter: &mut Emitter) { + emitter.blank(); + emitter.comment("--- runtime: str_is_float_form ---"); + emitter.label_global("__rt_str_is_float_form"); + + // -- set up the helper frame (locals: cstr=[rbp-8], end_i=[rbp-16], end_d=[rbp-24]) -- + emitter.instruction("push rbp"); // preserve the caller frame pointer before calling libc parsers + emitter.instruction("mov rbp, rsp"); // establish a stable helper frame pointer + emitter.instruction("sub rsp, 32"); // allocate aligned slots for the C-string pointer and end pointers + + // -- copy the PHP string into the C-string scratch buffer -- + emitter.instruction("call __rt_cstr"); // copy the bounded PHP string into the C-string scratch buffer + emitter.instruction("mov QWORD PTR [rbp - 8], rax"); // save the C-string pointer for the second parse + + // -- integer parse: strtoll(cstr, &end_i, 10) reports where the integer prefix ends -- + emitter.instruction("mov rdi, rax"); // strtoll arg1: the C-string pointer + emitter.instruction("lea rsi, [rbp - 16]"); // strtoll arg2: &end_i + emitter.instruction("mov edx, 10"); // strtoll arg3: parse in base 10 + emitter.instruction("call strtoll"); // parse the integer prefix + + // -- float parse: strtod(cstr, &end_d) reports where the numeric value ended -- + emitter.instruction("mov rdi, QWORD PTR [rbp - 8]"); // reload the C-string pointer for strtod + emitter.instruction("lea rsi, [rbp - 24]"); // strtod arg2: &end_d + emitter.instruction("call strtod"); // parse the full numeric value + + // -- if strtod consumed more bytes than strtoll, the string is float-form -- + emitter.instruction("mov r8, QWORD PTR [rbp - 24]"); // load the end pointer returned by strtod + emitter.instruction("cmp r8, QWORD PTR [rbp - 16]"); // did strtod consume more bytes than strtoll? + emitter.instruction("ja __rt_str_is_float_form_true_linux_x86_64"); // yes: the string is float-form + emitter.instruction("xor rax, rax"); // no: the string is int-form + emitter.instruction("jmp __rt_str_is_float_form_done_linux_x86_64"); // skip the true path + + emitter.label("__rt_str_is_float_form_true_linux_x86_64"); + emitter.instruction("mov rax, 1"); // report that the string is float-form + + emitter.label("__rt_str_is_float_form_done_linux_x86_64"); + emitter.instruction("add rsp, 32"); // release the helper frame + emitter.instruction("pop rbp"); // restore the caller frame pointer + emitter.instruction("ret"); // return the float-form flag in rax +} \ No newline at end of file diff --git a/src/ir_lower/expr/mod.rs b/src/ir_lower/expr/mod.rs index 7d7b9fe08b..37441d3981 100644 --- a/src/ir_lower/expr/mod.rs +++ b/src/ir_lower/expr/mod.rs @@ -363,6 +363,18 @@ fn lower_numeric_binary( return lower_mixed_numeric_binary(ctx, lhs, rhs, mixed_op, expr); } } + if matches!(op, BinOp::Div) && (lhs.ir_type == IrType::Str || rhs.ir_type == IrType::Str) { + let lhs = coerce_to_float(ctx, lhs, expr); + let rhs = coerce_to_float(ctx, rhs, expr); + return ctx.emit_value( + Op::FDiv, + vec![lhs.value, rhs.value], + None, + PhpType::Float, + Op::FDiv.default_effects(), + Some(expr.span), + ); + } if lhs.ir_type == IrType::F64 || rhs.ir_type == IrType::F64 { let lhs = coerce_to_float(ctx, lhs, expr); let rhs = coerce_to_float(ctx, rhs, expr); @@ -9314,6 +9326,7 @@ fn coerce_to_float_at_span( match value.ir_type { IrType::F64 => value, IrType::I64 => ctx.emit_value(Op::IToF, vec![value.value], None, PhpType::Float, Op::IToF.default_effects(), span), + IrType::Str => ctx.emit_value(Op::StrToF, vec![value.value], None, PhpType::Float, Op::StrToF.default_effects(), span), _ => ctx.emit_value( Op::Cast, vec![value.value], diff --git a/src/types/checker/inference/ops.rs b/src/types/checker/inference/ops.rs index 503e1fedda..4185950b60 100644 --- a/src/types/checker/inference/ops.rs +++ b/src/types/checker/inference/ops.rs @@ -45,7 +45,13 @@ impl Checker { "Exponentiation requires numeric operands", )); } - Ok(PhpType::Float) + if lt == PhpType::Str || rt == PhpType::Str { + self.warn_numeric_string_operand(left, <); + self.warn_numeric_string_operand(right, &rt); + Ok(PhpType::Mixed) + } else { + Ok(PhpType::Float) + } } BinOp::Add => { if is_array_like_type(<) || is_array_like_type(&rt) { @@ -59,7 +65,11 @@ impl Checker { "Arithmetic operators require numeric operands", )); } - if uses_mixed_numeric_dispatch(<) || uses_mixed_numeric_dispatch(&rt) { + if uses_mixed_numeric_dispatch(<) || uses_mixed_numeric_dispatch(&rt) + || lt == PhpType::Str || rt == PhpType::Str + { + self.warn_numeric_string_operand(left, <); + self.warn_numeric_string_operand(right, &rt); Ok(PhpType::Mixed) } else if lt == PhpType::Float || rt == PhpType::Float { Ok(PhpType::Float) @@ -76,11 +86,20 @@ impl Checker { "Arithmetic operators require numeric operands", )); } + if lt == PhpType::Str || rt == PhpType::Str { + self.warn_numeric_string_operand(left, <); + self.warn_numeric_string_operand(right, &rt); + } // Division always returns float (PHP compat: 10/3 → 3.333...) if *op == BinOp::Div || lt == PhpType::Float || rt == PhpType::Float { - Ok(PhpType::Float) + if lt == PhpType::Str || rt == PhpType::Str { + Ok(PhpType::Mixed) + } else { + Ok(PhpType::Float) + } } else if matches!(op, BinOp::Sub | BinOp::Mul) - && (uses_mixed_numeric_dispatch(<) || uses_mixed_numeric_dispatch(&rt)) + && (uses_mixed_numeric_dispatch(<) || uses_mixed_numeric_dispatch(&rt) + || lt == PhpType::Str || rt == PhpType::Str) { Ok(PhpType::Mixed) } else { @@ -152,6 +171,31 @@ impl Checker { } } + /// Emits a compile-time warning when a constant string operand used in arithmetic + /// is not purely numeric (e.g. `"12abc"`, `"abc"`). PHP 8 emits + /// `Warning: A non-numeric value encountered` for leading-numeric strings and + /// `TypeError: Unsupported operand types: string + int` for fully non-numeric + /// strings at runtime; elephc warns at compile time for the constant case. + fn warn_numeric_string_operand(&mut self, expr: &Expr, ty: &PhpType) { + if *ty != PhpType::Str { + return; + } + let value = match &expr.kind { + ExprKind::StringLiteral(s) => s, + _ => return, + }; + if is_php_numeric_string(value) { + return; + } + let warning = crate::errors::CompileWarning::new( + expr.span, + "A non-numeric value encountered in arithmetic", + ); + if !self.warnings.iter().any(|w| w.span.line == warning.span.line && w.span.col == warning.span.col && w.message == warning.message) { + self.warnings.push(warning); + } + } + /// Merges two array-like types for the `+` operator (array union). /// /// Handles `PhpType::Array` vs `PhpType::Array`, `PhpType::AssocArray` vs @@ -1184,12 +1228,14 @@ fn is_array_like_type(ty: &PhpType) -> bool { /// Returns `true` if `ty` is a valid operand type for numeric binary operators /// (addition, subtraction, multiplication, division, modulo, comparison, spaceship). -/// Numeric operands include `Int`, `Float`, `Bool`, `Void`, `Mixed`, or a union -/// with mixed integer dispatch behavior. +/// Numeric operands include `Int`, `Float`, `Bool`, `Void`, `Mixed`, `Str`, or a union +/// with mixed integer dispatch behavior. `Str` is accepted because PHP coerces numeric +/// strings at runtime; the result type becomes `Mixed` to capture int-or-float outcomes. fn is_numeric_operand_type(checker: &Checker, ty: &PhpType) -> bool { matches!( ty, PhpType::Int | PhpType::Float | PhpType::Bool | PhpType::Void | PhpType::Mixed + | PhpType::Str ) || checker.is_union_with_mixed_int_dispatch(ty) } @@ -1223,3 +1269,48 @@ fn uses_mixed_numeric_dispatch(ty: &PhpType) -> bool { fn is_empty_indexed_array_literal(expr: &Expr) -> bool { matches!(&expr.kind, ExprKind::ArrayLiteral(elems) if elems.is_empty()) } + +/// Returns `true` if `s` is a purely numeric string per PHP's `is_numeric()`. +/// PHP accepts leading/trailing whitespace and optional sign, followed by an +/// integer, float, or scientific-notation literal. Strings like `"12abc"` or +/// `"abc"` return `false` (leading-numeric or non-numeric). +fn is_php_numeric_string(s: &str) -> bool { + let trimmed = s.trim(); + if trimmed.is_empty() { + return false; + } + let bytes = trimmed.as_bytes(); + let mut i = 0; + if bytes[i] == b'+' || bytes[i] == b'-' { + i += 1; + } + if i >= bytes.len() { + return false; + } + let mut has_digit = false; + while i < bytes.len() && bytes[i].is_ascii_digit() { + has_digit = true; + i += 1; + } + if i < bytes.len() && bytes[i] == b'.' { + i += 1; + while i < bytes.len() && bytes[i].is_ascii_digit() { + has_digit = true; + i += 1; + } + } + if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') { + i += 1; + if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') { + i += 1; + } + let exp_start = i; + while i < bytes.len() && bytes[i].is_ascii_digit() { + i += 1; + } + if i == exp_start { + return false; + } + } + has_digit && i == bytes.len() +} diff --git a/tests/codegen/operators.rs b/tests/codegen/operators.rs index 056237729d..6d8a04d5c3 100644 --- a/tests/codegen/operators.rs +++ b/tests/codegen/operators.rs @@ -469,3 +469,72 @@ echo ($i == $m ? "y" : "n"), ($m == $i ? "y" : "n"), ($i == $h["n"] ? "y" : "n") ); assert_eq!(out, "yyyn"); } + +// --- Issue #362: numeric string arithmetic --- + +/// Verifies that a pure integer-form string added to an int coerces to int and yields the PHP result: +/// `"123" + 3` produces `int(126)`. +#[test] +fn test_numeric_string_plus_int() { + let out = compile_and_run(r#"` with string operands rejects them -/// with the "Spaceship operator requires numeric operands" error. -#[test] -fn test_error_spaceship_string() { - expect_error( - r#" "b";"#, - "Spaceship operator requires numeric operands", - ); -} - /// Tests that using `$this` inside a `static` method produces the expected /// "Cannot use $this inside a static method" error. #[test] diff --git a/tests/error_tests/type_system.rs b/tests/error_tests/type_system.rs index a9f287cf42..6be4360b43 100644 --- a/tests/error_tests/type_system.rs +++ b/tests/error_tests/type_system.rs @@ -129,16 +129,6 @@ fn test_error_type_mismatch_reassign() { expect_error("