Repository navigation
An any compares with undefined, null and strings by what it holds - #417
Merged
Merged
Conversation
Four ways comparing an `any` gave the wrong answer: - With undefined, UndefLogicHelper answered at compile time: `==` false, `!=` true, whatever the `any` held. It now hands an `any` to the run-time comparison (AnyCompareOp), as it already did for a class or an interface. - LogicOp boxed the other operand only when its type differed from the `any`'s - but the operands are lowered already, and an `any`, a string and null are all a `ptr`. The string (or null) itself went in as the box, and its "tag" was read from whatever it pointed at (address 8 for null): `let t: any = "text"; t == "text"` was false. It is boxed now by its TypeScript type, on the stack - the box lives for the comparison only, and a heap one was never freed under reference counting (96 MB for 2M comparisons at -O0). - AnyCompareOp answers undefined and null before opening any box: a null pointer is undefined (a zeroed `any` slot is one), otherwise the tag's kind says. Loosely undefined and null equal each other and nothing else; strictly each equals only itself; an ordering stays false. - Two string boxes compared the bytes of their payload, a pointer: equal text built at run time was unequal. They compare with strcmp now. And `if (a)` / `!a` of an `any` holding undefined or null threw "Can't cast from any type" from the generated ___unbox<boolean>; it returns false for both, as JavaScript does. Test 00any_equality_undefined.ts. Without the change it fails in every model, compile and JIT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Comparing an
anygave wrong answers in four ways, andif (a)on ananycould throw. These are the twoanybugs noted in #416, both of which turned out to be broader.const u: any = undefined; u == undefinedconst n: any = null; n == nulllet t: any = "text"; t == "text"anys holding equal text built at run timeif (u)/!uwithu: any = undefinedCauses and fixes
Compared with
undefined, the answer was fixed at compile time.UndefLogicHelperreturned==false and!=true whatever theanyheld. It now hands ananyto the run-time comparison (AnyCompareOp), as it already did for a class or an interface.The other operand wasn't boxed.
LogicOpboxed the other operand only when its type differed from theany's. By then the operands are already lowered, and anany, a string andnullare all justptr, so no box was made:null, the tag was read from address 8.The operand is now boxed by its TypeScript type, on the stack. The box only lives for the comparison. A heap box would never have been freed under rc: 96 MB for 2M comparisons at
-O0. With the stack box it's 3.9 MB.undefinedandnullare answered before any box is opened. A nullanypointer counts asundefined, which is also what a zeroedanyslot is. Otherwise the tag's kind decides:==,!=),undefinedandnullequal each other and nothing else;===,!==), each equals only itself;Two string boxes compared their pointer bytes. Equal text built at run time came out unequal. They're compared with
strcmpnow.Truthiness:
___unbox<boolean>threw for ananyholdingundefinedornull, and it's whatif (a)and!ause. It now returnsfalsefor both, as JavaScript does.Tests
00any_equality_undefined.tscovers:undefinedandnullunder each operator;letthat is reassigned;undefinedagainstnull;Without the change it fails in every model, compiled and under JIT.
Results
sleep/weakref_basiclink errorsNot in this PR
anyValue == 7fails with "Symbols not found:??_7type_info@@6B@". The comparison unboxes through___unbox<si32>, whose fallbackthrowneeds MSVC'stype_infovtable, and the rc JIT doesn't resolve it. This was already the case before this PR.🤖 Generated with Claude Code