fix(compiler): == / != on references is identity; only text compares by content

emit_bin_vals lowered every pointer-typed ==/!= to @lp_str_eq, so two
distinct records compared their bytes up to the first zero byte. Content
compare now needs both sides to be text (string, or the untyped
pointer/ptr runtime code carries text in); other references use
icmp eq ptr, and string vs a non-text reference is a compile error.
Adds selfhost/tests/identity.ludic; both seeds regenerated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-16 16:06:31 +03:00
parent e010c2cecc
commit 1900f9ca80
7 changed files with 19750 additions and 19403 deletions

View file

@ -161,19 +161,31 @@ function emit_bin(e: Node) -> Val {
return r
}
# text types: `==` on two of these compares content, not the pointers. Besides
# `string`, that is the untyped raw pointer — `pointer`, and `ptr`, the element of
# a `pointers` buffer — which runtime code uses to carry text.
function is_textish(t: pointer) -> bool { return (t == "string") or (t == "pointer") or (t == "ptr") }
# lower `a <op> b` on two already-evaluated operands. Shared by binary expressions
# and compound assignment (`x += y` is exactly `x = x + y`), so both agree on
# string concatenation, Q16.16 multiply/divide, and int->long promotion.
# `isnull` marks a comparison against the literal null (pointer identity, not
# string content).
function emit_bin_vals(op: pointer, a: Val, b: Val, isnull: bool) -> Val {
# strings are pointer-typed, so any `+` with a pointer operand is concatenation,
# and `==`/`!=` between pointers is content comparison — except `x == null`,
# which is a pointer-identity test and falls through to the icmp below.
# strings are pointer-typed, so any `+` with a pointer operand is concatenation.
# `==`/`!=` compares by content only when BOTH sides are text (`string`, or an
# untyped raw pointer, see is_textish); every other reference — records,
# slices, enums, buffers — compares by identity in the icmp below, as does
# `x == null`. A string against a non-text reference is a type error.
let ptrish = (llty(a.ty) == "ptr") or (llty(b.ty) == "ptr")
if ptrish {
if (op == ("+")) { return emit_str_op("+", a, b) }
if ((op == ("==")) or (op == ("!="))) and not isnull { return emit_str_op(op, a, b) }
if ((op == ("==")) or (op == ("!="))) and not isnull {
if is_textish(a.ty) and is_textish(b.ty) { return emit_str_op(op, a, b) }
if ((a.ty == "string") and (llty(b.ty) == "ptr")) or ((b.ty == "string") and (llty(a.ty) == "ptr")) {
perr(`cannot compare {a.ty} with {b.ty} using {op}: strings compare by content, other references by identity`)
}
}
}
let fx = (a.ty == "fixed") or (b.ty == "fixed")
# a 64-bit operand (and no fixed/ptr involved) promotes the whole expression to