Announcing TypeScript 2.6

Daniel Rosenwasser

TypeScript 2.6 is here in time for Halloween, but have no fear! We’ve got some great treats for you in this release.

If you haven’t heard of TypeScript, it’s a language that builds on top of the most up-to-date versions of JavaScript by adding optional static types. These types don’t just help catch things like typos and logic errors; they also can bring you even better tooling like editor completions, easier navigation of your codebase, and more. Best of all, you don’t always have to write out your type annotations – TypeScript can often infer them for you!

To learn more about TypeScript itself, you can visit our website, but if you’re already familiar with it and want to try out the release, we have it available over NuGet or through npm using the following command:

npm install -g typescript

Visual Studio 2015 users (who have Update 3) can install TypeScript 2.6 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. Visual Studio 2017 users should be sure to read up on how you can configure your project to target specific versions of TypeScript.

TypeScript 2.6 will be available for other editors soon. In the meantime you can configure Visual Studio Code and Sublime Text to use a newer version. Other editors may have different approaches to getting TypeScript 2.6 running.

Let’s see what’s ready today in TypeScript 2.6!

Contravariant parameter types with --strictFunctionTypes

When comparing signatures – things that make your types callable or constructable – TypeScript has to account for both the return types and the parameter types.

Return types are easy – for a function f to be assignable to g, f‘s return type has to be assignable to g‘s return type. The fact that the directionality doesn’t change in this comparison is called covariance.

However, parameters are actually a different story – the correct approach is to go in the opposite direction! To see why, let’s take a quick example where we assign the below function g to f:

interface Animal { animalProp: any };
interface Dog extends Animal { dogProp: any };

let f = (animal: Animal) => animal.animalProp;
let g = (dog: Dog) => dog.dogProp;

// Should this succeed?
f = g;

At a glance, we might be tempted to say that since Dog is assignable to Animal, g is assignable to f, but that’s not the case. It becomes clear once we ask the right questions about substitutability:

  • Is it okay for a value of type (dog: Dog) => any to say it can be used in place of a (animal: Animal) => any?
    • Is it okay to say my function only expects an Animal when it may use properties that on Dog?
      • Only if an Animal can be used in place of a Dog – so is Animal assignable to Dog?
        • No! It’s missing dogProp.

So g is not assignable to f, but is the opposite true?

  • Is it okay for a value of type (animal: Animal) => any to say it can be used in place of a (dog: Dog) => any?
    • Is it okay to say my function expects a Dog when it may use properties that on Animal?
      • Only if a Dog can be used in place of an Animal – so is Dog assignable to Animal?
        • Yes!

Notice that in asking if (animal: Animal) => any is assignable to (dog: Dog) => any, we end up asking whether Dog is assignable to Animal. This flip in direction is called contravariance.

While this approach is appropriate in most languages, it’s difficult to reconcile it with the way that JavaScript is broadly used. Things like using arrays and describing methods in the HTML DOM hierarchy turn out to be problematic with strict contravariance. For instance, in the Array<T> type, its pop method returns a T and its push method takes a T. If TypeScript compared every function parameter contravariantly, it would make all Arrays invariant on T since T occurs in both covariant and contravariant positions. In other words, Array<Dog> wouldn’t be assignable to Array<Animal>, which could be challenging to work around for many scenarios.

That’s why TypeScript compares parameters bivariantly or bidirectionally. It means completely unrelated types will get caught, but it means that certain unsavory issues can fall through the cracks when there is enough overlap:

function makeLowerCase(s: string) {
    return s.toLowerCase();
}

declare let foo: Promise<string | number>;
foo.then(makeLowerCase); // Whoops! TypeScript allows this, but `makeLowerCase` might get a `number`.

That’s why in TypeScript 2.6, we’re bringing users a way to tighten things up with --strictFunctionTypes Under this new --strict mode flag, any function type that doesn’t originate from a method has its parameters compared strictly contravariantly.

That means that the above code will fail, because when we try to pass makeLowerCase with the type (s: string) => stringto then which expects a function of type (onfulfilled: string | number) => ..., we’ll flip directions and try to assess whether string | number is assignable to string (which isn’t the case – string is a subtype of string | number). Excluding methods from the check allows TypeScript to continue modeling the above use-cases (e.g. event handlers and simpler array handling) while still bringing this much-demanded strictness check.

Covariance and contravariance probably deserve a more thorough explanation. If you’d like to read a bit more, Stephan Boyer has an approachable article that gives a reasonable high-level explanation. You can also read up more on the original pull request. The short version is that with --strictFunctionTypes, you’ll be able to catch many common mistakes.

Keep in mind that while --strictFunctionTypes is opt-in, it will automatically be turned on if you’re operating with the --strict flag on. This may introduce some breaks, so to disable the check with --strict on, you can specify --strictFunctionTypes false on the command line or in your tsconfig.json as so:

{
    "compilerOptions": {
        "strict": true,
        "strictFunctionTypes": false
    }
}

Translated tsc via the --locale flag

The standalone TypeScript compiler now provides localized (translated) messages over npm when using the --locale flag. Simply pass the appropriate language tag as an argument to the TypeScript compiler’s --locale option. If the language is supported, TypeScript will provide a translated version, and fall back to

This means that you can get messages

in Chinese (Simplified with zh-CN and Traditional with zh-TW):

tsc --pretty --locale zh-CN

bar.ts(1,1): error TS2362: 算术运算左侧必须是 "any"、、"number" 或枚举类型。

'hello' * 1
~~~~~~~

in Spanish:

$ tsc --pretty --locale es

foo.ts(1,1): error TS2362: La parte izquierda de una operación aritmética debe ser de tipo "any", "number" o un tipo enum.

'hello' * 1
~~~~~~~

in Japanese:

$ tsc --pretty --locale ja

foo.ts(1,1): error TS2362: 算術演算の左辺には、'any' 型、'number' 型、または列挙型を指定してください。

'hello' * 1
~~~~~~~

in Russian:

$ tsc --pretty --locale ru

foo.ts(1,1): error TS2362: Левый операнд арифметической операции должен иметь тип any, number или тип перечисления.

'hello' * 1
~~~~~~~

and others that you’ll be able to read about on our compiler options page.

Faster --watch mode

TypeScript’s --watch mode now acts much more incrementally when emitting modules. Given a set of changed files, tsc will now figure out affected set of files for which a change might be impactful. This means that only that impacted files will undergo a tree transform pass (the process of transforming code from TypeScript to ES2016 to ES2015 to ES5 to ES3), as well as the emit pass (printing out the transformed files themselves). For extremely large codebases that heavily use ECMAScript modules, this can make a significant difference.

If you’re not using --watch mode because you rely on another build tool, the good news is that we intend to provide other tools with an API so that they can also get some of the same performance wins from this change. Tools that plug TypeScript into Webpack, Gulp, and others might be able to leverage this API, and we’re hoping to deliver it in one of our near future releases.

Error suppression comments with // @ts-ignore

Historically, we’ve avoided error suppression within TypeScript because most cases where users have asked for it could be solved through more accurate declaration files or using a type assertion to any. However, over time, we have seen two motivating examples: migrating from JavaScript to TypeScript, and overcoming type-checks that live in legacy code.

With TypeScript 2.6 we’re bringing // @ts-ignore comments to TypeScript files. These comments are a light-weight way to suppress any error that occurs on the next line. For example, in the following code, TypeScript would ordinarily report an error about the console.log statement being unreachable. In TypeScript 2.6, the // @ts-ignore comment squelches the error entirely.

if (false) {
    // @ts-ignore: Unreachable code error
    console.log("hello");
}

While this feature is motivated by pragmatism, we encourage taking type-checking errors seriously. Our guidance here is to use suppression comments very sparingly. In situations where you do need to use these comments, we suggest leaving a trailing explanation of why the comment is necessary like in the example above.

Improved tooling support

Our investment in TypeScript doesn’t only involve advancing the language and compiler. Improving the language service is core to the TypeScript experience. Here’s a few improvements you’ll see soon in editors like Visual Studio and Visual Studio Code.

Quick fixes for implicit anys

TypeScript can now look at use-sites to infer types for declarations whose types are implicitly any.

Refactor JSDoc to TypeScript annotations

TypeScript now provides a refactoring to add parameter annotations from JSDoc comments.

When migrating from an older JavaScript codebase, you can use this refactoring together with the implicit any quick fix to type your codebase even faster.

Invoke uncalled decorators

Occasionally you might try to use a decorator without calling it first. Thankfully, TypeScript can use some basic heuristics to figure these scenarios out, and can provide a useful error message with a handy quick-fix to correct from something like @Input to @Input().

Auto-install from @types

Editors will soon be able to provide a quick fix to install type declarations for untyped imports!

Breaking Changes and Deprecations

There are several minor changes that may impact your codebase. While you can read up more about them in our Breaking Changes section, please keep these in mind when upgrading.

  • Write only references are now considered unused under --noUnusedLocals and --noUnusedParameters.
  • In ambient contexts (e.g. declaration files, and declare module blocks), expressions are now disallowed in defaultexports.
  • Uninhabitable types resulting from intersections (number & string, "foo" & 42, etc.) will simplify to never when placed in a union.
  • Various organizational changes have been made to DOM declarations in lib.d.ts.

As for deprecations, the getSymbolDisplayBuilder API is now deprecated. For simple use cases, you should be able to move to TypeChecker#symbolToString. For more advanced use cases, we plan to bring symbolToString fully up to par with SymbolDisplayBuilder in functionality by TypeScript 2.7. In a release following TypeScript 2.7, we will be be removing getSymbolDisplayBuilder.

What’s next?

To get a more complete picture of what’s in TypeScript 2.6, you can check out our What’s New in TypeScript wiki page. You can also see what we’ve got coming up in our release schedule on the TypeScript Roadmap.

It should go without saying, but let us know if you run into any problems on our issue tracker. And if you’re enjoying this release, let us know on Twitter by using the #iHeartTypeScript hashtag.

We hope that this version of TypeScript is easy to adopt, brings even more type safety, makes you more productive, and is just plain fun to use.

Happy Hacking!

0 comments

Discussion is closed.

Feedback usabilla icon