Repository navigation
A generic class takes its type arguments from its constructor's arguments; undefined in a typed slot is zero - #416
Merged
Conversation
A finished generator's `{ value: undefined, done: true }` puts undefined in
a `value` of the generator's element type. When that type cannot hold it
(si32, a number, a boolean), the field was LLVM undef, both in the constant
tuple (LLVMCodeHelper::getTupleFromArrayAttr) and in ts.UndefOp's lowering,
and reading it back gave whatever the register held: the last yielded
value, or garbage - a Set<si32>'s `values().drop(4).next()` printed
`{ value: -971415704, done: true }`. Owning types were already zero under
reference counting, for the same reason and a worse one (a retain walks an
undef pointer).
Both now give zero for every type in every model: deterministic, and the
same answer as a grown array's new slots.
Test 00generator_done_value_zero.ts (a finished si32 generator's value,
twice). Without the fix it fails in every model.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ents `new Set([2, 4, 6, 8])` was Set<any>: a generic class written without type arguments never looked at its constructor's arguments. It took its defaults (`Set<V = any>`), or, with none, was "Generic class 'Box' is missing type arguments" - even for `new Box(5)`. DefaultLib's own Set and Map tests therefore only ever ran the `any` path. inferClassTypeFromConstructorArguments does for `new` what a generic function's call already does: it evaluates each argument's type, reads the constructor's parameter types with the class's type parameters standing for themselves, runs them through inferType, widens the result and instantiates the class. A type parameter the arguments do not reach keeps its default. It stays out of the way when there is nothing sure to say: several constructors, a type parameter with neither an inferred type nor a default, or an inferred type still generic. A function argument's type is not used (its parameters are what is being worked out), and a declared type still wins: `const s: Set<number> = new Set([1, 2])` is Set<number>. Literals follow function inference: `new Set([2, 4, 6, 8])` is Set<si32>, as `first([2, 4])` is first<si32>. It needed a fix in inferType on the way: an unnamed tuple's fields (`[K, V]`) were matched by their field id, which is null for every one of them, so each found the first field and K and V both took the first element's type. `firstValue([["one", 1]])` inferred <string, string> and did not compile. They are matched by position now. Test 00generic_class_constructor_inference.ts: no default, a default, no arguments, a declared type, a [K, V][] parameter, a parameter left to its default, a callback argument, `new` inside a generic function, and the function-call tuple case. Without the change it does not compile. 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
new Set([2, 4, 6, 8])wasSet<any>. A generic class written without type arguments never looked at its constructor's arguments:Set<V = any>), it took the defaults;new Box(5).As a result, DefaultLib's Set and Map tests only ever ran the
anypath. This PR has two commits.1. A generic class takes its type arguments from its constructor's arguments
inferClassTypeFromConstructorArgumentsdoes fornewwhat a generic function call already does:inferType;Details:
const s: Set<number> = new Set([1, 2])isSet<number>.new Set([2, 4, 6, 8])isSet<si32>, just asfirst([2, 4])isfirst<si32>. This is the existing integer-literal rule, applied the same way to classes.This needed one fix in
inferType. An unnamed tuple's fields ([K, V]) were matched by field id, and that id is null for all of them, so every field found the first one:KandVboth took the first element's type.firstValue([["one", 1]])inferred<string, string>and didn't compile, andnew Map([["a", 1]])would have gone the same way. Fields are now matched by position.Test:
00generic_class_constructor_inference.ts. Cases:[K, V][]parameter;newinside a generic function;Without the change it doesn't compile.
2.
undefinedin a slot that can't hold it is zero, not undefTyped Sets exposed this straight away. A finished generator's
{ value: undefined, done: true }putsundefinedinto avalueof the element type. When that type can't hold it (si32, number, boolean), the field was LLVMundef. That happened both in the constant tuple (LLVMCodeHelper::getTupleFromArrayAttr) and ints.UndefOp's lowering.Reading it back gave whatever the register held:
new Set([1, 2, 3]).values().drop(4).next()printed{ value: -971415704, done: true }. Owning types were already zero under rc, for the same reason and a worse one: a retain walks an undef pointer. Now every type in every model gets zero, which is deterministic and matches what a grown array's new slots hold.Test:
00generator_done_value_zero.ts. Without the fix it fails in every model.Results
sleep/weakref_basiclink errorsDefaultLib's tests now use the typed path.
set_union.ts, for example, instantiatesSet<si32>.The companion DefaultLib PR (ASDAlexander77/TypeScriptCompilerDefaultLib#16) updates
iterator_drop.ts. Its last case expected ananySet's{ value: undefined, done: true }. ASet<si32>can't holdundefined, so the case now checksdoneandvalue == 0.Found on the way, not fixed
let a: any = undefined; a == undefinedis false.anyfield that is assigned"text"and then compared== "text"is false. In a larger file the same lines crashed instead.🤖 Generated with Claude Code