3.6 Type Parameters

A type parameter represents an actual type that the parameter is bound to in a generic type reference or a generic function call. Type parameters have constraints that establish upper bounds for their actual type arguments.

Since a type parameter represents a multitude of different type arguments, type parameters have certain restrictions compared to other types. In particular, a type parameter cannot be used as a base class or interface.

3.6.1 Type Parameter Lists

Class, interface, type alias, and function declarations may optionally include lists of type parameters enclosed in < and > brackets. Type parameters are also permitted in call signatures of object, function, and constructor type literals.

  TypeParameters:   <TypeParameterList>

  TypeParameterList:   TypeParameter   TypeParameterList,TypeParameter

  TypeParameter:   BindingIdentifierConstraintopt

  Constraint:   extendsType

Type parameter names must be unique. A compile-time error occurs if two or more type parameters in the same TypeParameterList have the same name.

The scope of a type parameter extends over the entire declaration with which the type parameter list is associated, with the exception of static member declarations in classes.

A type parameter may have an associated type parameter constraint that establishes an upper bound for type arguments. Type parameters may be referenced in type parameter constraints within the same type parameter list, including even constraint declarations that occur to the left of the type parameter.

The base constraint of a type parameter T is defined as follows:

  • If T has no declared constraint, T‘s base constraint is the empty object type {}.
  • If T‘s declared constraint is a type parameter, T‘s base constraint is that of the type parameter.
  • Otherwise, T‘s base constraint is T‘s declared constraint.

In the example

  1. interface G<T, U extends V, V extends Function> { }

the base constraint of ‘T’ is the empty object type and the base constraint of ‘U’ and ‘V’ is ‘Function’.

For purposes of determining type relationships (section 3.11), type parameters appear to be subtypes of their base constraint. Likewise, in property accesses (section 4.13), new operations (section 4.14), and function calls (section 4.15), type parameters appear to have the members of their base constraint, but no other members.

It is an error for a type parameter to directly or indirectly be a constraint for itself. For example, both of the following declarations are invalid:

  1. interface A<T extends T> { }
  2. interface B<T extends U, U extends T> { }

3.6.2 Type Argument Lists

A type reference (section 3.8.2) to a generic type must include a list of type arguments enclosed in angle brackets and separated by commas. Similarly, a call (section 4.15) to a generic function may explicitly include a type argument list instead of relying on type inference.

  TypeArguments:   <TypeArgumentList>

  TypeArgumentList:   TypeArgument   TypeArgumentList,TypeArgument

  TypeArgument:   Type

Type arguments correspond one-to-one with type parameters of the generic type or function being referenced. A type argument list is required to specify exactly one type argument for each corresponding type parameter, and each type argument for a constrained type parameter is required to satisfy the constraint of that type parameter. A type argument satisfies a type parameter constraint if the type argument is assignable to (section 3.11.4) the constraint type once type arguments are substituted for type parameters.

Given the declaration

  1. interface G<T, U extends Function> { }

a type reference of the form ‘G<A, B>’ places no requirements on ‘A’ but requires ‘B’ to be assignable to ‘Function’.

The process of substituting type arguments for type parameters in a generic type or generic signature is known as instantiating the generic type or signature. Instantiation of a generic type or signature can fail if the supplied type arguments do not satisfy the constraints of their corresponding type parameters.

3.6.3 This-types

Every class and interface has a this-type that represents the actual type of instances of the class or interface within the declaration of the class or interface. The this-type is referenced using the keyword this in a type position. Within instance methods and constructors of a class, the type of the expression this (section 4.2) is the this-type of the class.

Classes and interfaces support inheritance and therefore the instance represented by this in a method isn’t necessarily an instance of the containing class—it may in fact be an instance of a derived class or interface. To model this relationship, the this-type of a class or interface is classified as a type parameter. Unlike other type parameters, it is not possible to explicitly pass a type argument for a this-type. Instead, in a type reference to a class or interface type, the type reference itself is implicitly passed as a type argument for the this-type. For example:

  1. class A {
  2. foo() {
  3. return this;
  4. }
  5. }
  6. class B extends A {
  7. bar() {
  8. return this;
  9. }
  10. }
  11. let b: B;
  12. let x = b.foo().bar(); // Fluent pattern works, type of x is B

In the declaration of b above, the type reference B is itself passed as a type argument for B’s this-type. Thus, the referenced type is an instantiation of class B where all occurrences of the type this are replaced with B, and for that reason the foo method of B actually returns B (as opposed to A).

The this-type of a given class or interface type C implicitly has a constraint consisting of a type reference to C with C‘s own type parameters passed as type arguments and with that type reference passed as the type argument for the this-type.