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
This commit is contained in:
Hamza
2026-10-19 17:26:39 +05:00
parent 0b32c2e6a1
commit d9adce038c
2 changed files with 56 additions and 2 deletions
+23 -2
View File
@@ -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())
+33
View File
@@ -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();