3.11 Type Relationships

Types in TypeScript have identity, subtype, supertype, and assignment compatibility relationships as defined in the following sections.

3.11.1 Apparent Members

The apparent members of a type are the members observed in subtype, supertype, and assignment compatibility relationships, as well as in the type checking of property accesses (section 4.13), new operations (section 4.14), and function calls (section 4.15). The apparent members of a type are determined as follows:

  • The apparent members of the primitive type Number and all enum types are the apparent members of the global interface type ‘Number’.
  • The apparent members of the primitive type Boolean are the apparent members of the global interface type ‘Boolean’.
  • The apparent members of the primitive type String and all string literal types are the apparent members of the global interface type ‘String’.
  • The apparent members of a type parameter are the apparent members of the constraint (section 3.6.1) of that type parameter.
  • The apparent members of an object type T are the combination of the following:
    • The declared and/or inherited members of T.
    • The properties of the global interface type ‘Object’ that aren’t hidden by properties with the same name in T.
    • If T has one or more call or construct signatures, the properties of the global interface type ‘Function’ that aren’t hidden by properties with the same name in T.
  • The apparent members of a union type U are determined as follows:
    • When all constituent types of U have an apparent property named N, U has an apparent property named N of a union type of the respective property types.
    • When all constituent types of U have an apparent call signature with a parameter list P, U has an apparent call signature with the parameter list P and a return type that is a union of the respective return types. The call signatures appear in the same order as in the first constituent type.
    • When all constituent types of U have an apparent construct signature with a parameter list P, U has an apparent construct signature with the parameter list P and a return type that is a union of the respective return types. The construct signatures appear in the same order as in the first constituent type.
    • When all constituent types of U have an apparent string index signature, U has an apparent string index signature of a union type of the respective string index signature types.
    • When all constituent types of U have an apparent numeric index signature, U has an apparent numeric index signature of a union type of the respective numeric index signature types.
  • The apparent members of an intersection type I are determined as follows:
    • When one of more constituent types of I have an apparent property named N, I has an apparent property named N of an intersection type of the respective property types.
    • When one or more constituent types of I have a call signature S, I has the apparent call signature S. The signatures are ordered as a concatenation of the signatures of each constituent type in the order of the constituent types within I.
    • When one or more constituent types of I have a construct signature S, I has the apparent construct signature S. The signatures are ordered as a concatenation of the signatures of each constituent type in the order of the constituent types within I.
    • When one or more constituent types of I have an apparent string index signature, I has an apparent string index signature of an intersection type of the respective string index signature types.
    • When one or more constituent types of I have an apparent numeric index signature, I has an apparent numeric index signature of an intersection type of the respective numeric index signature types.

If a type is not one of the above, it is considered to have no apparent members.

In effect, a type’s apparent members make it a subtype of the ‘Object’ or ‘Function’ interface unless the type defines members that are incompatible with those of the ‘Object’ or ‘Function’ interface—which, for example, occurs if the type defines a property with the same name as a property in the ‘Object’ or ‘Function’ interface but with a type that isn’t a subtype of that in the ‘Object’ or ‘Function’ interface.

Some examples:

  1. var o: Object = { x: 10, y: 20 }; // Ok
  2. var f: Function = (x: number) => x * x; // Ok
  3. var err: Object = { toString: 0 }; // Error

The last assignment is an error because the object literal has a ‘toString’ method that isn’t compatible with that of ‘Object’.

3.11.2 Type and Member Identity

Two types are considered identical when

  • they are both the Any type,
  • they are the same primitive type,
  • they are the same type parameter,
  • they are union types with identical sets of constituent types, or
  • they are intersection types with identical sets of constituent types, or
  • they are object types with identical sets of members.

Two members are considered identical when

  • they are public properties with identical names, optionality, and types,
  • they are private or protected properties originating in the same declaration and having identical types,
  • they are identical call signatures,
  • they are identical construct signatures, or
  • they are index signatures of identical kind with identical types.

Two call or construct signatures are considered identical when they have the same number of type parameters with identical type parameter constraints and, after substituting type Any for the type parameters introduced by the signatures, identical number of parameters with identical kind (required, optional or rest) and types, and identical return types.

Note that, except for primitive types and classes with private or protected members, it is structure, not naming, of types that determines identity. Also, note that parameter names are not significant when determining identity of signatures.

Private and protected properties match only if they originate in the same declaration and have identical types. Two distinct types might contain properties that originate in the same declaration if the types are separate parameterized references to the same generic class. In the example

  1. class C<T> { private x: T; }
  2. interface X { f(): string; }
  3. interface Y { f(): string; }
  4. var a: C<X>;
  5. var b: C<Y>;

the variables ‘a’ and ‘b’ are of identical types because the two type references to ‘C’ create types with a private member ‘x’ that originates in the same declaration, and because the two private ‘x’ members have types with identical sets of members once the type arguments ‘X’ and ‘Y’ are substituted.

3.11.3 Subtypes and Supertypes

S is a subtype of a type T, and T is a supertype of S, if S has no excess properties with respect to T (3.11.5) and one of the following is true:

  • S and T are identical types.
  • T is the Any type.
  • S is the Undefined type.
  • S is the Null type and T is not the Undefined type.
  • S is an enum type and T is the primitive type Number.
  • S is a string literal type and T is the primitive type String.
  • S is a union type and each constituent type of S is a subtype of T.
  • S is an intersection type and at least one constituent type of S is a subtype of T.
  • T is a union type and S is a subtype of at least one constituent type of T.
  • T is an intersection type and S is a subtype of each constituent type of T.
  • S is a type parameter and the constraint of S is a subtype of T.
  • S is an object type, an intersection type, an enum type, or the Number, Boolean, or String primitive type, T is an object type, and for each member M in T, one of the following is true:
    • M is a property and S has an apparent property N where
      • M and N have the same name,
      • the type of N is a subtype of that of M,
      • if M is a required property, N is also a required property, and
      • M and N are both public, M and N are both private and originate in the same declaration, M and N are both protected and originate in the same declaration, or M is protected and N is declared in a class derived from the class in which M is declared.
    • M is a non-specialized call or construct signature and S has an apparent call or construct signature N where, when M and N are instantiated using type Any as the type argument for all type parameters declared by M and N (if any),
      • the signatures are of the same kind (call or construct),
      • M has a rest parameter or the number of non-optional parameters in N is less than or equal to the total number of parameters in M,
      • for parameter positions that are present in both signatures, each parameter type in N is a subtype or supertype of the corresponding parameter type in M, and
      • the result type of M is Void, or the result type of N is a subtype of that of M.
    • M is a string index signature of type U, and U is the Any type or S has an apparent string index signature of a type that is a subtype of U.
    • M is a numeric index signature of type U, and U is the Any type or S has an apparent string or numeric index signature of a type that is a subtype of U.

When comparing call or construct signatures, parameter names are ignored and rest parameters correspond to an unbounded expansion of optional parameters of the rest parameter element type.

Note that specialized call and construct signatures (section 3.9.2.4) are not significant when determining subtype and supertype relationships.

Also note that type parameters are not considered object types. Thus, the only subtypes of a type parameter T are T itself and other type parameters that are directly or indirectly constrained to T.

3.11.4 Assignment Compatibility

Types are required to be assignment compatible in certain circumstances, such as expression and variable types in assignment statements and argument and parameter types in function calls.

S is assignable to a type T, and T is assignable from S, if S has no excess properties with respect to T (3.11.5) and one of the following is true:

  • S and T are identical types.
  • S or T is the Any type.
  • S is the Undefined type.
  • S is the Null type and T is not the Undefined type.
  • S or T is an enum type and the other is the primitive type Number.
  • S is a string literal type and T is the primitive type String.
  • S is a union type and each constituent type of S is assignable to T.
  • S is an intersection type and at least one constituent type of S is assignable to T.
  • T is a union type and S is assignable to at least one constituent type of T.
  • T is an intersection type and S is assignable to each constituent type of T.
  • S is a type parameter and the constraint of S is assignable to T.
  • S is an object type, an intersection type, an enum type, or the Number, Boolean, or String primitive type, T is an object type, and for each member M in T, one of the following is true:
    • M is a property and S has an apparent property N where
      • M and N have the same name,
      • the type of N is assignable to that of M,
      • if M is a required property, N is also a required property, and
      • M and N are both public, M and N are both private and originate in the same declaration, M and N are both protected and originate in the same declaration, or M is protected and N is declared in a class derived from the class in which M is declared.
    • M is an optional property and S has no apparent property of the same name as M.
    • M is a non-specialized call or construct signature and S has an apparent call or construct signature N where, when M and N are instantiated using type Any as the type argument for all type parameters declared by M and N (if any),
      • the signatures are of the same kind (call or construct),
      • M has a rest parameter or the number of non-optional parameters in N is less than or equal to the total number of parameters in M,
      • for parameter positions that are present in both signatures, each parameter type in N is assignable to or from the corresponding parameter type in M, and
      • the result type of M is Void, or the result type of N is assignable to that of M.
    • M is a string index signature of type U, and U is the Any type or S has an apparent string index signature of a type that is assignable to U.
    • M is a numeric index signature of type U, and U is the Any type or S has an apparent string or numeric index signature of a type that is assignable to U.

When comparing call or construct signatures, parameter names are ignored and rest parameters correspond to an unbounded expansion of optional parameters of the rest parameter element type.

Note that specialized call and construct signatures (section 3.9.2.4) are not significant when determining assignment compatibility.

The assignment compatibility and subtyping rules differ only in that

  • the Any type is assignable to, but not a subtype of, all types,
  • the primitive type Number is assignable to, but not a subtype of, all enum types, and
  • an object type without a particular property is assignable to an object type in which that property is optional.

The assignment compatibility rules imply that, when assigning values or passing parameters, optional properties must either be present and of a compatible type, or not be present at all. For example:

  1. function foo(x: { id: number; name?: string; }) { }
  2. foo({ id: 1234 }); // Ok
  3. foo({ id: 1234, name: "hello" }); // Ok
  4. foo({ id: 1234, name: false }); // Error, name of wrong type
  5. foo({ name: "hello" }); // Error, id required but missing

3.11.5 Excess Properties

The subtype and assignment compatibility relationships require that source types have no excess properties with respect to their target types. The purpose of this check is to detect excess or misspelled properties in object literals.

A source type S is considered to have excess properties with respect to a target type T if

  • S is a fresh object literal type, as defined below, and
  • S has one or more properties that aren’t expected in T.

A property P is said to be expected in a type T if one of the following is true:

  • T is not an object, union, or intersection type.
  • T is an object type and
    • T has a property with the same name as P,
    • T has a string or numeric index signature,
    • T has no properties, or
    • T is the global type ‘Object’.
  • T is a union or intersection type and P is expected in at least one of the constituent types of T.

The type inferred for an object literal (as described in section 4.5) is considered a fresh object literal type. The freshness disappears when an object literal type is widened (3.12) or is the type of the expression in a type assertion (4.16).

Consider the following example:

  1. interface CompilerOptions {
  2. strict?: boolean;
  3. sourcePath?: string;
  4. targetPath?: string;
  5. }
  6. var options: CompilerOptions = {
  7. strict: true,
  8. sourcepath: "./src", // Error, excess or misspelled property
  9. targetpath: "./bin" // Error, excess or misspelled property
  10. };

The ‘CompilerOptions’ type contains only optional properties, so without the excess property check, any object literal would be assignable to the ‘options’ variable (because a misspelled property would just be considered an excess property of a different name).

In cases where excess properties are expected, an index signature can be added to the target type as an indicator of intent:

  1. interface InputElement {
  2. name: string;
  3. visible?: boolean;
  4. [x: string]: any; // Allow additional properties of any type
  5. }
  6. var address: InputElement = {
  7. name: "Address",
  8. visible: true,
  9. help: "Enter address here", // Allowed because of index signature
  10. shortcut: "Alt-A" // Allowed because of index signature
  11. };

3.11.6 Contextual Signature Instantiation

During type argument inference in a function call (section 4.15.2) it is in certain circumstances necessary to instantiate a generic call signature of an argument expression in the context of a non-generic call signature of a parameter such that further inferences can be made. A generic call signature A is instantiated in the context of non-generic call signature B as follows:

  • Using the process described in 3.11.7, inferences for A‘s type parameters are made from each parameter type in B to the corresponding parameter type in A for those parameter positions that are present in both signatures, where rest parameters correspond to an unbounded expansion of optional parameters of the rest parameter element type.
  • The inferred type argument for each type parameter is the union type of the set of inferences made for that type parameter. However, if the union type does not satisfy the constraint of the type parameter, the inferred type argument is instead the constraint.

3.11.7 Type Inference

In certain contexts, inferences for a given set of type parameters are made from a type S, in which those type parameters do not occur, to another type T, in which those type parameters do occur. Inferences consist of a set of candidate type arguments collected for each of the type parameters. The inference process recursively relates S and T to gather as many inferences as possible:

  • If T is one of the type parameters for which inferences are being made, S is added to the set of inferences for that type parameter.
  • Otherwise, if S and T are references to the same generic type, inferences are made from each type argument in S to each corresponding type argument in T.
  • Otherwise, if S and T are tuple types with the same number of elements, inferences are made from each element type in S to each corresponding element type in T.
  • Otherwise, if T is a union or intersection type:
    • First, inferences are made from S to each constituent type in T that isn’t simply one of the type parameters for which inferences are being made.
    • If the first step produced no inferences then if T is a union type and exactly one constituent type in T is simply a type parameter for which inferences are being made, inferences are made from S to that type parameter.
  • Otherwise, if S is a union or intersection type, inferences are made from each constituent type in S to T.
  • Otherwise, if S and T are object types, then for each member M in T:
    • If M is a property and S contains a property N with the same name as M, inferences are made from the type of N to the type of M.
    • If M is a call signature and a corresponding call signature N exists in S, N is instantiated with the Any type as an argument for each type parameter (if any) and inferences are made from parameter types in N to the corresponding parameter types in M for positions that are present in both signatures, and from the return type of N to the return type of M.
    • If M is a construct signature and a corresponding construct signature N exists in S, N is instantiated with the Any type as an argument for each type parameter (if any) and inferences are made from parameter types in N to the corresponding parameter types in M for positions that are present in both signatures, and from the return type of N to the return type of M.
    • If M is a string index signature and S contains a string index signature N, inferences are made from the type of N to the type of M.
    • If M is a numeric index signature and S contains a numeric index signature N, inferences are made from the type of N to the type of M.
    • If M is a numeric index signature and S contains a string index signature N, inferences are made from the type of N to the type of M.

When comparing call or construct signatures, signatures in S correspond to signatures of the same kind in T pairwise in declaration order. If S and T have different numbers of a given kind of signature, the excess first signatures in declaration order of the longer list are ignored.

TODO: Update to reflect improved union and intersection type inference.

3.11.8 Recursive Types

Classes and interfaces can reference themselves in their internal structure, in effect creating recursive types with infinite nesting. For example, the type

  1. interface A { next: A; }

contains an infinitely nested sequence of ‘next’ properties. Types such as this are perfectly valid but require special treatment when determining type relationships. Specifically, when comparing types S and T for a given relationship (identity, subtype, or assignability), the relationship in question is assumed to be true for every directly or indirectly nested occurrence of the same S and the same T (where same means originating in the same declaration and, if applicable, having identical type arguments). For example, consider the identity relationship between ‘A’ above and ‘B’ below:

  1. interface B { next: C; }
  2. interface C { next: D; }
  3. interface D { next: B; }

To determine whether ‘A’ and ‘B’ are identical, first the ‘next’ properties of type ‘A’ and ‘C’ are compared. That leads to comparing the ‘next’ properties of type ‘A’ and ‘D’, which leads to comparing the ‘next’ properties of type ‘A’ and ‘B’. Since ‘A’ and ‘B’ are already being compared this relationship is by definition true. That in turn causes the other comparisons to be true, and therefore the final result is true.

When this same technique is used to compare generic type references, two type references are considered the same when they originate in the same declaration and have identical type arguments.

In certain circumstances, generic types that directly or indirectly reference themselves in a recursive fashion can lead to infinite series of distinct instantiations. For example, in the type

  1. interface List<T> {
  2. data: T;
  3. next: List<T>;
  4. owner: List<List<T>>;
  5. }

‘List<T>’ has a member ‘owner’ of type ‘List<List<T>>’, which has a member ‘owner’ of type ‘List<List<List<T>>>’, which has a member ‘owner’ of type ‘List<List<List<List<T>>>>’ and so on, ad infinitum. Since type relationships are determined structurally, possibly exploring the constituent types to their full depth, in order to determine type relationships involving infinitely expanding generic types it may be necessary for the compiler to terminate the recursion at some point with the assumption that no further exploration will change the outcome.