6. Functions

Functions, be they function declarations, expressions or even methods, are internally modeled by means of a function type. In this chapter, the general function type is described along with its semantics and type constraints. Function definitions and expressions are then introduced in terms of statements and expressions. Method definitions and special usages are described in Methods.

6.1. Function Type

A function type is modeled as Object (see [ECMA11a(p.S13, p.p.98)] in ECMAScript.

Function types can be defined by means of;

6.1.1. Properties

In any case, a function type declares the signature of a function and allows validation of calls to that function. A function type has the following properties:

typePars

(0-indexed) list of type parameters (i.e. type variables) for generic functions.

fpars

(0-indexed) list of formal parameters.

returnType

(possibly inferred) return type (expression) of the function or method.

name

Name of function or method, may be empty or automatically generated (for messages).

body

The body of the function, it contains statements stmts. The body is null if a function type is defined in a type expression, and it is the last argument in case of a function object constructor, or the content of the function definition body.

Additionally, the following pseudo properties for functions are defined:

thisTypeRef

The this type ref is the type to which the this-keyword would be evaluated if used inside the function or member. The inference rules are described in This Keyword.

fpars

List of formal parameters and the this type ref. This is only used for sub typing rules. If this is not used inside the function, then any is set instead of the inferred thisTypeRef to allow for more usages. The property is computed as follows:

tfpars=if...this is used or explicitly declaredthen...thisTypeRef+fparselse...any+fpars

Parameters (in pars) have the following properties:

name

Name of the parameter.

type

Type (expression) of the parameter. Note that only parameter types can be variadic or optional.

The function definition can be annotated similar to Methods except that the final and abstract modifiers aren’t supported for function declarations. A function declaration is always final and never abstract. Also, a function has no property advice set.

Semantics

Req. IDE-79: Function Type (ver. 1)

Type Given a function type F, the following constraints must be true:

  1. Optional parameters must be defined at the end of the (formal) parameter list. In particular, an optional parameter must not be followed by a non-optional parameter:

    F.fparsi.optionalk>i:¬F.fparsk.optvar
  2. Only the last parameter of a method may be defined as variadic parameter:

    F.fparsi.variadici=|F.fpars|-1
  3. If a function explicitly defines a return type, the last statement of the transitive closure of statements of the body must be a return statement:

    F.typeRefUndefined
    |f.body.stmts|>0
    f.body.stmts|f.body.stmts*|-1*isa ReturnStatement

  4. If a function explicitly defines a return type, all return statements must return a type conform to that type:

    F.typeRefUndefined

    rF.body.stmts,r isa ReturnStatement:
    r.exprnull[[r.expr.typeRef]]<:[[F.typeRef]]

6.1.2. Type Inference

For the given non-parameterized function types Fleft with Fleft.tfpars=L0,L1,...Lk and |Fleft.typesPars|=0
Fright with Fright.tfpars=R0,R1,...Rn and |Fright.typesPars|=0,
we say Fleft conforms to Fright, written as Fleft<:Fright, if and only if:

  • Fright.returnType=void
    Fleft.returnType=voidFright.opt
    (Fleft.returnType<:Fright.returnType¬Fleft.opt¬Fright.opt)

  • if kn:

    •  i,1ik:Ri.optLi.optLi.varRi.varLi.var

    •  i,1ik:Li:>Ri

    • Lk.var=true i,k<in:LK:>Ri

      else (k>n):

    •  i,1in:Ri.optLi.optLi.varRi.varLi.var

    •  i,1in:Li:>Ri

    •  n<ik:Li.optLi.var

    • Rn.var=true i,n<ik:Li:>Rn

Function Variance Chart shows a simple example with the function type conformance relations.

cdVarianceFunctionChart
Figure 6. Function Variance Chart

{function()} <: {function(A)} <: {function(A, A)} might be surprising for Java programmers. However, in JavaScript it is possible to call a function with any number of arguments independently from how many formal parameters the function defines.

If a function does not define a return type, any is assumed if at least one of the (indirectly) contained return statements contains an expression. Otherwise void is assumed. This is also true if there is an error due to other constraint violations.

bindsfFF.returnType=nullrreturnsF:r.expressionnullΓf’(’arglist ’)’:anybindsfFF.returnType=nullrreturnsF:r.expressionnullΓf’(’arglist ’)’:void

with

rF.body.statements|μr=ReturnStatementsF.body.statementsreturnssreturnsF:RETSsubs.statements|μsub=ReturnStatementsubs.statementsreturnssubreturnss:RETS
Example 61. Function type conformance

The following incomplete snippet demonstrates the usage of two function variables f1 and f2, in which [[f2]]<:[[f1]] must hold true according to the aforementioned constraints. A function bar declares a parameter f1, which is actually a function itself. f2 is a variable, to which a function expression is a assigned. Function bar is then called with f2 as an argument. Thus, the type of f2 must be a subtype of the f1’s type.

function bar(f1: {function(A,B):C}) { ... }

var f2: {function(A,B):C} = function(p1,p2){...};
bar(f1);

The type of this can be explicitly set via the @This annotation.

Example 62. Function Subtyping
function f(): A {..}
function p(): void {..}

fAny(log: {function():any}) {...}
fVoid(f: {function():void}) {..}
fA(g: {function():A}) {...}

fAny(f);    // --> ok       A <: any
fVoid(f);   // -->error     A !<: void
fA(f);      // --> ok (easy)    A <: A

fAny(p);    // --> ok       void <: any
fVoid(p);   // --> ok       void <: void
fA(p);      // --> error    void !<: A
Example 63. Subtyping with function types

If classes A, B, and C are defined as previously mentioned, i.e. C<:B<:A, then the following subtyping relations with function types are to be evaluated as follows:

       {function(B):B} <: {function(B):B}           -> true
        {function():A} <: {function():B}            -> false
        {function():C} <: {function():B}            -> true
         {function(A)} <: {function(B)}             -> true
         {function(C)} <: {function(B)}             -> false

     {function():void} <: {function():void}         -> true
{function():undefined} <: {function():void}         -> true
     {function():void} <: {function():undefined}    -> true (!)

        {function():B} <: {function():void}         -> true (!)
        {function():B} <: {function():undefined}    -> false (!)
     {function():void} <: {function():B}            -> false
{function():undefined} <: {function():B}            -> true

The following examples demonstrate the effect of optional and variadic parameters:

{function(A)} <: {function(B)}                      -> true
{function(A...)} <: {function(A)}                   -> true
{function(A, A)} <: {function(A)}                   -> false
{function(A)} <: {function(A,A)}                    -> true (!)
{function(A, A...)} <: {function(A)}                -> true
{function(A)} <: {function(A,A...)}                 -> true (!)
{function(A, A...)} <: {function(B)}                -> true
{function(A?)} <: {function(A?)}                    -> true
{function(A...)} <: {function(A...)}                -> true
{function(A?)} <: {function(A)}                     -> true
{function(A)} <: {function(A?)}                     -> false
{function(A...)} <: {function(A?)}                  -> true
{function(A?)} <: {function(A...)}                  -> true (!)
{function(A,A...)} <: {function(A...)}              -> false
{function(A,A?)} <: {function(A...)}                -> false
{function(A?,A...)} <: {function(A...)}             -> true
{function(A...)} <: {function(A?,A...)}             -> true
{function(A...)} <: {function(A?)}                  -> true
{function(A?,A?)} <: {function(A...)}               -> true (!)
{function(A?,A?,A?)} <: {function(A...)}            -> true (!)
{function(A?)} <: {function()}                      -> true (!)
{function(A...)} <: {function()}                    -> true (!)

The following examples demonstrate the effect of optional return types:

{function():void} <: {function():void}              -> true
{function():X}    <: {function():void}              -> true
{function():X?}   <: {function():void}              -> true
{function():void} <: {function():Y}                 -> false
{function():X}    <: {function():Y}                 -> X <: Y
{function():X?}   <: {function():Y}                 -> false (!)
{function():void} <: {function():Y?}                -> true (!)
{function():X}    <: {function():Y?}                -> X <: Y
{function():X?}   <: {function():Y?}                -> X <: Y
       {function():B?} <: {function():undefined}    -> false (!)
{function():undefined} <: {function():B?}           -> true

The following examples show the effect of the @This annotation:

{@This(A) function():void} <: {@This(X) function():void}    -> false
{@This(B) function():void} <: {@This(A) function():void}    -> false
{@This(A) function():void} <: {@This(B) function():void}    -> true
{@This(any) function():void} <: {@This(X) function():void}  -> true
{function():void} <: {@This(X) function():void}             -> true
{@This(A) function():void} <: {@This(any) function():void}  -> false
{@This(A) function():void} <: {function():void}             -> false

For the given function types
Fleft with Fleft.tfpars=L0,L1,...Lk
Fright with Fright.tfpars=R0,R1,...Rn,
we say Fleft conforms to Fright, written as Fleft<:Fright, if and only if:

  • if |Fleft.typePars|=|Fright.typePars|=0:

  • else if
    |Fleft.typePars|>0|Fright.typePars|=0:

  • else if |Fleft.typePars|=|Fright.typePars|:

    • ΓVirVil|0inFleft<:Fright ( accordingly)

    • -

      0in:intersectionVil.upperBounds:>intersectionVir.upperBounds

      with Fleft.typePars=V0l,V1l,...Vnl and Fright.typePars=V0r,V1r,...Vnr
      (i.e. we replace each type variable in Fright by the corresponding type variable at the same index in Fleft and check the constraints from Function Type Conformance Non-Parameterized as if Fleft and Fright were non-parameterized functions and, in addition, the upper bounds on the left side need to be supertypes of the upper bounds on the right side).

Note that the upper bounds on the left must be supertypes of the right-side upper bounds (for similar reasons why types of formal parameters on the left are required to be supertypes of the formal parameters’ types in ). Where a particular type variable is used, on co- or contra-variant position, is not relevant:

Example 64. Bounded type variable at co-variant position in function type
class A {}
class B extends A {}

class X {
    <T extends B> m(): T { return null; }
}
class Y extends X {
    @Override
    <T extends A> m(): T { return null; }
}

Method m in Y may return an A, thus breaking the contract of m in X, but only if it is parameterized to do so, which is not allowed for clients of X, only those of Y. Therefore, the override in the above example is valid.

The subtype relation for function types is also applied for method overriding to ensure that an overriding method’s signature conforms to that of the overridden method, see [Req-IDE-72] (applies to method comnsumption and implementation accordingly, see [Req-IDE-73] and [Req-IDE-74]). Note that this is very different from Java which is far more restrictive when checking overriding methods. As Java also supports method overloading: given two types A,B with B<:A and a super class method void m(B param), it is valid to override m as void m(A param) in N4JS but not in Java. In Java this would be handled as method overloading and therefore an @Override annotation on m would produce an error.

The upper bound of a function type F is a function type with the lower bound types of the parameters and the upper bound of the return type:
upperfunctionP1...Pn:R:=functionlowerP1...lowerPn:upperR

The lower bound of a function type F is a function type with the upper bound types of the parameters and the lower bound of the return type:
lowerfunctionP1...Pn:R:=functionupperP1...upperPn:lowerR

6.1.3. Autoboxing of Function Type

Function types, compared to other types like String, come only in on flavour: the Function object representation. There is no primitive function type. Nevertheless, for function type expressions and function declarations, it is possible to call the properties of Function object directly. This is similar to autoboxing for strings.

Access of Function properties on functions
// function declaration
var param: number = function(a,b){}.length // 2

function a(x: number) : number { return x*x; }
// function reference
a.length; // 1

// function variable
var f = function(m,l,b){/*...*/};
f.length; // 3

class A {
    s: string;
    sayS(): string{ return this.s; }
}

var objA: A = new A();
objA.s = "A";

var objB = {s:"B"}

// function variable
var m = objA.sayS; // method as function, detached from objA
var mA: {function(any)} = m.bind(objA); // bind to objA
var mB: {function(any)} = m.bind(objB); // bind to objB

m()  // returns: undefined
mA() // returns: A
mB() // returns: B

m.call(objA,1,2,3);  // returns: A
m.apply(objB,[1,2,3]); // returns: B
m.toString(); // returns: function sayS(){ return this.s; }

6.1.4. Arguments Object

A special arguments object is defined within the body of a function. It is accessible through the implicitly-defined local variable named , unless it is shadowed by a local variable, a formal parameter or a function named arguments or in the rare case that the function itself is called ’arguments’ [ECMA11a(p.S10.5, p.p.59)]. The argument object has array-like behavior even though it is not of type array:

  • All actual passed-in parameters of the current execution context can be retrieved by 0-based index access.

  • The length property of the arguments object stores the actual number of passed-in arguments which may differ from the number of formally defined number of parameters fpars of the containing function.

  • It is possible to store custom values in the arguments object, even outside the original index boundaries.

  • All obtained values from the arguments object are of type any.

In non-strict ES mode the callee property holds a reference to the function executed [ECMA11a(p.S10.6, p.p.61)].

Req. IDE-81: Arguments.callee (ver. 1)

In N4JS and in ES strict mode the use of arguments.callee is prohibited.

Req. IDE-82: Arguments as formal parameter name (ver. 1)

In N4JS, the formal parameters of the function cannot be named arguments. This applies to all variable execution environments like field accessors (getter/setter, Field Accessors (Getter/Setter)), methods (Methods) and constructors (Constructor and Classifier Type), where FormalParameter type is used.

// regular function
function a1(s1: string, n2: number) {
    var l: number = arguments.length;
    var s: string = arguments[0] as string;
}

class A {
    // property access
    get s(): string { return ""+arguments.length; } // 0
    set s(n: number) { console.log( arguments.length ); }  // 1
    // method
    m(arg: string) {
        var l: number = arguments.length;
        var s: string = arguments[0]  as string;
    }
}

// property access in object literals
var x = {
    a:5,
    get b(): string {
        return ""+arguments.length
    }
}

// invalid:
function z(){
    arguments.length // illegal, see next lines
    // define arguments to be a plain variable of type number:
    var arguments: number = 4;
}

6.2. ECMAScript 5 Function Definition

6.2.1. Function Declaration

6.2.1.1. Syntax

A function can be defined as described in [ECMA11a(p.S13, p.p.98)] and additional annotations can be specified. Since N4JS is based on [ECMA15a], the syntax contains constructs not available in [ECMA11a]. The newer constructs defined only in [ECMA15a] and proposals already implemented in N4JS are described in ECMAScript 2015 Function Definition and ECMAScript Proposals Function Definition.

In contrast to plain JavaScript, function declarations can be used in blocks in N4JS. This is only true, however, for N4JS files, not for plain JS files.
Syntax Function Declaration and Expression
FunctionDeclaration <Yield>:
    => ({FunctionDeclaration}
        annotations+=Annotation*
        (declaredModifiers+=N4Modifier)*
        -> FunctionImpl <Yield,Yield,Expression=false>
    ) => Semi?
;


fragment AsyncNoTrailingLineBreak *: (declaredAsync?='async' NoLineTerminator)?;

fragment FunctionImpl<Yield, YieldIfGenerator, Expression>*:
    'function'
    (
        generator?='*' FunctionHeader<YieldIfGenerator,Generator=true> FunctionBody<Yield=true,Expression>
    |   FunctionHeader<Yield,Generator=false> FunctionBody<Yield=false,Expression>
    )
;

fragment FunctionHeader<Yield, Generator>*:
    TypeVariables?
    name=BindingIdentifier<Yield>?
    StrictFormalParameters<Yield=Generator>
    (-> ':' returnTypeRef=TypeRef)?
;

fragment FunctionBody <Yield, Expression>*:
        <Expression> body=Block<Yield>
    |   <!Expression> body=Block<Yield>?
;

Properties of the function declaration and expression are described in Function Type.

For this specification, we introduce a supertype FunctionDefinition for both, FunctionDeclaration and FunctionExpression. This supertype contains all common properties of these two subtypes, that is, all properties of FunctionExpression.

Example 65. Function Declaration with Type Annotation
// plain JS
function f(p) { return p.length }
// N4JS
function f(p: string): number { return p.length }
6.2.1.2. Semantics

A function defined in a class’s method (or method modifier) builder is a method, see Methods for details and additional constraints. The metatype of a function definition is function type (Function Type), as a function declaration is only a different syntax for creating a Function object. Constraints for function type are described in Function Type. Another consequence is that the inferred type of a function definition fdecl is simply its function type F.

[[fdecl]][[F]]

Note that the type of a function definition is different from its return type f.decl!

  1. In plain JavaScript, function declarations must only be located on top-level, that is they must not be nested in blocks. Since this is supported by most JavaScript engines, only a warning is issued.

6.2.2. Function Expression

A function expression [ECMA11a(p.S11.2.5)] is quite similar to a function declaration. Thus, most details are explained in ECMAScript 5 Function Definition.

6.2.2.1. Syntax
FunctionExpression:
         ({FunctionExpression}
            FunctionImpl<Yield=false,YieldIfGenerator=true,Expression=true>
         )
;
6.2.2.2. Semantics and Type Inference

In general, the inferred type of a function expression simply is the function type as described in Function Type. Often, the signature of a function expression is not explicitly specified but it can be inferred from the context. The following context information is used to infer the full signature:

  • If the function expression is used on the right hand side of an assignment, the expected return type can be inferred from the left hand side.

  • If the function expression is used as an argument in a call to another function, the full signature can be inferred from the corresponding type of the formal parameter declaration.

Although the signature of the function expression may be inferred from the formal parameter if the function expression is used as argument, this inference has some conceptual limitations. This is demonstrated in the next example.

Example 66. Inference Of Function Expression’s Signature

In general, {function():any} is a subtype of {function():void} (cf. Function Type). When the return type of a function expression is inferred, this relation is taken into account which may lead to unexpected results as shown in the following code snippet:

function f(cb: {function():void}) { cb() }
f(function() { return 1; });

No error is issued: The type of the function expression actually is inferred to {function():any}, because there is a return statement with an expression. It is not inferred to {function():void}, even if the formal parameter of f suggests that. Due to the previously-stated relation {function():any} <: {function():void} this is correct – the client (in this case function f) works perfectly well even if cb returns something. The contract of arguments states that the type of the argument is a subtype of the type of the formal parameter. This is what the inferencer takes into account!

6.3. ECMAScript 2015 Function Definition

6.3.1. Formal Parameters

Parameter handling has been significantly upgraded in ECMAScript 6. It now supports parameter default values, rest parameters (variadics) and destructuring. Formal parameters can be modified to be either default or variadic. In case a formal parameter has no modifier, it is called normal. Modified parameters also become optional.

Modifiers of formal parameters such as default or rest are neither evaluated nor rewritten in the transpiler.

6.3.1.1. Optional Parameters

An optional formal parameter can be omitted when calling a function/method. An omitted parameter has the value undefined. In case the omitted parameter is variadic, the value is an empty array.

Parameters can not be declared as optional explicitly. Instead, being optional is true when a parameter is declared as default or variadic. Note that any formal parameter that follows a default parameter is itself also a default thus an optional parameter.

6.3.1.2. Default Parameters

A default parameter value is specified for a parameter via an equals sign (=). If a caller doesn’t provide a value for the parameter, the default value is used.

Default initializers of parameters are specified at a formal parameter of a function or method after the equal sign using an arbitrary initializer expression, such as var = "s". However, this default initializer can be omitted. When a formal parameter has a declared type, the default initializer is specified at the end, such as: var : string = "s". The initializer expression is only evaluated in case no actual argument is given for the formal parameter. Also, the initializer expression is evaluated when the actual argument value is undefined.

Formal parameters become default parameters implicitly when they are preceded by an explicit default parameter. In such cases, the default initializer is undefined.

Req. IDE-14501: Default parameters (ver. 1)

Any normal parameter which is preceded by a default parameter also becomes a default parameter. Its initializer is undefined.

When a method is overwritten, its default parameters are not part of the overwriting method. Consequently, initializers of default parameters in abstract methods are obsolete.

6.3.1.3. Variadic

Variadic parameters are also called rest parameters. Marking a parameter as variadic indicates that method accepts a variable number of parameters. A variadic parameter implies that the parameter is also optional as the cardinality is defined as 0..*. No further parameter can be defined after a variadic parameter. When no argument is given for a variadic parameter, an empty array is provided when using the parameter in the body of the function or method.

Req. IDE-16: Variadic and optional parameters (ver. 1)

For a parameter p, the following condition must hold: p.varp.opt.

A parameter can not be declared both variadic and with a default value. That is to say that one can either write varName= (default) or ...varName, but not ...varName=.

Declaring a variadic parameter of type T causes the type of the method parameter to become Array<T>. That is, declaring function(…​tags : string) causes tags to be an Array<string> and not just a scalar string value.

To make this work at runtime, the compiler will generate code that constructs the parameter from the arguments parameter explicitly passed to the function.

Req. IDE-17: Variadic at Runtime (ver. 1)

At runtime, a variadic parameter is never set to undefined. Instead, the array may be empty. This must be true even if preceding parameters are optional and no arguments are passed at runtime.

For more constraints on using the variadic modifier, see Function-Object-Type.

6.3.2. Generator Functions

Generators come together with the yield expression and can play three roles: the role of an iterator (data producer), of an observer (data consumer), and a combined role which is called coroutines. When calling a generator function or method, the returned generator object of type Generator<TYield,TReturn,TNext> can be controlled by its methods (cf. [ECMA15a(p.S14.4)], also see [Kuizinas14a]).

6.3.2.1. Syntax

Generator functions and methods differ from ordinary functions and methods only in the additional * symbol before the function or method name. The following syntax rules are extracted from the real syntax rules. They only display parts relevant to declaring a function or method as a generator.

GeneratorFunctionDeclaration <Yield>:
        (declaredModifiers+=N4Modifier)*
        'function' generator?='*'
        FunctionHeader<YieldIfGenerator,Generator=true>
        FunctionBody<Yield=true,Expression=false>
;

GeneratorFunctionExpression:
        'function' generator?='*'
        FunctionHeader<YieldIfGenerator,Generator=true>
        FunctionBody<Yield=true,Expression=true>
;

GeneratorMethodDeclaration:
    annotations+=Annotation+ (declaredModifiers+=N4Modifier)* TypeVariables?
    generator?='*' NoLineTerminator LiteralOrComputedPropertyName<Yield>
    MethodParamsReturnAndBody<Generator=true>
6.3.2.2. Semantics

The basic idea is to make code dealing with Generators easier to write and more readable without changing their functionality. Take this example:

Example 67. Two simple generator functions
// explicit form of the return type
function * countTo(iMax:int) : Generator<int,string,undefined> {
	for (int i=0; i<=iMax; i++)
		yield i;
	return "finished";
}
val genObj1 = countTo(3);
val values1 = [...genObj1]; // is [0,1,2,3]
val lastObj1 = genObj1.next(); // is {value="finished",done=true}

// shorthand form of the return type
function * countFrom(start:int) : int {
	for (int i=start; i>=0; i--)
		yield i;
	return finished;
}
val genObj2 = countFrom(3);
val values2 = [...genObj2]; // is [3,2,1,0]
val lastObj2 = genObj2.next(); // is {value="finished",done=true}

In the example above, two generator functions are declared. The first declares its return type explicitly whereas the second uses a shorthand form.

Generator functions and methods return objects of the type Generator<TYield,TReturn,TNext> which is a subtype of the Iterable<TYield> and Iterator<TYield> interfaces. Moreover, it provides the methods throw(exception:any) and return(value:TNext?) for advanced control of the generator object. The complete interface of the generator class is given below.

The generator class
public providedByRuntime interface Generator<out TYield, out TReturn, in TNext>
	extends Iterable<TYield>, Iterator<TYield> {
	public abstract next(value: TNext?): IteratorEntry<TYield>
	public abstract [Symbol.iterator](): Generator<TYield, TReturn, TNext>
	public abstract throw(exception: any): IteratorEntry<TYield>;
	public abstract return(value: TNext?): IteratorEntry<TReturn>;
}

Req. IDE-14370: Modifier * (ver. 1)

  1. * may be used on declared functions and methods, and for function expressions.

  2. A function or method f with a declared return type R that is declared * has an actual return type of Generator<TYield,TReturn,TNext>.

  3. A generator function or method can have no declared return type, a shorthand form of a return type or an explicitly declared return type.

    1. The explicitly declared return type is of the form Generator<TYield,TReturn,TNext> with the type variables:

      1. TYield as the expected type of the yield expression argument,

      2. TReturn as the expected type of the return expression, and

      3. TNext as both the return type of the yield expression.

    2. The shorthand form only declares the type of TYield which implicitly translates to Generator<TYield,TReturn,any> as the return type.

      1. The type TReturn is inferred to either undefined or any from the body.

      2. In case the declared type is void, actual return type evaluates to Generator<undefined,undefined,any>.

    3. If no return type is declared, both TYield and TReturn are inferred from the body to either any or undefined. TNext is any.

  4. Given a generator function or method f with an actual return type Generator<TYield,TReturn,TNext>:

    1. all yield statements in f must have an expression of type TYield.

    2. all return statements in f must have an expression of type TReturn.

  5. Return statements in generator functions or methods are always optional.

Req. IDE-14371: Modifier yield and yield* (ver. 1)

  1. yield and yield* may only be in body of generator functions or methods.

  2. yield expr takes only expressions expr of type TYield in a generator function or methods with the actual type Generator<TYield,TReturn,TNext>.

  3. The return type of the yield expression is TNext.

  4. yield* fg() takes only iterators of type Iterator<TYield>, and generator functions or methods fg with the actual return type Generator<? extends TYield,? extends TReturn,? super TNext>.

  5. The return type of the yield* expression is any, since a custom iterator could return an entry {done=true,value} and any value for the variable value.

Similar to async functions, shorthand and explicit form * function():int{}; and * function():Generator<int,TResult,any> are equal, given that the inferred TResult of the former functions equals to TResult in the latter function). In other words, the return type of generator functions or methods is wrapped when it is not explicitly defined as Generator already. Thus, whenever a nested generator type is desired, it has to be defined explicitly. Consider the example below.

Type variables with async methods.
class C<T> {
	genFoo(): T{} // equals to genFoo(): Generator<T, undefined, any>;
				// note that TResult depends on the body of genFoo()
}
function fn(C<int> c1, C<Generator<int,any,any>> c2) {
	c1.genFoo();  // returns Generator<int, undefined, any>
	c2.genFoo();  // returns Generator<Generator<int,any,any>, undefined, any>
}
6.3.2.3. Generator Arrow Functions

As of now, generator arrow functions are not supported by EcmaScript 6 and also, the support is not planned. However, introducing generator arrow function in EcmaScript is still under discussion. For more information, please refer to ESDiscuss.org and StackOverflow.com.

6.3.3. Arrow Function Expression

This is an ECMAScript 6 expression (see [ECMA15a(p.S14.2)]) for simplifying the definition of anonymous function expressions, a.k.a. lambdas or closures. The ECMAScript Specification calls this a function definition even though they may only appear in the context of expressions.

Along with Assignments, Arrow function expressions have the least precedence, e.g. they serve as the entry point for the expression tree.

Arrow function expressions can be considered syntactic window-dressing for old-school function expressions and therefore do not support the benefits regarding parameter annotations although parameter types may be given explicitly. The return type can be given as type hint if desired, but this is not mandatory (if left out, the return type is inferred). The notation @=> stands for an async arrow function (Asynchronous Arrow Functions).

6.3.3.1. Syntax

The simplified syntax reads like this:

ArrowExpression returns ArrowFunction:
    =>(
        {ArrowFunction}
        (
            '('
                ( fpars+=FormalParameterNoAnnotations ( ',' fpars+=FormalParameterNoAnnotations )* )?
            ')'
            (':' returnTypeRef=TypeRef)?
        |   fpars+=FormalParameterNoType
        )
        '=>'
    ) (
        (=> hasBracesAroundBody?='{' body=BlockMinusBraces '}') | body=ExpressionDisguisedAsBlock
    )
;

FormalParameterNoAnnotations returns FormalParameter:
    (declaredTypeRef=TypeRef variadic?='...'?)? name=JSIdentifier
;
FormalParameterNoType returns FormalParameter: name=JSIdentifier;

BlockMinusBraces returns Block: {Block} statements+=Statement*;

ExpressionDisguisedAsBlock returns Block:
    {Block} statements+=AssignmentExpressionStatement
;

AssignmentExpressionStatement returns ExpressionStatement: expression=AssignmentExpression;
6.3.3.2. Semantics and Type Inference

Generally speaking, the semantics are very similar to the function expressions but the devil’s in the details:

  • arguments: Unlike normal function expressions, an arrow function does not introduce an implicit arguments variable (Arguments Object), therefore any occurrence of it in the arrow function’s body has always the same binding as an occurrence of arguments in the lexical context enclosing the arrow function.

  • this: An arrow function does not introduce a binding of its own for the this keyword. That explains why uses in the body of arrow function have the same meaning as occurrences in the enclosing lexical scope. As a consequence, an arrow function at the top level has both usages of arguments and this flagged as error (the outer lexical context doesn’t provide definitionsfor them).

  • super: As with function expressions in general, whether of the arrow variety or not, the usage of super isn’t allowed in the body of arrow functions.

In N4JS, a top-level arrow function can’t refer to this as there’s no outer lexical context that provides a binding for it.

In N4JS, a top-level arrow function can’t include usages of arguments in its body, again because of the missing binding for it.

6.4. ECMAScript Proposals Function Definition

6.4.1. Asynchronous Functions

To improve language-level support for asynchronous code, there exists an ECMAScript proposal [45] based on Promises which are provided by ES6 as built-in types. N4JS implements this proposal. This concept is supported for declared functions and methods (Asynchronous Methods) as well as for function expressions and arrow functions (Asynchronous Arrow Functions).

6.4.1.1. Syntax

The following syntax rules are extracted from the real syntax rules. They only display parts relevant to declaring a function or method as asynchronous.

AsyncFunctionDeclaration <Yield>:
        (declaredModifiers+=N4Modifier)*
        declaredAsync?='async' NoLineTerminator 'function'
        FunctionHeader<Yield,Generator=false>
        FunctionBody<Yield=false,Expression=false>
;

AsyncFunctionExpression:
        declaredAsync?='async' NoLineTerminator 'function'
        FunctionHeader<Yield=false,Generator=false>
        FunctionBody<Yield=false,Expression=true>
;

AsyncArrowExpression <In, Yield>:
        declaredAsync?='async' NoLineTerminator '('
            (fpars+=FormalParameter<Yield>
                (',' fpars+=FormalParameter<Yield>)*)?
        ')' (':' returnTypeRef=TypeRef)? '=>'
        (   '{' body=BlockMinusBraces<Yield> '}'
            | body=ExpressionDisguisedAsBlock<In>
        )
;

AsyncMethodDeclaration:
    annotations+=Annotation+ (declaredModifiers+=N4Modifier)* TypeVariables?
    declaredAsync?='async' NoLineTerminator LiteralOrComputedPropertyName<Yield>
    MethodParamsReturnAndBody

’async’ is not a reserved word in ECMAScript and it can therefore be used either as an identifier or as a keyword, depending on the context. When used as a modifier to declare a function as asynchronous, then there must be no line terminator after the async modifier. This enables the parser to distinguish between using async as an identifier reference and a keyword, as shown in the next example.

Example 68. Async as keyword and identifier
async (1)
function foo() {}
// vs
async function bar(); (2)
1 In this snippet, the async on line 1 is an identifier reference (referencing a variable or parameter) and the function defined on line 2 is a non-asynchronous function. The automatic semicolon insertion adds a semicolon after the reference on line 1.
2 In contrast, async on line 4 is recognized as a modifier declaring the function as asynchronous.
6.4.1.2. Semantics

The basic idea is to make code dealing with Promises easier to write and more readable without changing the functionality of Promises. Take this example:

A simple asynchronous function using async/await.
// some asynchronous legacy API using promises
interface DB {}
interface DBAccess {
    getDataBase(): Promise<DB,?>
    loadEntry(db: DB, id: string): Promise<string,?>
}

var access: DBAccess;

// our own function using async/await
async function loadAddress(id: string) : string {
    try {
        var db: DB = await access.getDataBase();
        var entry: string = await access.loadEntry(db, id);
        return entry.address;
    }
    catch(err) {
        // either getDataBase() or loadEntry() failed
        throw err;
    }
}

The modifier async changes the return type of loadAddress() from string (the declared return type) to Promise<string,?> (the actual return type). For code inside the function, the return type is still string: the value in the return statement of the last line will be wrapped in a Promise. For client code outside the function and in case of recursive invocations, the return type is Promise<string,?>. To raise an error, simply throw an exception, its value will become the error value of the returned Promise.

If the expression after an await evaluates to a Promise, execution of the enclosing asynchronous function will be suspended until either a success value is available (which will then make the entire await-expression evaluate to this success value and continue execution) or until the Promise is rejected (which will then cause an exception to be thrown at the location of the await-expression). If, on the other hand, the expression after an await evaluates to a non-promise, the value will be simply passed through. In addition, a warning is shown to indicate the unnecessary await expression.

Note how method loadAddress() above can be implemented without any explicit references to the built-in type Promise. In the above example we handle the errors of the nested asynchronous calls to getDataBase() and loadEntry() for demonstration purposes only; if we are not interested in the errors we could simply remove the try/catch block and any errors would be forwarded to the caller of loadAddress().

Invoking an async function commonly adopts one of two forms:

  • var p: Promise<successType,?> = asyncFn()

  • await asyncFn()

These patterns are so common that a warning is available whenever both

  1. Promise is omitted as expected type; and

  2. await is also omitted.

The warning aims at hinting about forgetting to wait for the result, while remaining non-noisy.

Req. IDE-86: Modifier async and await (ver. 1)

  1. async may be used on declared functions and methods as well as for function expressions and arrow functions.

  2. A function or method that is declared async can have no declared return type, a shorthand form of a return type or an explicitly declared return type.

    1. The explicitly declared return type is of the form Promise<R,E> where R is the type of all return statements in the body, and E is the type of exceptions that are thrown in the body.

    2. The shorthand form only declares the type of R which implicitly translates to Promise<R,?> as the actual return type.

    3. In case no return type is declared, the type R of Promise<R,?> is inferred from the body.

  3. A function or method f with a declared return type R that is declared async has an actual return type of

    1. R if R is a subtype of Promise<?,?>,

    2. Promise<undefined,?> if R is type void.

    3. Promise<R,?> in all other cases (i.e. the declared return type R is being wrapped in a Promise).

  4. Return type inference is only performed when no return type is declared.

    1. The return type R of Promise<R,?> is inferred either as void or as any.

  5. Given a function or method f that is declared async with a declared return type R, or with a declared return type Promise<R,?>, all return statements in f must have an expression of type R (and not of type Promise<R,?>).

  6. await can be used in expressions directly enclosed in an async function, and behaves like a unary operator with the same precedence as yield in ES6.

  7. Given an expression expr of type T, the type of (await expr) is inferred to T if T is not a Promise, or it is inferred to S if T is a Promise with a success value of type S, i.e. T <: Promise<S,?> .

In other words, the return type R of async functions and methods will always be wrapped to Promise<R,?> unless R is a Promise already. As a consequence, nested Promises as a return type of a async function or method have to be stated explicitly like Promise<Promise<R,?>,?>.

When a type variable T is used to define the the return type of an async function or method, it will always be wrapped. Consider the example below.

Example 69. Type variables with async methods.
interface I<T> {
	async foo(): T;  // amounts to foo(): Promise<T,?>
}
function snafu(i1: I<int>, i2: I<Promise<int,?>>) {
	i1.foo();  // returns Promise<int,?>
	i2.foo();  // returns Promise<Promise<int,?>,?>
}
6.4.1.3. Asynchronous Arrow Functions

An await expression is allowed in the body of an async arrow function but not in the body of a non-async arrow function. The semantics here are intentional and are in line with similar constraint for function expressions.

6.5. N4JS Extended Function Definition

6.5.1. Generic Functions

A generic function is a function with a list of generic type parameters. These type parameters can be used in the function signature to declare the types of formal parameters and the return type. In addition, the type parameters can be used in the function body, for example when declaring the type of a local variable.

In the following listing, a generic function foo is defined that has two type parameters S and T. Thereby S is used as to declare the parameter type Array<S> and T is used as the return type and to construct the returned value in the function body.

Generic Function Definition
function <S,T> foo(s: Array<S>): T { return new T(s); }

If a generic type parameter is not used as a formal parameter type or the return type, a warning is generated.

6.5.2. Promisifiable Functions

In many existing libraries, which have been developed in pre-ES6-promise-API times, callback methods are used for asynchronous behavior. An asynchronous function follows the following conventions:

'function' name '(' arbitraryParameters ',' callbackFunction ')'

Usually the function returns nothing (void). The callback function usually takes two arguments,in which the first is an error object and the other is the result value of the asynchronous operation. The callback function is called from the asynchronous function, leading to nested function calls (aka ’callback hell’).

In order to simplify usage of this pattern, it is possible to mark such a function or method as @Promisifiable. It is then possible to ’promisify’ an invocation of this function or method, which means no callback function argument has to be provided and a will be returned. The function or method can then be used as if it were declared with async. This is particularly useful in N4JS definition files (.n4jsd) to allow using an existing callback-based API from N4JS code with the more convenient await.

Example 70. Promisifiable

Given a function with an N4JS signature

f(x: int, cb: {function(Error, string)}): void

This method can be annotated with Promisifiable as follows:

@Promisifiable f(x: int, cb: {function(Error, string)}): void

With this annotation, the function can be invoked in four different ways:

f(42, function(err, result1) { /* ... */ });            // traditional
var promise: Promise<string,Error> = @Promisify f(42);  // promise
var result3: string = await @Promisify f(42);           // long
var result4: string = await f(42);                      // short

The first line is only provided for completeness and shows that a promisifiable function can still be used in the ordinary way by providing a callback - no special handling will occur in this case. The second line shows how f can be promisified using the @Promisify annotation - no callback needs to be provided and instead, a Promise will be returned. We can either use this promise directly or immediately await on it, as shown in line 3. The syntax shown in line 4 is merely shorthand for await @Promisify, i.e. the annotation is optional after await.

Req. IDE-87: Promisifiable (ver. 1)

A function or method f can be annotated with @Promisifiable if and only if the following constraints hold:

  1. Last parameter of f is a function (the callback).

  2. The callback has a signature of

    • {function(E, T0, T1, …​, Tn): V}, or

    • {function(T0, T1, …​, Tn): V}

      in which E is type Error or a subtype thereof, T0,...,Tn are arbitrary types except or its subtypes. E, if given, is then the type of the error value, and T0,...,Tn are the types of the success values of the asynchronous operation.
      Since the return value of the synchronous function call is not available when using @Promisify, V is recommended to be void, but it can be any type.

  3. The callback parameter may be optional.[46]

According to [Req-IDE-87], a promisifiable function or method may or may not have a non-void return type, and that only the first parameter of the callback is allowed to be of type Error, all other parameters must be of other types.

A promisifiable function f with one of the two valid signatures given in [Req-IDE-87] can be promisified with Promisify or used with await, if and only if the following constraints hold:

  1. Function f must be annotated with @Promisifiable.

  2. Using @Promisify f() without await returns a promise of type Promise<S,F> where

    • S is IterableN<T0,…​,Tn> if n2, T if n=1, and undefined if n=0.

    • F is E if given, undefined otherwise.

  3. Using await @Promisify f() returns a value of type IterableN<T0,…​,Tn> if n2, of type T if n=1, and of type undefined if n=0.

  4. In case of using an await, the annotation can be omitted.
    I.e., await @Promisify f() is equivalent to await f().

  5. Only call expressions using f as target can be promisified, in other words this is illegal:

    var pf = @Promisify f; // illegal code!

Quick Links