TypeScript logo on a dark blue background

TypeScript 4.6 is this year’s first feature release for the statically typed JavaScript superset. It adds several improvements around constructors, compilation, and code analysis. There are also a couple of breaking changes to be aware of before you upgrade.

Control Flow Analysis Improvements

This release brings several enhancements for TypeScript’s control flow analysis capabilities. They better equip TypeScript to more precisely understand how your code operates, leading to narrower type definitions and fewer unexpected errors.

The first change concerns discriminant property unions that have been destructured from objects. It applies to cases where you’re working with union types that consist of multiple objects. Individual objects in the union may have differences in their type definitions but still share some keys.

It’s common to use those shared keys to conditionally inspect the other parts of the data structure. TypeScript used to error when using this workflow with a destructuring assignment. The destructuring syntax created wholly new variables, devoid of the association that linked them as object properties. The compiler now remembers that the values originated from the same object, allowing simpler code to be written.

The release announcement uses an example similar to this:

type Action =
    | {kind: "Number", data: number}
    | {kind: "String", data: string};
 
function handle(action: Action) {
 
    const {kind, data} = action;
 
    if (kind === "Number") {
        const square = (data * data);
    }
    else if (kind === "String") {
        const lines = data.split("n");
    }
 
}

TypeScript 4.5 would not permit this code. The destructuring assignment (const {kind, data}) created two independent variables; the compiler couldn’t understand that kind being Number means data must be a number type. Now it will recognize that fact, letting you use the destructuring syntax with discriminated unions.

Advertisement

Control flow analysis has also been tightened around dependent parameters. This syntax lets you specify complex rules for the nature of variadic parameters. Here’s a type definition for a function using this behavior:

type Func = (...args: ["Number", number], ["String", string]) => void;

This function’s signature specifies that you can either pass String or Number as its first parameter. If you use Number, the next parameter must be a value of type number. Alternatively, a string can be given if it’s following String.

Similarly to the discriminated unions example above, TypeScript now narrows the type of dependent parameters based on the values that preceded them:

const demo: Func = (kind, data) => {
    if (kind === "Number") {
        const square = (data * data);
    }
    else if (kind === "String") {
        const lines = data.split("n");
    }
};

The compiler now appreciates that data must be a number if kind is Number. This code would have thrown an error with TypeScript 4.5.

Constructor Enhancements

The constructors of JavaScript classes that extend a parent must call super() before the this keyword can be used:

// Not allowed - "this" used before "super"
class B extends A {
    constructor() {
        this.demo = "foobar";
        super();
    }
}
 
// Working correct order
class C extends A {
    constructor() {
        super();
        this.demo = "foobar";
    }
}

TypeScript’s historically been too strict in its enforcement of this requirement. Classes containing property initializers would be rejected if they had any code before the super() statement, even if it never referred to this:

const example = () => null;
 
class C extends A {
 
    demoProperty = true;
 
    // Unnecessarily treated as invalid 
    constructor() {
        example();
        super();
    }
}
Advertisement

This handling helped to optimize TypeScript’s checks for genuine instances of this being used before super(). It also resulted in a lot of acceptable code failing to compile, forcing authors to refactor work that was actually valid JavaScript.

TypeScript 4.6 solves this issue. The compiler now falls in line with vanilla JavaScript by allowing code before super() if it won’t result in this being used. This gives you more freedom to write class constructors in the way that makes most sense for each situation. TypeScript will still detect any real cases of this being referenced too early.

More Inference Improvements

Indexed access inferences are now more precise. This gives the compiler visibility into indexed access types which index to mapped objects. Although this was already possible before, older TypeScript releases produced poor quality inference which didn’t always have full awareness of the mapped object’s types.

TypeScript’s recursion depth checks have also been adjusted to enable better detection of incompatible recursive types. The change can improve type check performance as it facilitates earlier bail out when a type’s recursion begins to infinitely expand. The improvements focus on how recursion is applied to types that use generics.

ES2022 Targeting

The --target flag has gained support for es2022 as a value. It enables full use of ES2022 features, ensuring syntax such as class fields and the Error.cause property are preserved without transpilation in your stable builds.

You can target ES2022 by passing the --target es2022 flag to tsc. Alternatively, change compilerOptions.target to es2022 in your project’s tsconfig.json file:

{
    "compilerOptions": {
        "target": "es2022"
    }
}

More JavaScript Syntax and Binding Errors

TypeScript now surfaces more standard JavaScript syntax and binding errors. These errors will show up during compilation and as you open files in your editor with the TypeScript extension for Visual Studio, Visual Studio Code, or Sublime Text installed.

Advertisement

Repeated const statements, incorrect use of keywords, and scoping mistakes will now be surfaced by TypeScript, giving you immediate feedback as you work. The functionality can be disabled by adding a // @ts-nocheck comment at the top of your source files.

This addition constitutes a breaking change as source files must now contain grammatically correct JavaScript. TypeScript’s compiler previously ignored most kinds of JavaScript syntax error. You should use the comment to turn off the feature if you think your codebase will be incompatible with this enforcement.

Another Breaking Change

There’s a second breaking change in this update: object rest expressions now drop non-spreadable members from generic objects. This improves consistency with the destructuring of non-generic types but means your existing variables may lack some properties they possessed in TypeScript 4.5.

Properties with non-spreadable values such as scalars and functions will be omitted from rest expressions. This also applies to other non-spreadable cases such as non-public members of class instances and this. Code such as the following used to work but will now throw an error:

class Demo {
 
    prop = "example";
 
    sayHello() {
        console.log("Hello World");
    }
 
    methodWithRest() {
 
        const {prop, ...rest} = this;
 
        // ERROR - Non-spreadable value (function) will be dropped
        rest.sayHello();
 
    }
 
}

Summary

TypeScript 4.6 enhances type inference, improves child class constructor handling, and adds ES2022 as a supported output target. There are also a couple of narrow breaking changes in the form of expanded JavaScript syntax error detection and dropped non-spreadable object rest expression members. You can upgrade to the new release by running npm update typescript or npm install typescript@latest within your project’s working directory.

Advertisement

The update also marks the debut of an advanced new tool for analyzing TypeScript’s existing type generation traces. The Trace Analyzer works with the output from tsc --generateTrace to help you pinpoint how complex type expressions are slowing down the compiler.

Profile Photo for James Walker James Walker
James Walker is a contributor to How-To Geek DevOps. He is the founder of Heron Web, a UK-based digital agency providing bespoke software development services to SMEs. He has experience managing complete end-to-end web development workflows, using technologies including Linux, GitLab, Docker, and Kubernetes.
Read Full Bio »