Skip to content

Bloomberg feedback for 5.0 Beta #52670

Closed
Closed
@dragomirtitian

Description

@dragomirtitian

By @molisani & @dragomirtitian

We are in the process of evaluating the impact of upgrading to TypeScript 5.0 on our codebase. Here are some preliminary findings.

This is shaping up to be a medium impact release. The following table lists changes that affected our codebase, and where appropriate a link to a separate Issue:

# Change Affects Release notes Packages affected Reported as
1 Union with any does not reduce to any Type Checking Not Announced ~6% #52568
2 No implicit coercions in relational operators Type Checking Yes ~2%
3 Type inference for declared enum member assignment Type Checking Not Announced ~2% #52531
4 Circular constraints don’t type check sometimes Type Checking Not Announced ~1% #52570
5 instanceof narrows to never on else branch Type Checking Not Announced 1 #52571
6 Nested callbacks are not contextually typed Type Checking Not Announced 1 #52575
7 Declarations no longer include default type parameters Declaration Emit Not Announced ~1%
8 Misc type checking improvements Type Checking Not Announced 2

Union with any does not reduce to any

Generally T | any will reduce to any regardless of the origin of such a union. In TS 5.0 this is not the case for unions originating from a conditional type such as below.

Reported as #52568 (fixed)

First appeared in 5.0.0-dev.20221208. A cursory look at merged PRs doesn’t reveal an immediate culprit.

type Spec = any extends object ? any : string;

type WithSpec<T extends number> = T

type R = WithSpec<Spec>

Playground Link

No implicit coercions in relational operators

TypeScript now forbids implicit coercions when using relational operators as described here. We investigated a number of errors raised by this new check but none of them seem to be real runtime issues for us.

Circular constraints don’t type check sometimes

This is a very hard to reproduce issue. It only occurs in VS Code when you first open the file and can’t be reproduced using the CLI. Unfortunately it impacts our build tool which uses the compiler API.

Reported as #52570

Give these files:

// types.ts
export type SelectorMap<T extends Record<string, (...params: unknown[]) => unknown>> = {
    [key in keyof T]: T[key];
};

// index.ts
export declare const value2: {
    sliceSelectors: <FuncMap extends import('./types').SelectorMap<FuncMap>>(selectorsBySlice: FuncMap) => { [P in keyof FuncMap]: Parameters<FuncMap[P]> };
};

We get these errors when we first open the types in VS Code.

Type parameter 'FuncMap' has a circular constraint.

Type 'FuncMap[P]' does not satisfy the constraint '(...args: any) => any'.
  Type 'FuncMap[keyof FuncMap]' is not assignable to type '(...args: any) => any'.
    Type 'FuncMap[string] | FuncMap[number] | FuncMap[symbol]' is not assignable to type '(...args: any) => any'.
      Type 'FuncMap[string]' is not assignable to type '(...args: any) => any'.

Any subsequent editing will make the errors go away.

Given the very unstable nature of this bug, it might be a question of file ordering. Moving the SelectorMap to the same file and dropping the import makes the error go away.

In our case these types appear in generated declaration files.

The circular constraint which is present doesn’t seem to make much sense in our case, so we took the opportunity to clean up the types, so this is not blocking for us, but the behavior is still very strange.

instanceof narrows to never on else branch

When testing with instanceof using a class that extends Function as the class, Typescript would previously not narrow the type on any branch. Now it narrows to never on the else branch. This causes some mixin code to break for us.

First appeared in 5.0.0-dev.20221130.

Reported as #52571 (fixed)

class PersonMixin extends Function {
    public [Symbol.hasInstance](o: any) {
        return typeof o === "object" && o !== null && o instanceof Person;
    }    
}
const cls = new PersonMixin();


function test(o:  Person | Car) {
    if (o instanceof cls) {
        console.log("Is Person");
        (o as Person).work()
    }
    else {
        console.log("Is Car")
        o.sayHi(); //o is never, was Person | Car
    }
}

Playground Link

Declarations no longer include default type parameters

On imported types with a type parameter that has a default, the default type for the type parameter is no longer included in the declaration emit (this is not the case if the type is in the same file):

// types.ts
export declare type Table<S = string> = { value: S };
 
// Emitted in both 4.9 and 5.0 as
// export declare const o: Table<string>;
export const o = null! as Table
// index.ts
import { Table } from "./type";


// Emitted in 5.0 as
// export declare const o: Table;
// Emitted in 4.9 as
// export declare const o: Table<string>;
export const o = null! as Table

This is arguably a very good improvement to declaration emit as it makes declarations not depend on the default of the original type, allowing it to change without the need to rebuild all downstream declarations.

Nested callbacks are not contextually typed

Nested callback parameters are not contextually typed if the signature comes from an indexed type, indexed with a type parameter.

Reported as #52575

export interface Event<T> {
    callback: (response: T) => void;
    nested: {
        callback: (response: T) => void;
    }
}


export type CustomEvents = {
    a: Event<string>
    b: Event<number> // If only one candidate type exists it works
};


declare function emit<T extends keyof CustomEvents>(type: T, data: CustomEvents[T]): void


emit('a', {
    callback: (r) => {},
    nested: {
        callback: (response) => {


        },
    },
});

Playground Link

Misc type checking improvements

The following code now errors, as it should, where it did not in 4.9:

let exports: { x?: unknown; foo?: unknown } = {};
exports = exports.foo; // error in 5.0 not in 4.9

Playground Link

Destructuring from unknown inside a catch error variable is now an error:

try {
} catch ({
    status, // error in 5.0 not in 4.9
}: unknown) {
}

Playground Link

Metadata

Metadata

Labels

DiscussionIssues which may not have code impact

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions