Announcing TypeScript 2.7 RC

Today we’re publishing the Release Candidate of TypeScript 2.7. To get started with the RC, you can access it through NuGet, or use npm with the following command:

npm install -g typescript@rc

Visual Studio 2015 users (who have Update 3) can install TypeScript 2.7 RC from here, and Visual Studio 2017 users using version 15.2 or later will be able to get TypeScript by simply installing it from here.

You can also get the Release Candidate working with Visual Studio Code and Sublime Text.

While we have many new features and fixes, we have a few highlights for the RC we think are especially important.

Definite Assignment Checks for Class Properties

TypeScript 2.7 introduces a new flag called --strictPropertyInitialization. This flag performs checks to ensure that each instance property of a class gets initialized in the constructor body, or by a property initializer. For example

class C {
    foo: number;
    bar = "hello";
    baz: boolean;
//  ~~~
//  Error! Property 'baz' has no initializer and is not definitely assigned in the constructor.
    constructor() {
        this.foo = 42;
    }
}

In the above, if we truly meant for baz to potentially be undefined, we should have declared it with the type boolean | undefined.

There are certain scenarios where properties can be initialized indirectly (perhaps by a helper method or dependency injection library), in which case you can use the new definite assignment assertion modifiers for your properties.

class C {
    foo!: number;
    // ^
    // Notice this '!' modifier.
    // This is the "definite assignment assertion"

    constructor() {
        this.initialize();
    }

    initialize() {
        this.foo = 0;
    }
}

Keep in mind that --strictPropertyInitialization will be turned on along with other --strict mode flags, which can impact your project. You can set the strictPropertyInitialization setting to false in your tsconfig.json‘s compilerOptions, or --strictPropertyInitialization false on the command line to turn off this checking.

Fixed Length Tuples

In TypeScript 2.6 and earlier, [number, string, string] was considered a subtype of [number, string]. This was motivated by TypeScript’s structural nature; the first and second elements of a [number, string, string] are respectively subtypes of the first and second elements of [number, string]. However, after examining real world usage of tuples, we noticed that most situations in which this was permitted was typically undesirable.

Thanks to a pull request from Tycho Grouwstra, tuple types now encode their arity into the type of their respective length property, and tuples of different arities are no longer assignable to each other. This is accomplished by leveraging numeric literal types, which now allow tuples to be distinct from tuples of different arities.

Conceptually, you might consider the type [number, string] to be equivalent to the following declaration of NumStrTuple:

interface NumStrTuple extends Array<number | string> {
    0: number;
    1: string;
    length: 2; // using the numeric literal type '2'
}

Note that this is a breaking change. If you need to resort to the original behavior in which tuples only enforce a minimum size, you can use a similar declaration that does not explicitly define a length property, falling back to number.

interface MinimumNumStrTuple extends Array<number | string> {
    0: number;
    1: string;
}

Improved narrowing for in and instanceof

Thanks to GitHub user IdeaHunter, the in operator now acts as a narrowing expression for types, narrowing out types that don’t explicitly declare properties of a given name.

interface A { a: number };
interface B { b: string };

function foo(x: A | B) {
    if ("a" in x) {
        return x.a;
    }
    return x.b;
}

Furthermore, the instanceof operator is now leverages the inheritance chain instead of relying on structural compatibility, more accurately reflecting whether how instanceof may behave at runtime.

// Error! 
export class C {
    foo = 1;
}

export class D extends C {
    bar = 2;
}

export class E {
    foo = 3;
}

declare let x: C | D | E;

if (x instanceof E) {
    x // 'E', but previously 'D | E'
}
else {
    x // 'C | D', but previously 'C'
}

While we’ve witnessed positive changes in most codebases, be aware that any change in inference and narrowing can impact your compilation. Changes to type declarations, as well as type assertions, may be necessary.

Breaking Changes

This release brings some minor breaking changes:

  • Tuples now have fixed numeric length properties.
  • instanceof and in now have slightly different narrowing behavior.
  • Inferences from generic signatures now use base constraint types of type parameters instead of any.
  • The setSelectionRange API now only accepts "forward" | "backward" | "none".
  • allowSyntheticDefaultImports no longer synthesizes default imports from TypeScript implementation files (i.e. .ts and .tsx).

Additionally, as mentioned above, users with the --strict setting on will automatically be opted in to --strictPropertyInitialization which errors on properties which are not directly initialized on their declarations or in constructor bodies. While easy to opt out of by explicitly turning this check off, your code may be impacted.

You can get a detailed look from our list of breaking changes issues for TypeScript 2.7 on GitHub, or keep track of general breaking changes on our Breaking Changes wiki page.

What’s next?

A more comprehensive list of this release, as well as our future plans, can be found on our roadmap. We anticipate the full release of TypeScript 2.7 in the next few weeks, and in the meantime, we hope you’ll be able to help give us all feedback about the RC so that we can ensure a terrific release for all TypeScript users.

Feel free to drop us a line on GitHub if you run into any problems, and let others know how you feel about this RC on Twitter and in the comments below!