TypeScript 1.4 sneak peek: union types, type guards, and more

Ryan Cavanaugh

With TypeScript 1.3 out the door, we’re focused on adding more type system and ECMAScript 6 features to TypeScript. Let’s take a quick look at some of the new features you’ll be able to use in the next release of TypeScript. All these features are live in the master branch on our GitHub repository if you’d like to check them out yourself today. 

With these features, we can more accurately and easily work with variables and expressions that may have different types at runtime. Together, these features help reduce the need for explicit type annotations, type assertions, and use of the ‘any’ type. Type definition file (.d.ts) authors can use these to more precisely describe external libraries. For those following the development of the compiler, you’ll notice we’re already using these features in the compiler today.

Union types

Overview

Union types are a powerful way to express a value that can be one of several types. For example, you might have an API for executing a program that takes a command-line as either a string or a string[]. You can now write:

interface RunOptions {
  program: string;
  commandline: string[]|string;
}

Assignment to union types works very intuitively. Anything you could assign to one of the union type’s members is assignable to the union:

var opts: RunOptions = /* … */;
opts.commandline = ‘-hello world’; // OK
opts.commandline = [‘-hello’, ‘world’]; // OK
opts.commandline = [42]; // Error, number is not string or string[]

When reading from a union type, you can see any properties that are shared by them:

if(opts.commandline.length === 0) { // OK, string and string[] both have ‘length’ property
  console.log(“it’s empty”);
}

Using type guards, you can easily work with a variable of a union type:

function formatCommandline(c: string[]|string) {
  if(typeof c === ‘string’) {
    return c.trim();
  } else {
    return c.join(‘ ‘);
  }
}

Type Guards

A common pattern in JavaScript is to use typeof or instanceof to examine the type of an expression at runtime. TypeScript now understands these conditions and will change type inference accordingly when used in an if block.

Using typeof to test a variable:

var x: any = /* … */;
if(typeof x === ‘string’) {
   console.log(x.subtr(1)); // Error, ‘subtr’ does not exist on ‘string’
}
// x is still any here
x.unknown(); // OK

EDIT: The above example does not give the error mentioned in the 1.4 release.  This will likely be improved in future releases.

Using instanceof with classes and union types:

class Dog { woof() { } }
class Cat { meow() { } }
var pet: Dog|Cat = /* … */;
if(pet instanceof Dog) {
   pet.woof(); // OK
} else {
   pet.woof(); // Error
}

Stricter Generics

With union types able to represent a wide range of type scenarios, we’ve decided to improve the strictness of certain generic calls. Previously, code like this would (surprisingly) compile without error:

function equal<T>(lhs: T, rhs: T): boolean {
   return lhs === rhs;
}

// Previously: No error
// New behavior: Error, no best common type between ‘string’ and ‘number’
var e = equal(42, ‘hello’);

Better Type Inference

Union types also allow for better type inference in arrays and other places where you might have multiple kinds of values in a collection:

var x = [1, ‘world’]; // x: Array<string|number>
x[0] = ‘hello’; // OK
x[0] = false; // Error, boolean is not string or number

Type Aliases

You can now define an alias for a type using the type keyword:

type PrimitiveArray = Array<string|number|boolean>;
type MyNumber = number;
type NgScope = ng.IScope;
type Callback = () => void;

Type aliases are exactly the same as their original types; they are simply alternative names.

In an upcoming post, we’ll talk about ECMAScript 6 features that we’re bringing into TypeScript. To learn more, and to try out the features mentioned in this blog post, check out the ‘master’ branch on the TypeScript GitHub site, and let us know what you think.

0 comments

Discussion is closed.

Feedback usabilla icon