Skip to content

?? takes the right side only for null or undefined, and a number never equals null - #431

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix-spurious-optional-warnings
Oct 1, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
fix-spurious-optional-warnings

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Problem

  • n ?? -1 for n?: number:
    • it tested the value's truthiness, so f(0) returned -1;
    • it compared the value with null through a cast to a pointer, which warned invalid cast from 'f64' to '!llvm.ptr';
    • its type stayed number | undefined.
  • s ?? "x" for s?: string, and y ?? 8 for number | null, did not compile.
  • 0 === null was true, because null was read as 0. The same happened for an optional number holding 0.
  • x === null for x: 0 | 1 | 42 failed in LLVM lowering ('llvm.icmp' op requires all operands to have the same type).
  • Every generator yielding a number or another value type warned using casting to undefined value. The warning came from the generator's finished {value: undefined, done: true}.

Fix

  • ?? now tests whether the left side is null or undefined according to its type (mlirGenIsNullish):
    • an optional is nullish when it has no value, and its value is tested only when there is one;
    • a value type is never nullish;
    • a union, any or a pointer type is compared with null and with undefined.
  • The type of a ?? b is a's type without null and undefined, combined with b's. When a can only be nullish, it is b's type.
  • Compares with null / undefined whose answer the type already decides are folded (foldNullishCompare):
    • a value type is never equal to either;
    • an optional value type is never === null;
    • an optional value type is == null exactly when it has no value.
  • Refactor: the value-level part of mlirGen(BinaryExpression) is now mlirGenBinaryValues, so ?? uses the same compare. Behaviour is unchanged.
  • The generator's finished value, for a value type, takes its zero directly (InternalFlags::GeneratorDoneValue). That is what the cast already lowered to. Other types keep the cast, so a string still gets the text "undefined".

Tests

  • New 00nullish_values.ts, run as compile and jit. It passes under gc, rc and none.
  • Release suite: 3347/3347.
  • DefaultLib built with this compiler: 159/159 compiled and 159/159 under JIT.
  • Not run: Debug builds, x86, -mm=own.

🤖 Generated with Claude Code

…ver equals null

`n ?? -1` for `n?: number` tested the value's truthiness, so `f(0)` gave -1, and
compared every left value with null through a cast to a pointer ("invalid cast from
'f64' to '!llvm.ptr'"); `s ?? "x"` for `s?: string` and `y ?? 8` for `number | null`
did not compile. The test is now chosen by the left side's type (mlirGenIsNullish):
an optional is nullish when it has no value (its value is tested only when there is
one), a value type never, a union / any / reference is compared with null and with
undefined. The result type is the left side's without null and undefined, or the
right side's when the left can only be nullish.

`n === null` / `n == null` for a number compared it with null read as 0, so `0 === null`
was true, also for an optional number holding 0, and `x === null` for `0 | 1 | 42` failed
in LLVM lowering. foldNullishCompare gives the answer the type implies; `mlirGen(BinaryExpression)`'s
value part is now mlirGenBinaryValues, so `??` uses the same compare.

A finished generator's `{value: undefined, done: true}` of a value type built its zero
through a cast that warned "using casting to undefined value" in every such generator;
the field now takes the zero directly (InternalFlags::GeneratorDoneValue).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 08036ab into main Oct 1, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-spurious-optional-warnings branch October 1, 2026 22:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant