Announcing TypeScript 3.5

Avatar

Daniel

Today we’re happy to announce the availability of TypeScript 3.5!

If you’re new to TypeScript, it’s a language that builds on JavaScript that adds optional static types. TypeScript code gets type-checked to avoid common mistakes like typos and accidental coercions, and then gets transformed by a program called the TypeScript compiler. The compiler strips out any TypeScript-specific syntax and optionally transforms your code to work with older browsers, leaving you with clean, readable JavaScript that can run in your favorite browser or Node.js. Built on top of all this is also a language service which uses all the type information TypeScript has to provide powerful editor functionality like code completions, find-all-references, quick fixes, and refactorings. All of this is cross-platform, cross-editor, and open source.

TypeScript also provides that same tooling for JavaScript users, and can even type-check JavaScript code typed with JSDoc using the checkJs flag. If you’ve used editors like Visual Studio or Visual Studio Code with .js files, TypeScript powers that experience, so you might already be using TypeScript!

To get started with TypeScript, you can get it through NuGet, or through npm with the following command:

npm install -g typescript

You can also get editor support by

Support for other editors will likely be rolling in in the near future.

Let’s explore what’s new in 3.5!

Speed improvements

TypeScript 3.5 introduces several optimizations around type-checking and incremental builds.

Type-checking speed-ups

Much of the expressivity of our type system comes with a cost – any more work that we expect the compiler to do translates to longer compile times. Unfortunately, as part of a bug fix in TypeScript 3.4 we accidentally introduced a regression that could lead to an explosion in how much work the type-checker did, and in turn, type-checking time. The most-impacted set of users were those using the styled-components library. This regression was serious not just because it led to much higher build times for TypeScript code, but because editor operations for both TypeScript and JavaScript users became unbearably slow.

Over this past release, we focused heavily on optimizing certain code paths and stripping down certain functionality to the point where TypeScript 3.5 is actually faster than TypeScript 3.3 for many incremental checks. Not only have compile times fallen compared to 3.4, but code completion and any other editor operations should be much snappier too.

If you haven’t upgraded to TypeScript 3.4 due to these regressions, we would value your feedback to see whether TypeScript 3.5 addresses your performance concerns!

--incremental improvements

TypeScript 3.4 introduced a new --incremental compiler option. This option saves a bunch of information to a .tsbuildinfo file that can be used to speed up subsequent calls to tsc.

TypeScript 3.5 includes several optimizations to caching how the state of the world was calculated – compiler settings, why files were looked up, where files were found, etc. In scenarios involving hundreds of projects using TypeScript’s project references in --build mode, we’ve found that the amount of time rebuilding can be reduced by as much as 68% compared to TypeScript 3.4!

For more details, you can see the pull requests to

The Omit helper type

Much of the time, we want to create an object that omits certain properties. It turns out that we can express types like that using TypeScript’s built-in Pick and Exclude helpers. For example, if we wanted to define a Person that has no location property, we could write the following:

type Person = {
    name: string;
    age: number;
    location: string;
};

type RemainingKeys = Exclude<keyof Person, "location">;

type QuantumPerson = Pick<Person, RemainingKeys>;

// equivalent to
type QuantumPerson = {
    name: string;
    age: number;
};

Here we “subtracted” "location" from the set of properties of Person using the Exclude helper type. We then picked them right off of Person using the Pick helper type.

It turns out this type of operation comes up frequently enough that users will write a helper type to do exactly this:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Instead of making everyone define their own version of Omit, TypeScript 3.5 will include its own in lib.d.ts which can be used anywhere. The compiler itself will use this Omit type to express types created through object rest destructuring declarations on generics.

For more details, see the pull request on GitHub to add Omit, as well as the change to use Omit for object rest.

Improved excess property checks in union types

TypeScript has a feature called excess property checking in object literals. This feature is meant to detect typos for when a type isn’t expecting a specific property.

type Style = {
    alignment: string,
    color?: string
};

const s: Style = {
    alignment: "center",
    colour: "grey"
//  ^^^^^^ error! 
};

In TypeScript 3.4 and earlier, certain excess properties were allowed in situations where they really shouldn’t have been. For instance, TypeScript 3.4 permitted the incorrect name property in the object literal even though its types don’t match between Point and Label.

type Point = {
    x: number;
    y: number;
};

type Label = {
    name: string;
};

const thing: Point | Label = {
    x: 0,
    y: 0,
    name: true // uh-oh!
};

Previously, a non-disciminated union wouldn’t have any excess property checking done on its members, and as a result, the incorrectly typed name property slipped by.

In TypeScript 3.5, the type-checker at least verifies that all the provided properties belong to some union member and have the appropriate type, meaning that the sample above correctly issues an error.

Note that partial overlap is still permitted as long as the property types are valid.

const pl: Point | Label = {
    x: 0,
    y: 0,
    name: "origin" // okay
};

The --allowUmdGlobalAccess flag

In TypeScript 3.5, you can now reference UMD global declarations like

export as namespace foo;

from anywhere – even modules – using the new --allowUmdGlobalAccess flag.

This feature might require some background if you’re not familiar with UMD globals in TypeScript. A while back, JavaScript libraries were often published as global variables with properties tacked on – you sort of hoped that nobody picked a library name that was identical to yours. Over time, authors of modern JavaScript libraries started publishing using module systems to prevent some of these issues. While module systems alleviated certain classes of issues, they did leave users who were used to using global variables out in the rain.

As a work-around, many libraries are authored in a way that define a global object if a module loader isn’t available at runtime. This is typically leveraged when users target a module format called “UMD”, and as such, TypeScript has a way to describe this pattern which we’ve called “UMD global namespaces”:

export as namespace preact;

Whenever you’re in a script file (a non-module file), you’ll be able to access one of these UMD globals.

So what’s the problem? Well, not all libraries conditionally set their global declarations. Some just always create a global in addition to registering with the module system. We decided to err on the more conservative side, and many of us felt that if a library could be imported, that was probably the the intent of the author.

In reality, we received a lot of feedback that users were writing modules where some libraries were consumed as globals, and others were consumed through imports. So in the interest of making those users’ lives easier, we’ve introduced the allowUmdGlobalAccess flag in TypeScript 3.5.

For more details, see the pull request on GitHub.

Smarter union type checking

When checking against union types, TypeScript typically compares each constituent type in isolation. For example, take the following code:

type S = { done: boolean, value: number }
type T =
    | { done: false, value: number }
    | { done: true, value: number };

declare let source: S;
declare let target: T;

target = source;

Assigning source to target involves checking whether the type of source is assignable to target. That in turn means that TypeScript needs to check whether S:

{ done: boolean, value: number }

is assignable to T:

{ done: false, value: number } | { done: true, value: number }

Prior to TypeScript 3.5, the check in this specific example would fail, because S isn’t assignable to { done: false, value: number } nor { done: true, value: number }. Why? Because the done property in S isn’t specific enough – it’s boolean whereas each constituent of T has a done property that’s specifically true or false. That’s what we meant by each constituent type being checked in isolation: TypeScript doesn’t just union each property together and see if S is assignable to that. If it did, some bad code could get through like the following:

interface Foo {
    kind: "foo";
    value: string;
}

interface Bar {
    kind: "bar";
    value: number;
}

function doSomething(x: Foo | Bar) {
    if (x.kind === "foo") {
        x.value.toLowerCase();
    }
}

// uh-oh - luckily TypeScript errors here!
doSomething({
    kind: "foo",
    value: 123,
});

So clearly this behavior is good for some set of cases. Was TypeScript being helpful in the original example though? Not really. If you figure out the precise type of any possible value of S, you can actually see that it matches the types in T exactly.

That’s why in TypeScript 3.5, when assigning to types with discriminant properties like in T, the language actually will go further and decompose types like S into a union of every possible inhabitant type. In this case, since boolean is a union of true and false, S will be viewed as a union of { done: false, value: number } and { done: true, value: number }.

For more details, you can see the original pull request on GitHub.

Higher order type inference from generic constructors

In TypeScript 3.4, we improved inference for when generic functions that return functions like so:

function compose<T, U, V>(
    f: (x: T) => U, g: (y: U) => V): (x: T) => V {
    
    return x => g(f(x))
}

took other generic functions as arguments, like so:

function arrayify<T>(x: T): T[] {
    return [x];
}

type Box<U> = { value: U }
function boxify<U>(y: U): Box<U> {
    return { value: y };
}

let newFn = compose(arrayify, boxify);

Instead of a relatively useless type like (x: {}) => Box<{}[]>, which older versions of the language would infer, TypeScript 3.4’s inference allows newFn to be generic. Its new type is <T>(x: T) => Box<T[]>.

TypeScript 3.5 generalizes this behavior to work on constructor functions as well.

class Box<T> {
    kind: "box";
    value: T;
    constructor(value: T) {
        this.value = value;
    }
}

class Bag<U> {
    kind: "bag";
    value: U;
    constructor(value: U) {
        this.value = value;
    }
}


function composeCtor<T, U, V>(
    F: new (x: T) => U, G: new (y: U) => V): (x: T) => V {
    
    return x => new G(new F(x))
}

let f = composeCtor(Box, Bag); // has type '<T>(x: T) => Bag<Box<T>>'
let a = f(1024); // has type 'Bag<Box<number>>'

In addition to compositional patterns like the above, this new inference on generic constructors means that functions that operate on class components in certain UI libraries like React can more correctly operate on generic class components.

type ComponentClass<P> = new (props: P) => Component<P>;
declare class Component<P> {
    props: P;
    constructor(props: P);
}

declare function myHoc<P>(C: ComponentClass<P>): ComponentClass<P>;

type NestedProps<T> = { foo: number, stuff: T };

declare class GenericComponent<T> extends Component<NestedProps<T>> {
}

// type is 'new <T>(props: NestedProps<T>) => Component<NestedProps<T>>'
const GenericComponent2 = myHoc(GenericComponent);

To learn more, check out the original pull request on GitHub.

Smart Select

TypeScript 3.5 provides an API for editors to expand text selections farther and farther outward in a way that is syntactically aware – in other words, the editor knows which constructs it should expand out to. This feature is called Smart Select, and the result is that editors don’t have to resort to heuristics like brace-matching, and you can expect selection expansion in editors like Visual Studio Code to “just work”.

Smart selection in action

As with all of our editing features, this feature is cross-platform and available to any editor which can appropriately query TypeScript’s language server.

Extract to type alias

Thanks to Wenlu Wang (GitHub user @Kingwl), TypeScript supports a useful new refactoring to extract types to local type aliases.

Example of extracting to a type alias

For those who prefer interfaces over type aliases, an issue exists for extracting object types to interfaces as well.

Breaking changes

Generic type parameters are implicitly constrained to unknown

In TypeScript 3.5, generic type parameters without an explicit constraint are now implicitly constrained to unknown, whereas previously the implicit constraint of type parameters was the empty object type {}.

In practice, {} and unknown are pretty similar, but there are a few key differences:

  • {} can be indexed with a string (k["foo"]), though this is an implicit any error under --noImplicitAny.
  • {} is assumed to not be null or undefined, whereas unknown is possibly one of those values.
  • {} is assignable to object, but unknown is not.

The decision to switch to unknown is rooted that it is more correct for unconstrained generics – there’s no telling how a generic type will be instantiated.

On the caller side, this typically means that assignment to object will fail, and methods on Object like toString, toLocaleString, valueOf, hasOwnProperty, isPrototypeOf, and propertyIsEnumerable will no longer be available.

function foo<T>(x: T): [T, string] {
    return [x, x.toString()]
    //           ~~~~~~~~ error! Property 'toString' does not exist on type 'T'.
}

As a workaround, you can add an explicit constraint of {} to a type parameter to get the old behavior.

//             vvvvvvvvvv
function foo<T extends {}>(x: T): [T, string] {
    return [x, x.toString()]
}

From the caller side, failed inferences for generic type arguments will result in unknown instead of {}.

function parse<T>(x: string): T {
    return JSON.parse(x);
}

// k has type 'unknown' - previously, it was '{}'.
const k = parse("...");

As a workaround, you can provide an explicit type argument:

// 'k' now has type '{}'
const k = parse<{}>("...");

{ [k: string]: unknown } is no longer a wildcard assignment target

The index signature { [s: string]: any } in TypeScript behaves specially: it’s a valid assignment target for any object type. This is a special rule, since types with index signatures don’t normally produce this behavior.

Since its introduction, the type unknown in an index signature behaved the same way:

let dict: { [s: string]: unknown };
// Was okay
dict = () => {};

In general this rule makes sense; the implied constraint of “all its properties are some subtype of unknown” is trivially true of any object type. However, in TypeScript 3.5, this special rule is removed for { [s: string]: unknown }.

This was a necessary change because of the change from {} to unknown when generic inference has no candidates. Consider this code:

declare function someFunc(): void;
declare function fn<T>(arg: { [k: string]: T }): void;
fn(someFunc);

In TypeScript 3.4, the following sequence occurred:

  • No candidates were found for T
  • T is selected to be {}
  • someFunc isn’t assignable to arg because there are no special rules allowing arbitrary assignment to { [k: string]: {} }
  • The call is correctly rejected

Due to changes around unconstrained type parameters falling back to unknown (see above), arg would have had the type { [k: string]: unknown }, which anything is assignable to, so the call would have incorrectly been allowed. That’s why TypeScript 3.5 removes the specialized assignability rule to permit assignment to { [k: string]: unknown }.

Note that fresh object literals are still exempt from this check.

const obj = { m: 10 }; 
// okay
const dict: { [s: string]: unknown } = obj;

Depending on the intended behavior of { [s: string]: unknown }, several alternatives are available:

  • { [s: string]: any }
  • { [s: string]: {} }
  • object
  • unknown
  • any

We recommend sketching out your desired use cases and seeing which one is the best option for your particular use case.

Improved excess property checks in union types

As mentioned above, TypeScript 3.5 is stricter about excess property checks on constituents of union types.

We have not witnessed examples where this checking hasn’t caught legitimate issues, but in a pinch, any of the workarounds to disable excess property checking will apply:

  • Add a type assertion onto the object (e.g. { myProp: SomeType } as ExpectedType)
  • Add an index signature to the expected type to signal that unspecified properties are expected (e.g. interface ExpectedType { myProp: SomeType; [prop: string]: unknown })

Fixes to unsound writes to indexed access types

TypeScript allows you to represent the operation of accessing a property of an object via the name of that property:

type A = {
    s: string;
    n: number;
};

function read<K extends keyof A>(arg: A, key: K): A[K] {
    return arg[key];
} 

const a: A = { s: "", n: 0 };
const x = read(a, "s"); // x: string

While commonly used for reading values from an object, you can also use this for writes:

function write<K extends keyof A>(arg: A, key: K, value: A[K]): void {
    arg[key] = value;
}

In TypeScript 3.4, the logic used to validate a write was much too permissive:

function write<K extends keyof A>(arg: A, key: K, value: A[K]): void {
    // ???
    arg[key] = "hello, world";
}
// Breaks the object by putting a string where a number should be
write(a, "n", "oops");

In TypeScript 3.5, this logic is fixed and the above sample correctly issues an error.

Most instances of this error represent potential errors in the relevant code. If you are convinced that you are not dealing with an error, you can use a type assertion instead.

lib.d.ts includes the Omit helper type

TypeScript 3.5 includes a new Omit helper type. As a result, any global declarations of Omit included in your project will result in the following error message:

Duplicate identifier 'Omit'.

Two workarounds may be used here:

  1. Delete the duplicate declaration and use the one provided in lib.d.ts.
  2. Export the existing declaration from a module file or a namespace to avoid a global collision. Existing usages can use an import or explicit reference to your project’s old Omit type.

Object.keys rejects primitives in ES5

In ECMAScript 5 environments, Object.keys throws an exception if passed any non-object argument:

// Throws if run in an ES5 runtime
Object.keys(10);

In ECMAScript 2015, Object.keys returns [] if its argument is a primitive:

// [] in ES6 runtime
Object.keys(10);

This is a potential source of error that wasn’t previously identified. In TypeScript 3.5, if target (or equivalently lib) is ES5, calls to Object.keys must pass a valid object.

In general, errors here represent possible exceptions in your application and should be treated as such. If you happen to know through other means that a value is an object, a type assertion is appropriate:

function fn(arg: object | number, isArgActuallyObject: boolean) {
    if (isArgActuallyObject) {
        const k = Object.keys(arg as object);
    }
}

Note that this change interacts with the change in generic inference from {} to unknown, because {} is a valid object whereas unknown isn’t:

declare function fn<T>(): T;

// Was okay in TypeScript 3.4, errors in 3.5 under --target ES5
Object.keys(fn());

What’s next?

As with our last release, you can see our 3.6 iteration plan document, as well as the feature roadmap page to get an idea of what’s coming in the next version of TypeScript. We’re anticipating 3.6 will bring a better experience for authoring and consuming generators, support for ECMAScript’s private fields proposal, and APIs for build tools to support fast incremental builds and projects references. Also of note is the fact that as of TypeScript 3.6, our release schedule will be switching to a cadence of every 3 months (instead of every 2 months as it has been until this point). We believe this will make it easier for us to validate changes with partner teams and

We hope that this version of TypeScript makes you faster and happier as you code. Let us know what you think of this release on Twitter, and if you’ve got any suggestions on what we can do better, feel free to file an issue on GitHub.

Happy hacking!

– Daniel Rosenwasser and the TypeScript team

Avatar
Daniel Rosenwasser

Program Manager, TypeScript

Follow Daniel   

12 comments

  • Avatar
    Patrick Roza

    Really great stuff, love the performance and union improvements.
    i hope the focus of typescript improvements will move to FP for a while soon; pipe operators |>, >> and <<, as well as improved support for partial application, for instance would be great assets.

    • Avatar
      Muhammad Imran

      I m watching these good articles pass by from years on news feeds. Today i m collecting breath n time to write..
      Would the auther of this n similar articles agree?.. Satya made us march behind his chakra engine like piedpiper just to ditch us to competitor engine. We kept marching behind powershell n this typescript, and not once we were warned python was creaping pass by under our chairs to become hit only to be woken up to reality where ms again telling us to change our religion from ms to another. We were never warned nor any articles passed by on news feed where ms would have fortold us to adapt accordingly, i/many kept in false hopes that ms has arrow in its quiver againt any competition. I dont want to whine/distract on winpho n other things i just need focus on these above 2 examples. Thanks. 

  • Avatar
    Alex Ivasyuv

    Thanks for article! But guys, I think you should seriously considering looking into issues instead of adding new stuff. For example currently on Github it’s ~3750 open issues. Also I found that some “maintainers” often close inconvinient issues.
    I eager to see Typescript to be part of ecosystem not just yet-another-transpiler-compiler, thus you have to hear what developers want to be fixed. For instance, Typescript still can’t produce VALID ESM output, e.g. with .js extenstion (adding it manually not an option, as it will break other build systems). There’re lots of such tickets, most of them are already closed. That question was raised since 2017, and not yet solved.

  • Kevin Frey
    Kevin Frey

    I absolutely have to agree with Alex. Three years on and no-one has bothered adding the “override” keyword to the language to ensure signature correctness of overridden methods. But one of the arguments seems to be that introducing a compiler flag to enforce overrides (with a fall-back of C++ style overrides as the “soft” default) is going to occupy too much of someone’s “mental space”. And then I see the fixes put into 3.5 that are probably irrelevant to 95% of people and clearly show that someone has the “mental space” to solve these kinds of issues.The other comment I’d make is that working software engineers are quite pragmatic about how they build code (I am, anyway). The language elements you are talking about here are in the 1% case for me, whereas an override keyword is in my 80% case (ie. it applies to what we do all day, every day, as a bread-and-butter engineering concept).
    You have some awesome features in the language _but_ it seems like you are too focussed on the “cool and nerdy” stuff rather than stuff regular developers will benefit from _most_.

  • Carl Erik Kopseng
    Carl Erik Kopseng

    Here to bring some kudos and positive wibes! I was using TS in its 0.8-1.X heyday (2015-16), and I found it verbose, slow and hard to get the syntax playing nicely. Refactoring in Webstorm was also lightyears away from what I was used to in Java, and quite error prone (unit tests ftw!). I never used it again after that project, until I recently saw the 3.4 release.
    That finally sparked my interest again: much faster (incremental) compiles makes for faster feedback (maybe even Gary Bernhard will be pleased?), sane co-living with functional programming, corporate backed intellisense that even works in Vim (!) and wonderful type inference! This is great work and I applaud you for getting to where you are today. I am finally getting back in the fold 🙂
    Though the amount of typings needed for just a simple project as react-redux makes me think you still have some way to go before creating these types doesn’t absorb huge amounts of time 🙂 
    Trying to sanely convert an existing enterprise project in a gradual, file-by-file manner is still something I think you should address to increase adoption. Dedicating a two week sprint just to add types is not going to fly in the kind of startups I work in (good intentions aside).

  • Avatar
    Web Places Online IT

    It concerns me that the change from `{}` to `unknown` is so significant that TypeScript should have used version 4.0 instead of 3.5.  I’m not a fan of breaking changes between minor versions within a major version number.  Anyone upgrading from 3.x to 3.5 can reasonably assume that there are no _breaking changes_ and it’s safe to upgrade and get the performance improvements.  And since so many use cases are affected by the breaking change, it’s really a major change.

  • Nico Jansen
    Nico Jansen

    > We believe this will make it easier for us to validate changes with partner teams and@Daniel Rosenwasser, don’t leave us hanging. What did you mean to say here? 👏

  • Stephen Haney
    Stephen Haney

    Thanks for prioritizing the Styled Component speed fixes – the weather’s getting too hot for pegged CPUs 😀 We will give 3.5 a spin shortly.

  • Stephen Haney
    Stephen Haney

    Hi guys, just wanted to report we’re still pegging CPUs with MacOS, VSCode, Create React App, TS, ESLint, and Styled Components on 3.5.1. I’ll open a GitHub issue as well if it will be helpful. I’ll do some more investigating first to isolate the cause.

Leave a comment