Skip to content
This repository has been archived by the owner on May 25, 2023. It is now read-only.

Latest commit

 

History

History
1056 lines (772 loc) · 45 KB

ch04.md

File metadata and controls

1056 lines (772 loc) · 45 KB

4 Expressions

This chapter describes the manner in which TypeScript provides type inference and type checking for JavaScript expressions. TypeScript's type analysis occurs entirely at compile-time and adds no run-time overhead to expression evaluation.

TypeScript's typing rules define a type for every expression construct. For example, the type of the literal 123 is the Number primitive type, and the type of the object literal { a: 10, b: "hello" } is { a: number; b: string; }. The sections in this chapter describe these rules in detail.

In addition to type inference and type checking, TypeScript augments JavaScript expressions with the following constructs:

  • Optional parameter and return type annotations in function expressions.
  • Default parameter values and rest parameters in function expressions.
  • Arrow function expressions.
  • Super calls and member access.
  • Type assertions.

Unless otherwise noted in the sections that follow, TypeScript expressions and the JavaScript expressions generated from them are identical.

4.1 Values and References

Expressions are classified as values or references. References are the subset of expressions that are permitted as the target of an assignment. Specifically, references are combinations of identifiers (section 4.3), parentheses (section 4.7), and property accesses (section 4.10). All other expression constructs described in this chapter are classified as values.

4.2 The this Keyword

The type of this in an expression depends on the location in which the reference takes place:

  • In a constructor, instance member function, instance member accessor, or instance member variable initializer, this is of the class instance type of the containing class.
  • In a static member function or static member accessor, the type of this is the constructor function type of the containing class.
  • In a function declaration or a standard function expression, this is of type Any.
  • In the global module, this is of type Any.

In all other contexts it is a compile-time error to reference this.

In the body of an arrow function expression, references to this are rewritten in the generated JavaScript code, as described in section 4.9.2.

4.3 Identifiers

When an expression is an Identifier, the expression refers to the most nested module, class, enum, function, variable, or parameter with that name whose scope (section 2.4) includes the location of the reference. The type of such an expression is the type associated with the referenced entity:

  • For a module, the object type associated with the module instance.
  • For a class, the constructor type associated with the constructor function object.
  • For an enum, the object type associated with the enum object.
  • For a function, the function type associated with the function object.
  • For a variable, the type of the variable.
  • For a parameter, the type of the parameter.

An identifier expression that references a variable or parameter is classified as a reference. An identifier expression that references any other kind of entity is classified as a value (and therefore cannot be the target of an assignment).

4.4 Literals

Literals are typed as follows:

  • The type of the null literal is the Null primitive type.
  • The type of the literals true and false is the Boolean primitive type.
  • The type of numeric literals is the Number primitive type.
  • The type of string literals is the String primitive type.
  • The type of regular expression literals is the global interface type RegExp.

4.5 Object Literals

Object literals are extended to support type annotations in get and set accessors.

PropertyAssignment: ( Modified )
    PropertyName : AssignmentExpression
    PropertyName CallSignature { FunctionBody }
    GetAccessor
    SetAccessor

GetAccessor:
    get PropertyName ( ) TypeAnnotation(opt) { FunctionBody }

SetAccessor:
    set PropertyName ( Identifier TypeAnnotation(opt) ) { FunctionBody }

The type of an object literal is an object type with the set of properties specified by the property assignments in the object literal. A get and set accessor may specify the same property name, but otherwise it is an error to specify multiple property assignments for the same property.

A property assignment of the form

PropertyName CallSignature { FunctionBody }

is simply shorthand for

PropertyName : function CallSignature { FunctionBody }

Each property assignment in an object literal is processed as follows:

  • If the object literal is contextually typed and the contextual type contains a property with a matching name, the property assignment is contextually typed by the type of that property.
  • Otherwise, if the object literal is contextually typed, the contextual type contains a numeric index signature, and the property assignment specifies a numeric property name, the property assignment is contextually typed by the type of the numeric index signature.
  • Otherwise, if the object literal is contextually typed and the contextual type contains a string index signature, the property assignment is contextually typed by the type of the string index signature.
  • Otherwise, the property assignment is processed without a contextual type.

The type of a property introduced by a property assignment of the form Name : Expr is the type of Expr.

A get accessor declaration is processed in the same manner as an ordinary function declaration (section 6.1) with no parameters. A set accessor declaration is processed in the same manner as an ordinary function declaration with a single parameter and a Void return type. When both a get and set accessor is declared for a property:

  • If both accessors include type annotations, the specified types must be identical.
  • If only one accessor includes a type annotation, the other behaves as if it had the same type annotation.
  • If neither accessor includes a type annotation, the inferred return type of the get accessor becomes the parameter type of the set accessor.

If a get accessor is declared for a property, the return type of the get accessor becomes the type of the property. If only a set accessor is declared for a property, the parameter type (which may be type Any if no type annotation is present) of the set accessor becomes the type of the property.

When an object literal is contextually typed by a type that includes a string index signature of type T, the resulting type of the object literal includes a string index signature with the widened form of the best common type of T and the types of the properties declared in the object literal. Likewise, when an object literal is contextually typed by a type that includes a numeric index signature of type T, the resulting type of the object literal includes a numeric index signature with the widened form of the best common type of T and the types of the numerically named properties (section 3.7.4) declared in the object literal.

4.6 Array Literals

In the absence of a contextual type, the type of an array literal is C[], where C is the Undefined type (section 3.2.6) if the array literal is empty, or the best common type of the element expressions if the array literal is not empty.

When an array literal is contextually typed (section 4.19) by an object type containing a numeric index signature of type T, each element expression is contextually typed by T and the type of the array literal is the best common type of T and the types of the element expressions.

4.7 Parentheses

A parenthesized expression

( Expression )

has the same type and classification as the Expression itself. Specifically, if the contained expression is classified as a reference, so is the parenthesized expression.

4.8 The super Keyword

The super keyword can be used in expressions to reference base class properties and the base class constructor.

CallExpression: ( Modified )
    ...
    super ( ArgumentList(opt) )
    super . IdentifierName

4.8.1 Super Calls

Super calls consist of the keyword super followed by an argument list enclosed in parentheses. Super calls are only permitted in constructors of derived classes, as described in section 8.3.2.

A super call invokes the constructor of the base class on the instance referenced by this. A super call is processed as a function call (section 4.12) using the construct signatures of the base class constructor function type as the initial set of candidate signatures for overload resolution. Type arguments cannot be explicitly specified in a super call. If the base class is a generic class, the type arguments used to process a super call are always those specified in the extends clause that references the base class.

The type of a super call expression is Void.

The JavaScript code generated for a super call is specified in section 8.6.2.

4.8.2 Super Property Access

A super property access consists of the keyword super followed by a dot and an identifier. Super property accesses are used to access base class member functions from derived classes and are permitted in

contexts where this (section 4.2) references a derived class instance or a derived class constructor function. Specifically:

  • In a constructor, instance member function, instance member accessor, or instance member variable initializer where this references a derived class instance, a super property access is permitted and must specify a public instance member function of the base class.
  • In a static member function or static member accessor where this references the constructor function object of a derived class, a super property access is permitted and must specify a public static member function of the base class.

Super property accesses are not permitted in other contexts, and it is not possible to access other kinds of base class members in a super property access. Note that super property accesses are not permitted inside standard function expressions nested in the above constructs because this is of type Any in such function expressions.

Super property accesses are typically used to access overridden base class member functions from derived class member functions. For an example of this, see section 8.4.2.

The JavaScript code generated for a super property access is specified in section 8.6.2.

4.9 Function Expressions

Function expressions are extended from JavaScript to optionally include parameter and return type annotations, and a new compact form, called arrow function expressions, is introduced.

FunctionExpression: ( Modified )
    function Identifier(opt) CallSignature { FunctionBody }

AssignmentExpression: ( Modified )
    ...
    ArrowFunctionExpression

ArrowFunctionExpression:
    ArrowFormalParameters => Block
    ArrowFormalParameters => AssignmentExpression

ArrowFormalParameters:
    CallSignature
    Identifier

The terms standard function expression and arrow function expression are used to refer to the FunctionExpression and ArrowFunctionExpression forms respectively. When referring to either, the generic term function expression is used.

The type of a function expression is an object type containing a single call signature with parameter and return types inferred from the function expression's signature and body.

The descriptions of function declarations provided in section 6.1 apply to function expressions as well, except that function expressions do not support overloading.

4.9.1 Standard Function Expressions

Standard function expressions are function expressions written with the function keyword. The type of this in a standard function expression is the Any type.

Standard function expressions are transformed to JavaScript in the same manner as function declarations (see section 6.5).

4.9.2 Arrow Function Expressions

TypeScript supports arrow function expressions, a new feature planned for ECMAScript 6. Arrow function expressions are a compact form of function expressions that omit the function keyword and have lexical scoping of this.

An arrow function expression of the form

ArrowFormalParameters => AssignmentExpression

is exactly equivalent to

ArrowFormalParameters => { return AssignmentExpression ; }

Furthermore, arrow function expressions of the forms

Identifier => Block
Identifier => AssignmentExpression

are exactly equivalent to

( Identifier ) => Block
( Identifier ) => AssignmentExpression

Thus, the following examples are all equivalent:

(x) => { return Math.sin(x); }
(x) => Math.sin(x)
x => { return Math.sin(x); }
x => Math.sin(x)

A function expression using the function keyword introduces a new dynamically bound this, whereas an arrow function expression preserves the this of its enclosing context. Arrow function expressions are particularly useful for writing callbacks, which otherwise often have an undefined or unexpected this.

In the example

class Messenger {
    message = "Hello World";
    start() {
        setTimeout(() => alert(this.message), 3000);
    }
};
var messenger = new Messenger();
messenger.start();

the use of an arrow function expression causes the callback to have the same this as the surrounding start method. Writing the callback as a standard function expression it becomes necessary to manually arrange access to the surrounding this, for example by copying it into a local variable:

class Messenger {
    message = "Hello World";
    start() {
        var _this = this;
        setTimeout(function() { alert(_this.message); }, 3000);
    }
};
var messenger = new Messenger();
messenger.start();

The TypeScript compiler applies this type of transformation to rewrite arrow function expressions into standard function expressions.

A construct of the form

< Identifier > ( ParamList ) => { ... }

could be parsed as an arrow function expression with a type parameter or a type assertion applied to an arrow function with no type parameter. It is resolved as the former, but parentheses can be used to select the latter meaning:

< Identifier > ( ( ParamList ) => { ... } )

4.9.3 Contextually Typed Function Expressions

Function expressions with no type parameters and no parameter or return type annotations (but possibly with optional parameters and default parameter values) are contextually typed in certain circumstances, as described in section 4.19.

When a function expression is contextually typed by a function type T, the function expression is processed as if it had explicitly specified parameter type annotations as they exist in T. Parameters are matched by position and need not have matching names. If the function expression has fewer parameters than T, the additional parameters in T are ignored. If the function expression has more parameters than T, the additional parameters are all considered to have type Any.

Furthermore, when a function expression is contextually typed by a function type T, expressions in contained return statements (section 5.7) are contextually typed by T's return type.

4.10 Property Access

A property access uses either dot notation or bracket notation. A property access expression is always classified as a reference.

A property access uses an object's apparent type (section 3.8.1) to determine its properties. Furthermore, in a property access, an object's apparent type includes the properties that originate in the Object or Function global interface types, as described in section 3.3.

A dot notation property access of the form

ObjExpr . Name

where ObjExpr is an expression and Name is an identifier (including, possibly, a reserved word), is used to access the property with the given name on the given object. A dot notation property access is processed as follows at compile-time:

  • If ObjExpr is of type Any, any Name is permitted and the property access is of type Any.
  • Otherwise, if Name denotes a property member in the apparent type of ObjExpr, the property access is of the type of that property.
  • Otherwise, the property access is invalid and a compile-time error occurs.

A bracket notation property access of the form

ObjExpr [ IndexExpr ]

where ObjExpr and IndexExpr are expressions, is used to access the property with the name computed by the index expression on the given object. A bracket notation property access is processed as follows at compile-time:

  • If IndexExpr is a string literal or a numeric literal and ObjExpr's apparent type has a property with the name given by that literal (converted to its string representation in the case of a numeric literal), the property access is of the type of that property.
  • Otherwise, if ObjExpr's apparent type has a numeric index signature and IndexExpr is of type Any, the Number primitive type, or an enum type, the property access is of the type of that index signature.
  • Otherwise, if ObjExpr's apparent type has a string index signature and IndexExpr is of type Any, the String or Number primitive type, or an enum type, the property access is of the type of that index signature.
  • Otherwise, if IndexExpr is of type Any, the String or Number primitive type, or an enum type, the property access is of type Any.
  • Otherwise, the property access is invalid and a compile-time error occurs.

4.11 The new Operator

A new operation has one of the following forms:

new ConstructExpr

new ConstructExpr ( Args )

where ConstructExpr is an expression and Args is an argument list. The first form is equivalent to supplying an empty argument list. ConstructExpr must be of type Any or of an object type with one or more construct or call signatures. The operation is processed as follows at compile-time:

  • If ConstructExpr is of type Any, Args can be any argument list and the result of the operation is of type Any.
  • If ConstructExpr's apparent type (section 3.8.1) is an object type with one or more construct signatures, the expression is processed in the same manner as a function call, but using the construct signatures as the initial set of candidate signatures for overload resolution. The result type of the function call becomes the result type of the operation.
  • If ConstructExpr's apparent type is an object type with no construct signatures but one or more call signatures, the expression is processed as a function call. A compile-time error occurs if the result of the function call is not Void. The type of the result of the operation is Any.

4.12 Function Calls

Function calls are extended from JavaScript to optionally include type arguments.

Arguments: ( Modified )
    TypeArguments(opt) ( ArgumentList(opt) )

A function call takes one of the forms

FuncExpr ( Args )

FuncExpr < TypeArgs > ( Args )

where FuncExpr is an expression of a function type or of type Any, TypeArgs is a type argument list (section 3.4.2), and Args is an argument list.

If FuncExpr is of type Any, or of an object type that has no call signatures but is a subtype of the Function interface, the call is an untyped function call. In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual types are provided for the argument expressions, and the result is always of type Any.

If FuncExpr's apparent type (section 3.8.1) is a function type, the call is a typed function call. TypeScript employs overload resolution in typed function calls in order to support functions with multiple call signatures. Furthermore, TypeScript may perform type argument inference to automatically determine type arguments in generic function calls.

4.12.1 Overload Resolution

The purpose of overload resolution in a function call is to ensure that at least one signature is applicable, to provide contextual types for the arguments, and to determine the result type of the function call, which could differ between the multiple applicable signatures. Overload resolution has no impact on the run-time behavior of a function call. Since JavaScript doesn't support function overloading, all that matters at run-time is the name of the function.

The compile-time processing of a typed function call consists of the following steps:

  • First, a list of candidate signatures is constructed from the call signatures in the function type in declaration order. For classes and interfaces, inherited signatures are considered to follow explicitly declared signatures in extends clause order.
    • A non-generic signature is a candidate when
      • the function call has no type arguments, and
      • the signature is applicable with respect to the argument list of the function call.
    • A generic signature is a candidate in a function call without type arguments when
      • type inference (section 4.12.2) succeeds in inferring a list of type arguments,
      • the inferred type arguments satisfy their constraints, and
      • once the inferred type arguments are substituted for their associated type parameters, the signature is applicable with respect to the argument list of the function call.
    • A generic signature is a candidate in a function call with type arguments when
      • The signature has the same number of type parameters as were supplied in the type argument list,
      • the type arguments satisfy their constraints, and
      • once the type arguments are substituted for their associated type parameters, the signature is applicable with respect to the argument list of the function call.
  • If the list of candidate signatures is empty, the function call is an error.
  • Otherwise, if the candidate list contains one or more signatures for which the type of each argument expression is a subtype of each corresponding parameter type, the return type of the first of those signatures becomes the return type of the function call.
  • Otherwise, the return type of the first signature in the candidate list becomes the return type of the function call.

A signature is said to be an applicable signature with respect to an argument list when

  • the number of arguments is not less than the number of required parameters,
  • the number of arguments is not greater than the number of parameters, and
  • for each argument expression e and its corresponding parameter P, when e is contextually typed (section 4.19) by the type of P, no errors ensue and the type of e is assignable to (section 3.8.4) the type of P.

4.12.2 Type Argument Inference

Given a signature < T1 , T2 , ... , Tn > ( p1 : P1 , p2 : P2 , ... , pm : Pm ), where each zero or more of the type parameters T, and an argument list ( e1 , e2 , ... , em ), the task of type argument inference is to find a set of type arguments A1...An to substitute for T1...Tn such that the argument list becomes an applicable signature.

The inferred type argument for a particular type parameter is determined from a set of candidate types. Given a type parameter T, let C denote the widened form (section 3.9) of the best common type (section 3.10) of the set of candidate types T. Then,

  • If C satisfies T's constraint, the inferred type argument for T is C.
  • Otherwise, the inferred type argument for T is T's constraint.

In order to compute candidate types, the argument list is processed as follows:

  • Initially all inferred type arguments are considered unfixed with an empty set of candidate types.
  • Proceeding from left to right, each argument expression e is inferentially typed by its corresponding parameter type P, possibly causing some inferred type arguments to become fixed, and candidate type inferences (section 3.8.6) are made for unfixed inferred type arguments from the type computed for e to P.

The process of inferentially typing an expression e by a type T is the same as that of contextually typing e by T, with the following exceptions:

  • Where expressions contained within e would be contextually typed, they are instead inferentially typed.
  • Where a contextual type would be included in a candidate set for a best common type (such as when inferentially typing an object or array literal), an inferential type is not.
  • When a function expression is inferentially typed (section 4.9.3) and a type assigned to a parameter in that expression references type parameters for which inferences are being made, the corresponding inferred type arguments to become fixed and no further candidate inferences are made for them.
  • If e is an expression of a function type that contains exactly one generic call signature and no other members, and T is a function type with exactly one non-generic call signature and no other members, then any inferences made for type parameters referenced by the parameters of T's call signature are fixed, and e's type is changed to a function type with e's call signature instantiated in the context of T's call signature (section 3.8.5).

In the example

function choose<T>(x: T, y: T): T {
    return Math.random() < 0.5 ? x : y;
}

var x = choose("Five", 5);

inferences for T in the call to choose are made as follows: For the first parameter, an inference is made from type string to T. For the second parameter, an inference is made from type number to T. Since the best common type (section 3.10) of string and number is the empty object type, the call to choose is equivalent to

var x = choose<{}>("Five", 5);

and the resulting type of x is therefore the empty object type. Note that had both arguments been of type string or number, x would have been of that type.

In the example

function map<T, U>(a: T[], f: (x: T) => U): U[] {
    var result: U[] = [];
    for (var i = 0; i < a.length; i++) result.push(f(a[i]));
    return result;
}

var names = ["Peter", "Paul", "Mary"];
var lengths = map(names, s => s.length);

inferences for T and U in the call to map are made as follows: For the first parameter, inferences are made from the type string[] (the type of names) to the type T[], inferring string for T. For the second parameter, inferential typing of the arrow expression s => s.length causes T to become fixed such that the inferred type string can be used for the parameter s. The return type of the arrow expression can then be determined, and inferences are made from the type (s: string) => number to the type (x: T) => U, inferring number for U. Thus the call to map is equivalent to

var lengths = map<string, number>(names, s => s.length);

and the resulting type of lengths is therefore number[].

In the example

function zip<S, T, U>(x: S[], y: T[], combine: (x: S) => (y: T) => U): U[] {
    var len = Math.max(x.length, y.length);
    var result: U[] = [];
    for (var i = 0; i < len; i++) result.push(combine(x[i])(y[i]));
    return result;
}

var names = ["Peter", "Paul", "Mary"];
var ages = [7, 9, 12];
var pairs = zip(names, ages, s => n => ({ name: s, age: n }));

inferences for S, T and U in the call to zip are made as follows: Using the first two parameters, inferences of string for S and number for T are made. For the third parameter, inferential typing of the outer arrow expression causes S to become fixed such that the inferred type string can be used for the parameter s. When a function expression is inferentially typed, its return expression(s) are also inferentially typed. Thus, the inner arrow function is inferentially typed, causing T to become fixed such that the inferred type number can be used for the parameter n. The return type of the inner arrow function can then be determined, which in turn determines the return type of the function returned from the outer arrow function, and inferences are made from the type (s: string) => (n: number) => { name: string; age: number } to the type (x: S) => (y: T) => R, inferring { name: string; age: number } for R. Thus the call to zip is equivalent to

var pairs = zip<string, number, { name: string; age: number }>(
    names, ages, s => n => ({ name: s, age: n }));

and the resulting type of pairs is therefore { name: string; age: number }[].

4.12.3 Grammar Ambiguities

The inclusion of type arguments in the Arguments production (section 4.12) gives rise to certain ambiguities in the grammar for expressions. For example, the statement

f(g<A, B>(7));

could be interpreted as a call to f with two arguments, g < A and B > (7). Alternatively, it could be interpreted as a call to f with one argument, which is a call to a generic function g with two type arguments and one regular argument.

The grammar ambiguity is resolved as follows: In a context where one possible interpretation of a sequence of tokens is an Arguments production, if the initial sequence of tokens forms a syntactically correct TypeArguments production and is followed by a ( token, then the sequence of tokens is processed an Arguments production, and any other possible interpretation is discarded. Otherwise, the sequence of tokens is not considered an Arguments production.

This rule means that the call to f above is interpreted as a call with one argument, which is a call to a generic function g with two type arguments and one regular argument. However, the statements

f(g < A, B > 7);

f(g < A, B > +(7));

are both interpreted as calls to f with two arguments.

4.13 Type Assertions

TypeScript extends the JavaScript expression grammar with the ability to assert a type for an expression:

UnaryExpression: ( Modified )
    ...
    < Type > UnaryExpression

A type assertion expression consists of a type enclosed in < and > followed by a unary expression. Type assertion expressions are purely a compile-time construct. Type assertions are not checked at run-time and have no impact on the emitted JavaScript (and therefore no run-time cost). The type and the enclosing < and > are simply removed from the generated code.

In a type assertion expression of the form < T > e, e is contextually typed (section 4.19) by T and the resulting type of e is required to be assignable to T, or T is required to be assignable to the widened form of the resulting type of e, or otherwise a compile-time error occurs. The type of the result is T.

Type assertions check for assignment compatibility in both directions. Thus, type assertions allow type conversions that might be correct, but aren't known to be correct. In the example

class Shape { ... }

class Circle extends Shape { ... }

function createShape(kind: string): Shape {
    if (kind === "circle") return new Circle();
    ...
}

var circle = <Circle> createShape("circle");

the type annotations indicate that the createShape function might return a Circle (because Circle is a subtype of Shape), but isn't known to do so (because its return type is Shape). Therefore, a type assertion is needed to treat the result as a Circle.

As mentioned above, type assertions are not checked at run-time and it is up to the programmer to guard against errors, for example using the instanceof operator:

var shape = createShape(shapeKind);
if (shape instanceof Circle) {
    var circle = <Circle> shape;
    ...
}

4.14 Unary Operators

The subsections that follow specify the compile-time processing rules of the unary operators. In general, if the operand of a unary operator does not meet the stated requirements, a compile-time error occurs and the result of the operation defaults to type Any in further processing.

4.14.1 The ++ and -- operators

These operators, in prefix or postfix form, require their operand to be of type Any, the Number primitive type, or an enum type, and classified as a reference (section 4.1). They produce a result of the Number primitive type.

4.14.2 The +, -, and ~ operators

These operators permit their operand to be of any type and produce a result of the Number primitive type.

The unary + operator can conveniently be used to convert a value of any type to the Number primitive type:

function getValue() { ... }

var n = +getValue();

The example above converts the result of getValue() to a number if it isn't a number already. The type inferred for n is the Number primitive type regardless of the return type of getValue.

4.14.3 The ! operator

The ! operator permits its operand to be of any type and produces a result of the Boolean primitive type.

Two unary ! operators in sequence can conveniently be used to convert a value of any type to the Boolean primitive type:

function getValue() { ... }

var b = !!getValue();

The example above converts the result of getValue() to a Boolean if it isn't a Boolean already. The type inferred for b is the Boolean primitive type regardless of the return type of getValue.

4.14.4 The delete Operator

The delete operator takes an operand of any type and produces a result of the Boolean primitive type.

4.14.5 The void Operator

The void operator takes an operand of any type and produces the value undefined. The type of the result is the Undefined type (3.2.6).

4.14.6 The typeof Operator

The typeof operator takes an operand of any type and produces a value of the String primitive type. In positions where a type is expected, typeof can also be used in a type query (section 3.6.3) to produce the type of an expression.

var x = 5;
var y = typeof x;   // Use in an expression
var z: typeof x;    // Use in a type query

In the example above, x is of type number, y is of type string because when used in an expression, typeof produces a value of type string (in this case the string "number"), and z is of type number because when used in a type query, typeof obtains the type of an expression.

4.15 Binary Operators

The subsections that follow specify the compile-time processing rules of the binary operators. In general, if the operands of a binary operator do not meet the stated requirements, a compile-time error occurs and the result of the operation defaults to type any in further processing. Tables that summarize the compile-time processing rules for operands of the Any type, the Boolean, Number, and String primitive types, and all object types and type parameters (the Object column in the tables) are provided.

4.15.1 The *, /, %, -, <<, >>, >>>, &, ^, and | operators

These operators require their operands to be of type Any, the Number primitive type, or an enum type. Operands of an enum type are treated as having the primitive type Number. If one operand is the null or undefined value, it is treated as having the type of the other operand. The result is always of the Number primitive type.

Any Boolean Number String Object
Any Number Number
Boolean
Number Number Number
String
Object

4.15.2 The + operator

The binary + operator requires both operands to be of the Number primitive type or an enum type, or at least one of the operands to be of type Any or the String primitive type. Operands of an enum type are treated as having the primitive type Number. If one operand is the null or undefined value, it is treated as having the type of the other operand. If both operands are of the Number primitive type, the result is of the Number primitive type. If one or both operands are of the String primitive type, the result is of the String primitive type. Otherwise, the result is of type Any.

Any Boolean Number String Object
Any Any Any Any String Any
Boolean Any String
Number Any Number String
String String String String String String
Object Any

A value of any type can converted to the String primitive type by adding an empty string:

function getValue() { ... }

var s = getValue() + "";

The example above converts the result of getValue() to a string if it isn't a string already. The type inferred for s is the String primitive type regardless of the return type of getValue.

4.15.3 The <, >, <=, >=, ==, !=, ===, and !== operators

These operators require one operand type to be identical to or a subtype of the other operand type. The result is always of the Boolean primitive type.

Any Boolean Number String Object
Any Boolean Boolean Boolean Boolean Boolean
Boolean Boolean Boolean
Number Boolean Boolean
String Boolean Boolean
Object Boolean Boolean

4.15.4 The instanceof operator

The instanceof operator requires the left operand to be of type Any, an object type, or a type parameter type, and the right operand to be of type Any or a subtype of the Function interface type. The result is always of the Boolean primitive type.

Note that object types containing one or more call or construct signatures are automatically subtypes of the Function interface type, as described in section 3.3.

4.15.5 The in operator

The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type, and the right operand to be of type Any, an object type, or a type parameter type. The result is always of the Boolean primitive type.

4.15.6 The && operator

The && operator permits the operands to be of any type and produces a result of the same type as the second operand.

Any Boolean Number String Object
Any Any Boolean Number String Object
Boolean Any Boolean Number String Object
Number Any Boolean Number String Object
String Any Boolean Number String Object
Object Any Boolean Number String Object

4.15.7 The || operator

The || operator permits the operands to be of any type.

If the || expression is contextually typed (section 4.19), the operands are contextually typed by the same type and the result is of the best common type (section 3.10) of the contextual type and the two operand types.

If the || expression is not contextually typed, the right operand is contextually typed by the type of the left operand and the result is of the best common type of the two operand types.

Any Boolean Number String Object
Any Any Any Any Any Any
Boolean Any Boolean {} {} {}
Number Any {} Number {} {}
String Any {} {} String {}
Object Any {} {} {} Object

4.16 The Conditional Operator

In a conditional expression of the form

Cond ? Expr1 : Expr2

the Cond expression may be of any type.

If the conditional expression is contextually typed (section 4.19), Expr1 and Expr2 are contextually typed by the same type and the result is of the best common type (section 3.10) of the contextual type and the types of Expr1 and Expr2. An error occurs if the best common type is not identical to at least one of the three candidate types.

If the conditional expression is not contextually typed, the result is of the best common type of the types of Expr1 and Expr2. An error occurs if the best common type is not identical to at least one of the two candidate types.

4.17 Assignment Operators

An assignment of the form

VarExpr = ValueExpr

requires VarExpr to be classified as a reference (section 4.1). ValueExpr is contextually typed (section 4.19) by the type of VarExpr, and the type of ValueExpr must be assignable to (section 3.8.4) the type of VarExpr, or otherwise a compile-time error occurs. The result is a value with the type of ValueExpr.

A compound assignment of the form

VarExpr Operator= ValueExpr

where Operator= is of the compound assignment operators

*= /= %= += -= <<= >>= >>>= &= ^= |=

is subject to the same requirements, and produces a value of the same type, as the corresponding non- compound operation. A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1) and the type of the non-compound operation to be assignable to the type of VarExpr.

4.18 The Comma Operator

The comma operator permits the operands to be of any type and produces a result that is of the same type as the second operand.

4.19 Contextually Typed Expressions

In certain situations, parameter and return types of function expressions are automatically inferred from the contexts in which the function expressions occur. For example, given the declaration

var f: (s: string) => string;

the assignment

f = function(s) { return s.toLowerCase(); }

infers the type of the s parameter to be the String primitive type even though there is no type annotation to that effect. The function expression is said to be contextually typed by the variable to which it is being assigned. Contextual typing occurs in the following situations:

  • In variable, parameter, and member declarations with a type annotation and an initializer, the initializer expression is contextually typed by the type of the variable, parameter, or property.
  • In return statements, if the containing function includes a return type annotation, return expressions are contextually typed by that return type. Otherwise, if the containing function is contextually typed by a type T, return expressions are contextually typed by T's return type.
  • In typed function calls, argument expressions are contextually typed by their parameter types.
  • In type assertions, the expression is contextually typed by the indicated type.
  • In || operator expressions without a contextual type, the right hand expression is contextually typed by the type of the left hand expression.
  • In assignment expressions, the right hand expression is contextually typed by the type of the left hand expression.
  • In contextually typed object literals, property assignments are contextually typed by their property types.
  • In contextually typed array literals, element expressions are contextually typed by the array element type.
  • In contextually typed || operator expressions, the operands are contextually typed as well.
  • In contextually typed conditional operator expressions, the operands are contextually typed as well.

Contextual typing of an expression e by a type T proceeds as follows:

  • If e is an ObjectLiteral and T is an object type, e is processed with the contextual type T, as described in section 4.5.
  • If e is an ArrayLiteral and T is an object type with a numeric index signature, e is processed with the contextual type T, as described in section 4.6.
  • If e is a FunctionExpression or ArrowFunctionExpression with no type parameters and no parameter or return type annotations, T is a function type with exactly one call signature and T's call signature is non-generic, then any inferences made for type parameters referenced by the parameters of T's call signature are fixed (section 4.12.2) and e is processed with the contextual type T, as described in section 4.9.3.
  • If e is a || operator expression and T is an object type, e is processed with the contextual type T, as described in section 4.15.7.
  • If e is a conditional operator expression and T is an object type, e is processed with the contextual type T, as described in section 4.16.
  • Otherwise, e is processed without a contextual type.

The rules above require expressions be of the exact syntactic forms specified in order to be processed as contextually typed constructs. For example, given the declaration of the variable f above, the assignment

f = s => s.toLowerCase();

causes the function expression to be contextually typed, inferring the String primitive type for s. However, simply enclosing the construct in parentheses

f = (s => s.toLowerCase());

causes the function expression to be processed without a contextual type, now inferring s and the result of the function to be of type Any as no type annotations are present.

In the following example

interface EventObject {
    x: number;
    y: number;
}

interface EventHandlers {
    mousedown?: (event: EventObject) => void;
    mouseup?: (event: EventObject) => void;
    mousemove?: (event: EventObject) => void;
}

function setEventHandlers(handlers: EventHandlers) { ... }

setEventHandlers({
    mousedown: e => { startTracking(e.x, e.y); },
    mouseup: e => { endTracking(); }
});

the object literal passed to setEventHandlers is contextually typed to the EventHandlers type. This causes the two property assignments to be contextually typed to the unnamed function type (event: EventObject) => void, which in turn causes the *e* parameters in the arrow function expressions to automatically be typed as EventObject.