4.15 Function Calls
Function calls are extended from JavaScript to support optional type arguments.
Arguments: ( Modified ) TypeArgumentsopt (
ArgumentListopt )
A function call takes one of the forms
func ( ... )
func < ... > ( ... )
where func is an expression of a function type or of type Any. The function expression is followed by an optional type argument list (section 3.6.2) and an argument list.
If func is of type Any, or of an object type that has no call or construct signatures but is a subtype of the Function interface, the call is an untyped function call. In an untyped function call no type arguments are permitted, argument expressions can be of any type and number, no contextual types are provided for the argument expressions, and the result is always of type Any.
If func has apparent call signatures (section 3.11.1) 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.15.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.
TODO: Describe use of wildcard function types in overload resolution.
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.15.2) succeeds for each type parameter,
- 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.
- A non-generic signature is a candidate when
- 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.23) by the type of P, no errors ensue and the type of e is assignable to (section 3.11.4) the type of P.
TODO: Spread operator in function calls and spreading an iterator into a function call.
4.15.2 Type Argument Inference
Given a signature < T1 , T2 , … , Tn > ( p1 : P1 , p2 : P2 , … , pm : Pm ), where each parameter type P references 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.
TODO: Update type argument inference and overload resolution rules.
Type argument inference produces a set of candidate types for each type parameter. Given a type parameter T and set of candidate types, the actual inferred type argument is determined as follows:
- If the set of candidate argument types is empty, the inferred type argument for T is T‘s constraint.
- Otherwise, if at least one of the candidate types is a supertype of all of the other candidate types, let C denote the widened form (section 3.12) of the first such candidate type. 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.
- Otherwise, if no candidate type is a supertype of all of the other candidate types, type inference has fails and no type argument is inferred for T.
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.11.7) 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.
- When a function expression is inferentially typed (section 4.10) 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.11.6).
An example:
function choose<T>(x: T, y: T): T {
return Math.random() < 0.5 ? x : y;
}
var x = choose(10, 20); // Ok, x of type number
var y = choose("Five", 5); // Error
In the first call to ‘choose’, two inferences are made from ‘number’ to ‘T’, one for each parameter. Thus, ‘number’ is inferred for ‘T’ and the call is equivalent to
var x = choose<number>(10, 20);
In the second call to ‘choose’, an inference is made from type ‘string’ to ‘T’ for the first parameter and an inference is made from type ‘number’ to ‘T’ for the second parameter. Since neither ‘string’ nor ‘number’ is a supertype of the other, type inference fails. That in turn means there are no applicable signatures and the function call is an error.
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.15.3 Grammar Ambiguities
The inclusion of type arguments in the Arguments production (section 4.15) 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.