From d9adce038cf4dddf0d2bd6687563e3abfda29d49 Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 19 Oct 2026 17:26:39 +0500 Subject: [PATCH] AutoLISP: `=` compares the symbols `type` returns `(= (type x) 'LIST)` is how an AutoLISP routine tests a type, because `type` returns a symbol. `=` coerced both of its arguments to numbers, so the symbol failed the coercion and the routine stopped at its first type test: _$ (= (type 1) 'INT) bad argument type: numberp: INT `=` and `/=` now compare numbers and strings as before, and any other atom -- a symbol, `nil`, an entity name, a selection set, a file handle -- by what it is, the way `eq` does. Two consequences, both closer to AutoLISP than raising was: a mismatch of kinds answers `nil` rather than failing, so `(= 1 "1")` and `(= '(1) '(1))` answer (comparing list structure is `equal`'s job), and `(= nil nil)` is `T`. The ordering comparisons still take numbers and strings only, as AutoLISP has them: `(< 'a 'b)` remains `bad argument type: numberp: A`. Fix: #1597 --- crates/engine/src/lisp/builtins.rs | 25 ++++++++++++++++++++-- crates/engine/src/lisp/tests.rs | 33 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/lisp/builtins.rs b/crates/engine/src/lisp/builtins.rs index f5535147..73aea363 100644 --- a/crates/engine/src/lisp/builtins.rs +++ b/crates/engine/src/lisp/builtins.rs @@ -307,11 +307,32 @@ fn chain(a: &[Value], ok: fn(std::cmp::Ordering) -> bool) -> R { } Ok(Value::t()) } +/// `=` and `/=`: numbers and strings compare as [`compare`] does, and any other atom compares by +/// what it is, as `eq` does. AutoLISP answers `(= (type x) 'LIST)` -- the idiom for a type test, +/// since `type` returns a symbol -- where coercing that symbol to a number failed the whole +/// routine with "bad argument type" instead (#1597). A mismatch is `nil`, not an error, so +/// `(= 1 "1")` and `(= '(1) '(1))` answer too. The ordering comparisons stay numbers and strings +/// only, as AutoLISP has them. +fn eq_chain(a: &[Value], want_equal: bool) -> R { + arity(a, 1, usize::MAX)?; + for w in a.windows(2) { + let [x, y] = w else { continue }; + let same = match (x, y) { + (Value::Str(_), Value::Str(_)) => compare(x, y)?.is_eq(), + _ if x.as_number().is_some() && y.as_number().is_some() => compare(x, y)?.is_eq(), + _ => eq(x, y), + }; + if same != want_equal { + return Ok(Value::Nil); + } + } + Ok(Value::t()) +} fn num_eq(a: &[Value]) -> R { - chain(a, |o| o.is_eq()) + eq_chain(a, true) } fn num_ne(a: &[Value]) -> R { - chain(a, |o| o.is_ne()) + eq_chain(a, false) } fn lt(a: &[Value]) -> R { chain(a, |o| o.is_lt()) diff --git a/crates/engine/src/lisp/tests.rs b/crates/engine/src/lisp/tests.rs index c02b48b1..e8eb135b 100644 --- a/crates/engine/src/lisp/tests.rs +++ b/crates/engine/src/lisp/tests.rs @@ -70,6 +70,39 @@ fn arithmetic_follows_autolisp() { assert_eq!(eval(&mut s, "(+ 1 nil)"), Err("bad argument type: numberp: nil".into())); } +/// `=` answers for the symbols `type` returns, which is how AutoLISP routines test a type: +/// `(= (type x) 'INT)`. Coercing the symbol to a number failed the whole routine with "bad +/// argument type" instead, so a routine written for AutoLISP stopped at its first type test +/// (#1597). The ordering comparisons are still numbers and strings, as AutoLISP has them. +#[test] +fn equality_compares_symbols_as_autolisp_does() { + let mut s = session(); + assert_eq!(ok(&mut s, "(= (type 1) 'INT)"), "T"); + assert_eq!(ok(&mut s, "(= (type 1.0) 'INT)"), "nil"); + assert_eq!(ok(&mut s, "(= (type '(1 2)) 'LIST)"), "T"); + assert_eq!(ok(&mut s, "(= (type \"a\") 'STR)"), "T"); + assert_eq!(ok(&mut s, "(/= (type 1) 'REAL)"), "T"); + assert_eq!(ok(&mut s, "(= 'a 'a 'a)"), "T"); + assert_eq!(ok(&mut s, "(= 'a 'b)"), "nil"); + + // The routine from the report runs. + ok(&mut s, "(defun isint (a) (if (= (type a) 'INT) T nil))"); + assert_eq!(ok(&mut s, "(isint 3)"), "T"); + assert_eq!(ok(&mut s, "(isint 3.0)"), "nil"); + assert_eq!(ok(&mut s, "(isint \"3\")"), "nil"); + + // A mismatch of kinds is nil, not an error; numbers and strings are unchanged. + assert_eq!(ok(&mut s, "(= 1 \"1\")"), "nil"); + assert_eq!(ok(&mut s, "(= '(1) '(1))"), "nil", "`=` is not `equal`"); + assert_eq!(ok(&mut s, "(= nil nil)"), "T"); + assert_eq!(ok(&mut s, "(= 1 1.0)"), "T"); + assert_eq!(ok(&mut s, "(= \"a\" \"a\" \"a\")"), "T"); + assert_eq!(ok(&mut s, "(= 1 1 2)"), "nil"); + + // Ordering still refuses what AutoLISP refuses. + assert_eq!(eval(&mut s, "(< 'a 'b)"), Err("bad argument type: numberp: A".into())); +} + #[test] fn lists_and_strings() { let mut s = session();