Repository navigation
Bloomberg feedback for 4.8 Beta #49835
Description
We are in the process of evaluating the impact of upgrading to 4.8 on our codebase. Here some preliminary findings:
| # | Change | Affects | Release notes | Packages affected |
|---|---|---|---|---|
| 1 | Detection of weak generic types | Type checking | Announced | 100% |
| 2 | NonNullable<T> can't be indexed |
Type checking | Not Announced | 36% |
| 3 | Generic function inference with undefined | Type checking | Not Announced | ~2% |
| 4 | Nullable and keyof behave differently in inference | Type checking | Not Announced | < 1% |
| 5 | Type aliases for generic classes can't be checked with instanceof |
Type checking | Not Announced | < 1% |
| 6 | Binding patterns do not directly contribute to inference candidates | Type checking | Announced | < 1% |
| 7 | Type parameter not assignable to intersection that contains itself and an object type with an optional property. | Type Checking | Not Announced | <1% |
Detection of weak generic types
Announced breaking change.
Note: This was originally slated for 4.7 and was pushed back to 4.8. We are now in a better position to deal with this breaking change
The generic weak types on constraints has caused all our packages to fail due to an error in a base package. The specific pattern that we ran into is this:
type Spec = {
props?: string[]
}
type Class<T extends Spec> = { s: Spec }
function cls<T>(s: T):Class<T> { return null! }Playground Link 4.7 / Playground Link 4.8
NonNullable<T> can't be indexed
Bug.
Fixed by #49681
The new definition for NonNullable<T> means that we can no longer index into generic type parameters using a property of the constraint.
The usage of NonNullable that is giving us the biggest problems is located inside a definition for a base library, this is the main reason this bug impacts such a large number of packages.
We are currently exploring workarounds, such as using the old definition of NonNullable in the main affected package.
We think this is a bug in the handling of intersections and indexing operations.
type C = {
prop: undefined | string
}
// Ok in 4.7 nok in 4.8
type R2<T extends C | undefined> = NonNullable<T>['prop']Playground Link 4.8 / Playground Link 4.7
Nullable and keyof behave differently in inference
Bug.
Fixed by #49681
Inferring a key from a nested type that excludes null and undefined produces never instead of the expected keys.
interface BundleProperties {
settings?: {
a?: string;
b?: number
};
}
// "a" | "b"
type x = keyof NonNullable<BundleProperties["settings"]>
function addTwoLevelProperty<
K1 extends keyof BundleProperties,
K2 extends keyof NonNullable<BundleProperties[K1]>
>(level1: K1, level2: K2) { }
// K2 is never in 4.8
addTwoLevelProperty("settings", "a")Playground Link 4.7
/ Playground Link 4.8
Generic function inference with union that contains undefined
Probably a bug.
Given a type parameter T if there were two candidates number and 1 | undefined for it, in TypeScript 4.7 T would have been inferred to number | undefined. In 4.8 we get T as number which breaks assignability:
function equal<T>(a: T, b: T) { }
declare const a: number;
declare const b: undefined | 0 | 1 | 2;
equal(a, b) // Error in 4.8
declare const x: number;
declare const y: number | undefined;
equal(x, y) // okPlayground Link 4.7 /
Playground Link 4.8
Similarly this happens with arrays, when the two array types have a common base type that is be used undefined is omitted from the resulting inferred type argument:
function equal<T>(a: T, b: T) { }
declare const a: (number | string)[];
declare const b: number[] | undefined;
equal(a, b); // error in 4.8 T is (number | string)[] instead of (number | string)[] | undefined as in 4.7Playground Link 4.7 / Playground Link 4.8
Similar case with objects:
function equal<T>(a: T, b: T) { }
declare const a: Record<string, any>;
declare const b: null | { a: any};
equal(a, b)Playground Link 4.7 / Playground Link 4.8
Conclusion: I would seem that when determining the base type between two candidate types undefined and null are removed.
Type aliases for generic classes can't be checked with instanceof
Probably a bug.
When using an aliasing of a generic class in a type constraint, any variable of this type is not checkable with instanceof.
// If Table is non generic error does not occur
declare class TableClass<S = any> {
_field: S
}
export type Table = TableClass;
function fn<T extends Table>(o: T) {
return o instanceof TableClass // error in 4.8
}
declare const o: Table;
o instanceof TableClass // This is ok Playground Link 4.8 / Playground Link 4.7
Binding patterns do not directly contribute to inference candidates
Announced breaking change. This is a net improvement to not rely on this behavior
Type parameter not assignable to intersection that contains itself and an object type with an optional property.
function test<T>(arg: T ): T & { _field?: string } {
return arg;
}The solution here seems to add a constraint of object to T. Not sure if this break was intentional or not. Probably introduced with #49119
Activity
dragomirtitian commentedon Jul 14, 2022
Daniel Rosenwasser (@DanielRosenwasser) Should I open tickets for the ones we think are bugs? I have not been able to find any bugs relating to
or
DanielRosenwasser commentedon Jul 14, 2022
Yes, if you wouldn't mind, I'd appreciate that a ton, and link to/from them here as well.
dragomirtitian commentedon Jul 18, 2022
Generic function inference with union that contains undefined reported as #49938
Type aliases for generic classes can't be checked with instanceof reported as #49937