Closed
Description
Stricter Relational Comparison Operators
- When narrowed to
Promise
, we reject comparisons with anumber
. - We use the comparable relationship to determine if we can compare two types. It is optimistic and roughly checks if one operand has overlap with the other - and allows a
Promise | number
to be compared with anumber
. - Recurring issue - discussed in the past
- Is it meaningful to allow
>=
when you have an object?- Feels like it's questionable.
- Can you get away with just restricting to
number
s andbigint
s?string
s!
- "We have to be more choosier when checking for overlappiness"
- Kind of want to figure out a strategy where we get the base primitive type of each operand and then check if they're assignable to
bigint | number
, orstring
, and both have overlap.- Probably some subtlety - make sure to test with generics.
Improved Logic When Choosing Between Covariant and Contravariant Inferences
interface A { a: string }
interface B extends A { b: string }
interface C extends A { c: string }
declare function cast<T, U extends T>(x: T, test: (x: T) => x is U): U;
declare function isC(x: A): x is C;
function f1(a: A, b: B) {
const x1 = cast(a, isC); // cast<A, C>
const x2 = cast(b, isC); // cast<A, C>
// ~~~
// Argument of type '(x: A) => x is C' is not assignable to parameter of type '(x: B) => x is B'.
// Type predicate 'x is C' is not assignable to 'x is B'.
// Property 'b' is missing in type 'C' but required in type 'B'.
}
- When doing type argument inference, we separate covariant inference sites from contravariant inference sites.
- When deciding on what to infer, we use these to prioritize.
- We try to pick covariant inferences, and by default construct a type from covariant inference candidates.
- We do not use this type constructed by covariant inferences if:
- the constructed type is
never
, or - it's not a subtype of any contravariant inference candidate.
- the constructed type is
- [Editor's Note]: The intuition here is that a contravariant inference site places a hard expectation on what it can be given; if that fails, using the contravariant inference will provide a better error message.
- We do not use this type constructed by covariant inferences if:
- Improve logic that chooses co- vs. contra-variant inferences #52123 added another exception here where we do not do this when the type parameter is used as a constraint of another type parameter and for that constrained type parameter, its covariant inference candidates the constructed covariant type of its constraint (i.e. the original type parameter).
- So now in the assignment to
x2
, we choose the typeA
instead ofC
becauseT
is used as the constraint ofU
, and the candidate ofA
would not have been assignable to
- When a covariant inference is a subtype of the contravariant inference, you have a range of types you can choose from.
- Doesn't solve all possible variations of this - doesn't fix the case of arbitrarily nested generic constraints.
Varying Subtype Reduction Depending on Declaration Order
declare let u: Promise<unknown>
declare let a: Promise<any>
// Assigned an expression that is Promise<any> | Promise<unknown>
// but that will undergo subtype reduction
// The type of union depends on whether a or u was declared first
let union =
// ^?
// Varies between 'Promise<any>' and 'Promise<unknown>'.
Math.random() > 0.5
? Promise.reject<any>()
: Promise.reject<unknown>();
union.then(v => v.toString());
- These differ by type ID based on where they're declared in the file and get sorted in an initial union type.
- When we try to reduce the union, whichever came first "wins" because
any
andunknown
are both (non-strict) subtypes of each other. - Unclear if you'd want
unknown
orany
to win out. Presumably first principles would indicateunknown
because it's the safer one. - Don't really have any solution here. Many similar issues show up.
Monomorphism Recap - Runtime Speed vs. Memory Usage
- We got some nice performance boosts from ensuring definite shapes on certain Nodes and Symbols - but it came at the expense of memory.
- Feels like a good tradeoff in general.
- We can start shrinking many of the types we have in the compiler - lots of optional boolean properties.
- But V8 allocates in chunks - so this work could be for nothing.
- Still buys us some wiggle room, more possibility to shrink down the line.
- Can we just shrink down
Identifier
to 2 properties?- 2 additional properties on top of
Node
. - We can try.
- Get rid of
originalKeywordKind
- Move
hasExtendedUnicodeEscape
to the containing source file, turn it into a slow path during emit. - Move emit-related things into their own bucket.
- 2 additional properties on top of