8. Expressions

For all expressions, we define the following pseudo properties:

containingExpression

The parent expression, in which an expression is contained, may be null.

containingStatement

The statement in which the expression is (indirectly) contained.

containingFunctionOrAccessor

The function, method, getter or setter in which the expression is (indirectly) contained, may be null.

containingClass

The class in which the expression is (indirectly) contained, may be null.

probableThisTarget

The potential target of a this keyword binding, this is not necessarily the containing class or object literal. In case of instance methods of a class T, this usually is the classifier T; in case of static methods, it is the classifier type type{type}.

container

The direct owner of the expression. z

The expressions and statements are ordered, describing first the constructs available in the 5th edition of ECMA-262, referred to as [ECMA11a] in the following. It is worth noting that the grammar snippets already use newer constructs in some cases.

8.1. ECMAScript 5 Expressions

N4JS supports the same expressions as ECMAScript. The semantics are described in [ECMA11a(p.S11)]. In N4JS, some expressions are extended for supporting the declaration of types, annotations, or parameterized usages. These extensions and type-related aspects as well as specific N4JS constraints are described in this section.

Some operators come in different ’flavors’, that is as binary operator, unary pre- or postfix operators, or assignment operators. For these operators, type constraints are only defined for the binary operator version and the other variants are deduced to that binary version. E.g., ++ and += are deduced to + (and simple assignment).

8.1.1. The this Literal

This section describes the this literal and the semantics of the @This annotation, the type this is described in This Type.

Semantics

Semantics are similar to the original ECMAScript this keyword, see [ECMA11a(p.11.1.1, p.p.63)]) Also see [West06a] and MozillaJSRef

Regarding the location where this may be used, the following restrictions apply:

Req. IDE-173: Valid location for this literal (ver. 1)

The literal may not be used in

  1. the initializer expression of static data fields in classes.

  2. the initializer expression of data fields in interfaces (applies to both static and non-static).

  3. static methods of interfaces and static field accessors of interfaces.

See also [Req-IDE-69].

Note: [Req-IDE-173] also applies for this literals inside arrow expressions in initializers.

The use of this is illustrated with some examples as it can often be confusing. Type inference heuristics and explanations are provided in the next section.

Example 76. This in Unrestricted Mode

In unrestricted mode, this is bound to the receiver. If there is no receiver it is bound to the global object, however, we often do not know exactly what the global object would be.

var name = "global a"; // assume the top level is similar to the global object
this.name; // <-- "global a"
function f() {
    return this.name; // <-- depends on call, usually "global a"
}
var ol1 = {
    name: "John",
    greeting: "Hello " + this.name, // "Hello global a" -- we do not greet John!
}
var ol2 = {
    name: "John",
    f: function() {
        this.name; // usually "John", as we assume f is called like ol2.f()
        var g = function() {
           return this.name; // "global a"
        }
        return g(); // no receiver, this in nested function g will be global scope
    }
}
Example 77. This in strict mode

In strict mode, this is bound to the receiver. If there is no receiver, it is bound to undefined. Thus, we will probably get a lot of errors:

"use strict"
var name = "global a"; // assume the top level is similar to the global object
this.name; // <-- error, this is undefined, there is no receiver
function f() {
    return this.name; // <-- depends on call, usually this produces an error as this is undefined
}
var ol1 = {
    name: "John",
    greeting: "Hello " + this.name, // will produce an error, as this is undefined
}
var ol2 = {
    name: "John",
    f: function() {
        this.name; // usually "John", as we assume f is called like ol2.f()
        var g = function() {
           this.name; // an error, see call below:
        }
        return g(); // no receiver, this in nested function g is undefined
    }
}
Example 78. This in N4JS mode

As in strict mode, this is bound to the receiver and if there is no receiver, it is bound to undefined. So the example above is also true for N4JS mode. Classes behave slightly differently:

class A {
    name = "John";
    greeting  = "Hello " + this.name; // works, in N4JS classes, greeting is "Hello John"

    f() {
        return this.name; // this usually is instance object, similar to object literals.
    }

    g() {
        var h = function() {
            return this.name; // as in object literals: no receiver, no this.
        }
        return h();
    }
}
In N4JS classes, this is always bound to the instance when used in field initialization.
Type Inference

The type is inferred from the this type is bound to. The inference, therefore, has to consider the original semantics as described in [ECMA11a(p.10.4., p.10.4.3, p.p.58)]. In ECMAScript the type of this is unfortunately determined by the function call and not by the function definition:

  • By default, this is bound to the global object [ECMA11a(p.10.4.1.1)]. Unfortunately it is often unknown what the global object will be at run time (e.g., node.js differs from browsers).

  • If a function is called without a receiver, this is bound to

    • the global object or

    • to undefined in strict mode.

  • If a function is called with a receiver,this is bound to the receiver object.

Actually, this is bound to the newly created object if a function is called with the new operator. If a function is known to be invoked with an explicit thisArg (apply() etc.), the @This annotation can be used to explicitly set the this type. This annotation has precedence over otherwise inferred bindings.

In general, the actual this target can not be inferred from the context of the this keyword. A heuristic is defined, however, to compute the probable this type:

  1. If the this keyword is used in some function annotated with an annotation @This, the type specified in the annotation is used. The inferred type is always nominal.

    f="this".containingFunctionOrAccessor
    f.hasAnnotation"@This"T=f.annotation["@This"]Γ"this":T

  2. If the this keyword is used in some instance method of a classifier or in an instance field initializer,this is bound to the T itself. If the this keyword is used in some static method of a classifier T or in a static field initializer, the prototype type (or constructor) of the classifier is used, that is type[T]. In both cases, the target is determined by using the expressions’s pseudo property probableThisTarget. If the this keyword is used in a function expression assigned to an property of an object literal, the type of the object literal is used. Note that usually this is the this type in instance methods, and the this type in static methods.

    T="this".probableThisTargetTnullΓ"this":T
  3. In all other cases: Non-strict mode:

    mode=unrestrictedΓ"this":global

Strict mode and N4JS mode:

modeunrestrictedΓ"this":globalundefined

If the actual this type is defined as a structural type, the structural type information is moved to the this type itself. This is transparent to the user in general but maybe visible in case of error messages. That is to say that the actual this type is always a nominal type. This is indicated by the nominal modifier (cf. [Req-IDE-90] constraints 1 and 2.).

  1. The @This annotation is only allowed on declared functions, function expressions (including arrow functions), methods, and field accessors, i.e. getters and setters, except static members of interfaces.

  2. The type declared by way of @This(..) an annotation of a method or field accessor must be a subtype of the member’s containing classifier.

Req. IDE-92: Single @This Annotation (ver. 1)

It is not allowed to use more then one @This(..) annotation on an element.

Example 79. Effect of Nominal This Type

Given the following declaration

@This(~Object with {a: string;}) f() {}

Since the this type is always nominal, ~ Object becomes Object. In case of method call, however, the returned value becomes structural again. In case of error messages the type of the return type is then

~this[Object] with {a: string;}

For the sake of simplicity, additional structural members are usually omitted in error messages, leading to

~this[Object]

instead of

this[~Object]
Example 80. This and Function Declaration

This example demonstrates the usage of functions annotated with @This. By using the argument union{A,B} it is possible to have two completely unrelated classes as the receiver type of the function logger. To pass an actual object the apply() method of the function is used.

class A {
    log: string() { return "A was logged"; }
}

class B {
    log: string() { return "B was logged"; }
}

@This(union{A,B})
function logger() { console.log("~ "+this.log()+" ~"); }


var a: A = new A();
logger.apply(a,[]); // prints "~ A was logged ~"
logger.apply( new B(),[]) // prints "~ B was logged ~"
Example 81. This and Function Expressions

In this example a function is created via a function expression. The function is then assigned to member field of class B. Via annotating the expression with @This(B), access to the receiver of type B is enabled.

class B {
    log(): string { return "B was logged"; }     // method
    logMe : {@This(B) function():void}; // reference to a function
}

var b: B = new B();
b.logMe = @This(B) function() { console.log("*>"+this.log()+"<*"); }
b.logMe(); // prints "*>B was logged<*"

Note that if a function is called as a constructor function with new, the type of this can be declared via annotation @This(..), as shown in the following snippet:

@This(
    ~Object with {
        w: number; h: number;
        area: {function():number};
    })
function Box(w: number w, h: number) {
    this.w = w;
    this.h = h;
    this.area = @This(
        ~Object with {
            w: number; h: number;
            area: {function():number};
        }) function() { return this.w * this.h }
}
var bError = Box(1,2)
var bOK = new Box(1,2)

Inside the constructor function Box, this is bound to the structural type definition due to the annotation.

Inside the nested function area, this is bound to the receiver object (if the function is called like bOk.area()). Again, this depends on the way the nested function is called, which can usually not be determined at the declaration location. The nested function must then be annotated accordingly.

When calling this function, the type of this is checked against the declared this type, which would cause an error in the first case.

The use of the @This annotation is not allowed on methods.

Using constructor functions is not recommended and an error or warning will be created. This is only useful for adapting third-party library code. Even in the latter case, it would probably make more sense to declare a (library) class Rectangle rather then defining the constructor function.

8.1.2. Identifier

Syntax

Identifiers as expressions are identifier references. They are defined as follows:

IdentifierRef <Yield>:
    id=[types::IdentifiableElement|BindingIdentifier<Yield>]
;

BindingIdentifier <Yield>:
    IDENTIFIER
    | <!Yield> 'yield'
    | N4Keyword
;
Semantics

The type of an identifier i is resolved depending on its binding and scope respectively (cf. [ECMA11a(p.10.2.2.1GetIdentifierReference, p.p.56)]. The following scopes (aka Lexical Environments) are defined:

  • function local; local variables, parameters

  • zero or more function closure in case of nested functions

  • module

  • global

These scope are nested as illustrated in Scopes.

Note that classes definitions and object literal do not define a scope: members of a class or properties of an object literal are to be accessed via this. Identifier references always reference declared elements, that is to say either variable, function, or class declarations. Properties of object literals or members of a class are referenced via PropertyAccess-Expression.property (see Property Accessors).

scopes
Figure 7. Scopes

An identifier may be bound to a variable (global or local variable, parameter, variable defined in a function’s closure), or to a property of an object. The latter case is known as property access as further described in Property Accessors.

Req. IDE-93: Read Access to Identifier (ver. 1)

If an identifier i is accessed, the bound declared element D must be readable if it is not used on the left-hand side of an assignment expression.

bindiD
 AssignmentExpression aei.container*:
ae.left=i
μae.left=PropertyAccessExpressionae.left.property=i:
D.readable

Type Inference

An identifier reference i is bound to an identifiable element i.id, which is expressed with the function bindii.id. The type of the reference is then inferred as follows:

Γidref.id:TΓIdentifierRef idref:T

8.1.3. Literals

Type Inference

The type of a literal can directly be derived from the grammar. The following axioms are defined for literals:

NullLiteral:null
BooleanLiteral:boolean
NumericLiteral:intornumber
StringLiteral:string
RegularExpressionLiteral:RegExpr

Note that there are no literals specific for pathSelector or i18nkey.

8.1.3.1. Integer Literals

Numeric literals representing integers in the range of JavaScript’s int32 are inferred to the built-in primitive type int instead of number. The following rules apply:

Req. IDE-94: Numeric literals (ver. 1)

  • Numeric literals with a fraction or using scientific notation, e.g. 2.0 and 2e0, respectively, are always inferred to number, even if they represent integers in the range of int32.

  • Numeric literals that represent integers in the range of JavaScript’s int32, i.e. from -231 to 231-1, are inferred to int.

  • Hexadecimal and octal literals are always interpreted as positive numbers, so all values above 0x7fffffff and 017777777777 lie outside the range of int32 and will thus be inferred to number; this is an important difference to Java. See below for further elaboration.

There are differences to numeric literals in Java:

Java JavaScript N4JS

Literal

Value

Type

Value

Type

2147483648

-2147483648

int

-2147483648

int

2147483647

2147483647

int

2147483647

int

0x7fffffff

2147483647

int

2147483647

int

0x80000000

-2147483648

int

+2147483648

number

0xffffffff

-1

int

4294967295

number

0x100000000

n/a

4294967296

number

017777777777

2147483647

int

2147483647

int

020000000000

-2147483648

int

+2147483648

number

037777777777

-1

int

4294967295

number

040000000000

0

int

4294967296

number

0100000000000

n/a

8589934592

number

The literals 0x100000000 and 0100000000000 produce a syntax error in Java.

Until IDE-1881 is complete, all built-in operations always return a number even if all operands are of type int. For the time being, we therefore interpret -1 as a negative integer literal (inferred to int), but -(1) as the negation of a positive integer literal (inferred to number).

8.1.4. Array Literal

Syntax
ArrayLiteral <Yield> returns ArrayLiteral:
    {ArrayLiteral} '['
        elements+=ArrayPadding* (
            elements+=ArrayElement<Yield>
            (',' elements+=ArrayPadding* elements+=ArrayElement<Yield>)*
            (trailingComma?=',' elements+=ArrayPadding*)?
        )?
    ']'
;

/**
 * This array element is used to pad the remaining elements, e.g. to get the
 * length and index right
 */
ArrayPadding returns ArrayElement: {ArrayPadding} ',';

ArrayElement <Yield> returns ArrayElement: {ArrayElement} spread?='...'? expression=AssignmentExpression<In=true,Yield>;
Type Inference

In general, an array literal is inferred as Array<T> (similar to the type of new Array()). The interesting question is the binding of the type variable T.

The type of an array padding p is inferred as follows:

Γp:undefined

The element type of an array literal is simply inferred as the (simplified) union of the type elements of the array. Thus, the type of an array literal a is inferred as follows:

Γa.elements¯:Te¯T=Te¯Γa:Array<T>

In other languages not supporting union types, the element type is often inferred as the join (LCST) of the element types. Using a union type here preserves more information (as the actual types are still known). For many use cases the behavior is similar though, as the members of a union type are the members of the join of the elements of the union.

Note that typeof [1,2,3] does not return Array<number> (as ECMAScript is not aware of the generic array type), but Object.

Example 82. Array Type Inference

The type for all variables declared in this example is inferred to Array<string>:

var names1          = ["Walter", "Werner"];
var names2          = new Array("Wim", "Wendelin");
var names3          = new Array<string>(3); // length is 3
var names4: Array<string>;

Empty array literals are inferred to any, by default. We are not using Array<?> here because then a typical JavaScript pattern would no longer be supported:

var a = [];
a.push('hello'); (1)
1 This would fail if a and thus [] were inferred to Array<?>
An important exception; if a type expectation exists for the empty array literal and the expected type is Array<T>, this will be used as the type of the array literal.

Req. IDE-95: Empty array literal (ver. 1)

An empty array literal will be inferred as follows:

  • If there is a type expectation for the empty array literal and the expected type is Array<T>, for any type T, then the type of the empty array literal will be inferred to Array<T>.

  • Otherwise, the type of the empty array literal will be inferred to Array<any>.

8.1.5. Object Literal

In addition to ordinary Javascript object literals, N4JS supports the spread operator within object literals as introduced in [ECMA18a].

Syntax

Cf. [ECMA11a(p.S11.1.5, p.p.65ff)] The syntax of an object literal is given by:

ObjectLiteral <Yield>: {ObjectLiteral}
    '{'
        ( propertyAssignments+=PropertyAssignment<Yield>
          (',' propertyAssignments+=PropertyAssignment<Yield>)* ','?
        )?
    '}'
;

PropertyAssignment <Yield>:
      PropertyNameValuePair<Yield>
    | PropertyGetterDeclaration<Yield>
    | PropertySetterDeclaration<Yield>
    | PropertyMethodDeclaration<Yield>
    | PropertyNameValuePairSingleName<Yield>
    | PropertySpread<Yield>
;


PropertyMethodDeclaration <Yield>:
    => ({PropertyMethodDeclaration}
        annotations+=Annotation*
        TypeVariables? returnTypeRef=TypeRef?
            (
                generator?='*'  LiteralOrComputedPropertyName<Yield> ->MethodParamsAndBody<Generator=true>
                | LiteralOrComputedPropertyName<Yield> ->MethodParamsAndBody <Generator=false>
            )
        )
    ';'?
;

PropertyNameValuePair <Yield>:
    => (
        {PropertyNameValuePair}
        annotations+=Annotation*
        declaredTypeRef=TypeRef? LiteralOrComputedPropertyName<Yield> ':'
    )
    expression=AssignmentExpression<In=true,Yield>
;

/*
 * Support for single name syntax in ObjectLiteral (but disallowed in actual object literals by ASTStructureValidator
 * except in assignment destructuring patterns)
 */
PropertyNameValuePairSingleName <Yield>:
    declaredTypeRef=TypeRef?
    identifierRef=IdentifierRef<Yield>
    ('=' expression=AssignmentExpression<In=true,Yield>)?
;

PropertyGetterDeclaration <Yield>:
    =>(
        {PropertyGetterDeclaration}
        annotations+=Annotation*
        GetterHeader<Yield>
    )
    body=Block<Yield=false>
;

PropertySetterDeclaration <Yield>:
    =>(
        {PropertySetterDeclaration}
        annotations+=Annotation*
        'set'
        ->LiteralOrComputedPropertyName <Yield>
    )
    '(' fpar=FormalParameter<Yield> ')' body=Block<Yield=false>
;

PropertySpread <Yield>:
	'...' expression=AssignmentExpression<In=true,Yield>
;
import Address from "my/Address";
var simple = {name: "Walter", age: 72, address: new Address()};
8.1.5.1. Properties

PropertyAssignments have common properties of PropertyNameValuePair, PropertyGetterDeclaration, and PropertySetterDeclaration:

annotations

The annotations of the property assignment.

name

The name of the property. This may be an identifier, a string or a numeric literal. When comparing names, we implicitly assume the name to be converted to an identifier, even if this identifier is not a valid ECMAScript identifier.

declaredType

The declared type of the property which may be null. This property is a pseudo property for PropertySetterDeclaration, in this case it is derived from the declared type of the setter’s formal parameter.

Additionally, we introduce the following pseudo properties to simplify constraints:

isAccessor

The read-only boolean property. This is true if the property assignment is a setter or getter declaration. This is comparable to ECMAScript’s spec function IsAccessoprDescriptor. For a given property assignment p this is semantically equivalent to μp=PropertyGetterDeclarationμp=PropertySetterDeclaration.

isData

The read-only boolean property. This is true if the property assignment is a name value pair. For a given property assignment p this is semantically equivalent to μp=PropertyNameValuePair. It is comparable to ECMAScript’s spec function isDataDescriptor. The equation isAccessor=¬isData is always true.

Semantics

Req. IDE-96: Object literal (ver. 1)

For a given object literal ol the following constraints must hold (cf. [ECMA11a(p.p.66)]:

  • the name of each property is given as an identifier, a string literal, a numeric literal, or as a computed property name with a compile-time expression (see Compile-Time Expressions). In particular, string literals, e.g. ['myProp'], built-in symbols, e.g. [Symbol.iterator], and literals of @StringBased enums are all valid computed property names.

  • Object literal may not have two PropertyNameValuePairs with the same name in strict mode (cf. 4.a [ECMA11a(p.p.66)]):

    mode=strictpaol.propertyAssignments,pa.isData:
    pa'ol.propertyAssignments:
    pa'.isAccessorpa'.name=pa.name

  • Object literal may not have PropertyNameValuePair and PropertyGetterDeclaration/PropertySetterDeclaration with the same name (cf. 4.b/c [ECMA11a(p.p.66)]):

    paol.propertyAssignments,pa.isData:
    pgsdol.propertyAssignments:
    μpgsdPropertyNameValuePairpgsd.name=pa.name

  • Object literal may not have multiple PropertyGetterDeclaration or PropertySetterDeclaration with the same name (cf. 4.d [ECMA11a(p.p.66)]):

    pgol.propertyAssignments,pg.isAccessor:
    pg'ol.propertyAssignmentspg:
    μpg'=μpgpg'.name=pg.name

It is a SyntaxError if the Identifier eval or the Identifier arguments occurs as the Identifier in a PropertySetParameterList of a PropertyAssignment that is contained in strict code or if its FunctionBody is strict code. [ECMA11a(p.p.66)]
  • If two or more property assignments have the same name (and the previous conditions hold), then the types of these assignments must conform. That is to say that the inferred (but not declared) type of all assignments must be type of probably declared types and if the types are explicitly declared, they must be equal.

  • In N4JS mode, the name of a property must be a valid N4JSIdentifier:

    mode=n4jspaol.propertyAssignments:
    μpa.name=N4JSIdentifier

Let E be the expected type of an object literal O as defined by the context in which O is used. If E is not type Object and not dynamic, then the compiler creates a warning O contains properties not found in E.

This is true in particular for object literals passed in as arguments of a spec-constructor.

8.1.5.2. Scoping and linking
Example 83. Scoping and linking
var p = {
    f: function() {
        console.log("p´s f");
    },
    b: function() {
        this.f();
    },
    o: {
        nested: "Hello"
    }
};
p.b();
p.o.nested;
  • Other properties within an object literal property can be accessed using this. In the expression of property name value pairs, however, this is not be bound to the containing object literal, but usually to undefined or global.

  • The properties of an object literal are accessible from outside.

  • Nested properties of an object literal are also accessible from outside.

Type Inference

An object literal implicitly extends ~Object, therefore, object literal types use structural typing. For details see Structural Typing. From a type systems point of view, the two variables ol and st below have the same type.

var ol = {
    s: "hello",
    n: 42
}
var st: ~Object with { s: string; n: number;};

8.1.6. Parenthesized Expression and Grouping Operator

The grouping operator is defined here as a parenthesized expression.

Syntax
ParenExpression <Yield>: '(' expression=Expression<In=true,Yield> ')';
Type Inference

The type of the grouping operator simply is the type of its nested expression. The type if a parenthesized expression pe is inferred as follows:

Γe:TΓ’(’e’)’:T
Example 84. Parenthesized Expression Type Examples

In the following listing, the type of the plain expressions is equivalent to the parenthesized versions:

class A{} class B extends A{}
var f: boolean; var a: A a; var b: B;

/* simple       <->     parenthesized */
10;                     (10);
"hello";                ("hello");
true;                   (true);
a;                      (a);
10-5;                   (10-5);
f?a:b                   (f?a:b);

8.1.7. Property Accessors

Syntax

Property accessors in N4JS are based on [ECMA11a(p.S11.2.1, p.p.67ff)]. They cannot only be used for accessing properties of an object, but also for accessing members of a class instance. In order to support parameterized calls, the syntax is extended to optionally allow type arguments.

ParameterizedPropertyAccessExpression:
    target=PrimaryExpression<Yield> ParameterizedPropertyAccessExpressionTail<Yield>
;

IndexedAccessExpression:
    target=PrimaryExpression<Yield> IndexedAccessExpressionTail<Yield>
;

fragment IndexedAccessExpressionTail <Yield>*:
    '[' index=Expression<In=true,Yield> ']'
;

fragment ParameterizedPropertyAccessExpressionTail <Yield>*:
    '.' TypeArguments? property=[types::IdentifiableElement|IdentifierName]
;

Note that in [ECMA11a], the index access is called bracket notation.

Direct Property Access

We define a special case of property access as follows:

A property access expression is called direct, iff

  • its target is an identifier reference to a class, interface, enum, or the built-in object Symbol, and

  • its property name denotes an owned member of the target classifier (not an inherited, consumed, or polyfilled member) or a literal if the target is an enum.

As a consequence, a direct property access can only refer to static members.

The first requirement of the above definition rules out property access expressions that do not directly point to their target classifier or enum, as shown in the following example:

class C {
  const field = 'hello';
}
C.field;  // direct property access to 'field'
let ctor = C;
ctor.field;  // *not* a direct property access to 'field'

Direct property access is the only form of property access allowed in compile-time expressions, cf. Compile-Time Expressions.

8.1.7.1. Properties

We define the following properties:

target

The receiver of the property access.

index

The index expression in case of an IndexedAccessExpression (returns null otherwise).

property

The name of the property in case of non-indexed-access expressions (returns null otherwise, although the index may be interpreted as property name).

We define the following pseudo properties:

isDotAccess

Read-only boolean property, returns true for non-index access expression (similar to μpIndexedAccessExpression).

isIndexAccess

Read-only boolean property, returns true for index access expression (similar to μp=IndexedAccessExpression.
The equation p.isDotAccess=¬p.isIndexAccess is always true.

name

Returns the name of the property. This is either the property converted to a simple name or the index converted to a name (where possible) if it is an indexed-accessed expression.

Semantics

The parameterization is part of the property access in case of generic methods. For generic functions, a parameterized function call is introduced (cf. Function Calls). The constraints are basically similar.

Req. IDE-97: Property Access and Dot Notation (ver. 1)

  1. If dot notation is used in N4JS mode, the referenced property must exist unless receiver is a dynamic type:

    pae.isDotAccess¬R.dyn mpae.target.type.properties:m.name=pae.name
  2. If dot notation is used and the referenced property exists, then the property must be accessible:

    pae.isDotAccess¬R.dyn mpae.target.type.properties:m.name=pae.nameαpaem
  3. If dot notation is used and the referenced property exists and this property is a member with a declared @This type (only possible for methods or field accessors), then the receiver must be a subtype of the declared @This type.

Req. IDE-98: Index Access (ver. 1)

An index access expression is valid iff one of the following cases applies:

  1. the receiver is of a dynamic type. In this case, the index may be any expression (need not be a compile-time expression).

  2. the receiver is an immediate instance of Object, i.e. it is a subtype of Object and its super types but not of any other type including ~Object and ~~Object.

  3. the receiver is of type Array, ArgumentType, string, or String (including their subtypes) and the index is an expression of type number.

  4. the index expression is a compile-time expression

    • and the receiver type defines a member with a name equal to the string representation of the index expression’s compile-time value

    • and the receiver is not an enum.

Although index access is very limited, it is still possible to use immediate instances of Object in terms of a map (but this applies only to index access, not the dot notation):

Example 85. Object as Map
var map: Object = new Object();
map["Kant"] = "Imperative";
map["Hegel"] = "Dialectic";
map.spinoza = "Am I?";  // error: Couldn't resolve reference to IdentifiableElement 'spinoza'.

Req. IDE-99: Parameterized Property Access (ver. 1)

For a parameterized property access expression pae, the following constraints must hold:

  1. The receiver or target must be a function or method:
    pae.target.type<:Function

  2. The number of type arguments must match the number of type parameters of the generic function or method:
    |pae.typeArgs|=|pae.target.typeVars|

  3. The type arguments of a parameterized property access expression must be subtypes of the boundaries of the parameters of the called generic method.

Also see constraints on read ([Req-IDE-93]) and write ([Req-IDE-121]) access.

Type Inference

We define the following type inferencing rules for property accessors:

  • The type of an indexed-access expression p is inferred as follows:

    ¬p.target.dynp.index.type<:numberΓp.target:Array<T>Γp:TelseΓp:any
  • The type of a property access expression is inferred as follows:

    ΓθRexpr.target:RΓexpr.property:TPropertyAccessExpression expr:T
  • The type of a parameterized access expression p is inferred as follows:

    mp.target:m.name=p.nameΓm:TΓp:TΓp:any

8.1.8. New Expression

Syntax
NewExpression: 'new' callee=MemberExpression<Yield> (-> TypeArguments)?
        (=> withArgs?='(' Arguments<Yield>? ')' )?
import Address from "my/Address";

var a = new Address();
// a.type := my/Address

class C<T> {
    constructor(param: T) {}
}
var c = new C<string>("hello");
Semantics

Req. IDE-100: New expression (ver. 1)

Let ne be a new expression, with Γne.callee:C. The following constraints must hold:

  1. The callee must be a constructor type: C<:constructor? or a constructable type.

  2. Let O be the type argument of C, that is C=constructorO. In that case,

    1. O must not be an interface or enum: μCInterfaceEnum

    2. O must not contain any wildcards.

    3. O must not be a type variable.

  3. If C is not a constructor type, it must be a constructable type, that is one of the following:

    Object, Function, String, Boolean,Number, Array, Date, RegExp, Error

    In particular, it must not refer to a primitive type or a defined functions (i.e., subtypes of Function) cannot be used in new-expressions in N4JS.

Remarks:

to 1 The type of an abstract class A is typeA. Or in other words: Only instantiable classes have an inferred type of constructor....

to 2 Even though it is possible to use the constructor type of an abstract class – concrete subclasses with override compatible constructor signature will be subclasses of this constructor.

to 3 It is not possible to refer to union or intersection at that location. So this is not explicitly denied here since it is not possible anyway.

Example 86. Abstract classes and construction

The following examples demonstrates the usage of abstract classes and constructor types, to make the first two constraints more clearer:

/* XPECT_SETUP org.eclipse.n4js.spec.tests.N4JSSpecTest END_SETUP */

abstract class A {}
class B extends A {}

// XPECT errors --> "Cannot instantiate abstract class A." at "A"
var x = new A();
// XPECT noerrors -->
var y = new B();

function foo(ctor : constructor{A}) {
    // XPECT noerrors -->
    return new ctor();
}

// XPECT errors --> "type{A} is not a subtype of constructor{A}." at "A"
foo(A);
// XPECT noerrors -->
foo(B);
Type Inference

The type of a new expression ne is inferred as follows:

Γne.callee:constructorCΓne:C

For classes, constructors are described in Constructor and Classifier Type.

In N4JS it is not allowed to call new on a plain function. For example:

function foo() {}
var x = new foo();

will issue an error.

8.1.10. Function Calls

In N4JS, a function call [ECMA11a(p.S11.2.3)] is similar to a method call. Additionally to the ECMAScript’s CallExpression, a ParameterizedCallExpression is introduced to allow type arguments passed to plain functions.

Syntax
[[function-calls-syntax]]

Similar to [ECMA11a(p.S11.2.3, p.p.68ff)], a function call is defined as follows:

CallExpression <Yield>:
    target=IdentifierRef<Yield>
    ArgumentsWithParentheses<Yield>
;

ParameterizedCallExpression <Yield>:
    TypeArguments
    target=IdentifierRef<Yield>
    ArgumentsWithParentheses<Yield>
;

fragment ArgumentsWithParentheses <Yield>*:
    '(' Arguments<Yield>? ')'
;

fragment Arguments <Yield>*:
    arguments+=AssignmentExpression<In=true,Yield> (',' arguments+=AssignmentExpression<In=true,Yield>)* (',' spread?='...' arguments+=AssignmentExpression<In=true,Yield>)?
    | spread?='...' arguments+=AssignmentExpression<In=true,Yield>
;
Semantics

Req. IDE-101: Function Call Constraints (ver. 1)

For a given call expression f bound to a method or function declaration F, the following constraints must hold:

  • If less arguments are provided than formal parameters were declared, the missing formal parameters must have been declared optional:
    |f.args|<|F.pars||f.args|<i|F.pars|:Fparsi.optional

  • If more arguments are provided than formal parameters were declared, the last formal parameter must have been declared variadic:
    |f.args|>|F.pars|F.pars|F.pars|-1.variadic

  • Types of provided arguments must be subtypes of the formal parameter types:
    0<i<min|f.args||F.pars|:f.argsi<:F.parsi

  • If more arguments are provided than formal parameters were declared, the type of the exceeding arguments must be a subtype of the last (variadic) formal parameter type:
    |F.pars|<i|f.args|:f.argsi<:F.pars|F.pars|-1

  • The number of type arguments in a parameterized call expression must be equal to the number of type parameters of the generic function / method and the type arguments must be subtypes of the corresponding declared upper boundaries of the type parameters of the called generic function.

Note that (for a limited time), constraints [Req-IDE-101] and [Req-IDE-102] are not applied if the the type of F is Function. See Function-Object-Type.

Type Inference

A call expression expr is bound to a method (Methods) or function declaration (which may be part of a function definition (Function Declaration or specified via a function type Function Type) F (via evaluation of MemberExpression. The type of the call is inferred from the function declaration or type F as follows:

bindexpr.targetFF.returnType:TΓexpr:T

A generic method invocation may be parameterized as well. This is rarely required as the function argument types are usually inferred from the given arguments. In some cases, for instance with pathSelectors, this is useful. In that case, the type variable defined in the generic method declaration is explicitly bound to types by using type arguments. See Property Accessors for semantics and type inference.

Example 87. Generic Method Invocation

This examples demonstrate how to explicitly define the type argument in a method call in case it cannot be inferred automatically.

class C {
    static <T> foo(p: pathSelector<T>): void {..}
};
C.<my.Address>foo("street.number");

Note that in many cases, the type inferencer should be able to infer the type automatically. For example, for a method

function <T> bar(c: T, p: pathSelector<T>): void {..};

and a function call

bar(context, "some.path.selector");
[source,n4js]

the type variable T can be automatically bound to the type of variable context.

8.1.11. Postfix Expression

Syntax
PostfixExpression returns Expression: LeftHandSideExpression
         (=>({PostfixExpression.expression=current} /* no line terminator here */ op=PostfixOperator))?
    ;
enum PostfixOperator: inc='++' | dec='--';
Semantics and Type Inference

The type inference and constraints for postfix operators ++ and --, cf. [ECMA11a(p.S11.3.1, p.p.70)], [ECMA11a(p.S11.3.1, p.p.70)], are defined similarly to their prefix variants (unary expressions), see Unary Expression.

Req. IDE-103: Postfix Expression Constraints (ver. 1)

For a given postfix expression u u with u.op++-- and u.expression.type:T, the following constraints must hold:

  • In N4JS mode, the type T of the expression must be a number.

  • If u.expression=PropertyAccesspappa.isDotAccess both get p and set p must be defined.

8.1.12. Unary Expression

Syntax

We define the following unary operators and expression, similar to [ECMA11a(p.p.70ff)]:

UnaryExpression returns Expression:
      PostfixExpression
    | ({UnaryExpression} op=UnaryOperator expression=UnaryExpression);
enum UnaryOperator: delete | void | typeof | inc='++' | dec='--' | pos='+' | neg='-' | inv='$\sim$' | not='!';
Semantics

For semantics of the delete operator, see also [MozillaJSRef]

Req. IDE-104: Delete Operator Constraints (ver. 1)

For a given unary expression u with u.op=delete, the following constraints must hold:

  • In strict mode, u.expression must be a reference to a property of an object literal, a member of a class type, or to a property of the global type (i.e., the reference must be bound, and the bound target must not be a variable).

  • In N4JS mode, the referenced property or member must not be declared in the containing type and the containing type reference must be declared dynamic.

Req. IDE-105: Void Operator Constraints (ver. 1)

There are no specific constraints defined for with u.op=void

Req. IDE-106: Typeof Operator Constraints (ver. 1)

There are no specific constraints defined for unary expression u with u.op=typeof.

Req. IDE-107: Increment/Decrement Constraints (ver. 1)

For a given unary expression u u with u.op++-- and u.expression.type:T, the following constraints must hold:

  • If mode is N4JS, the type T of the expression must be a number

    ΓUnaryExpressionExpression:number
  • If u.expression=PropertyAccesspappa.isDotAccess both get p and set p must be defined.

For a given unary expression u u with u.op+- and u.expression.type:T, the following constraints must hold:

  • In N4JS mode, the type T of the expression must be a number:

ΓUnaryExpressionExpression:number

Req. IDE-109: Logical Not Operator Constraints (ver. 1)

There are no specific constraints defined for with u.op=!.

Type Inference

The following operators have fixed types independent of their operand types:

Γ’delete’ expression:boolean
Γ’void’ expression:undefined
Γ’typeof’ expression:string
Γ(’++’—’–’—’+’—’-’—’ ’) expression:number
Γ’!’ expression:boolean

8.1.13. Multiplicative Expression

Syntax
MultiplicativeExpression returns Expression: UnaryExpression
      (=>({MultiplicativeExpression.lhs=current} op=MultiplicativeOperator) rhs=UnaryExpression)*;
enum MultiplicativeOperator: times='*' | div='/' | mod='%';
Semantics

Req. IDE-110: Multiplicative Expression Constraints (ver. 1)

For a given multiplicative expression the following constraints must hold in N4JS mode :

  • The types of the operands may be any type:

    ΓMultiplicativeExpressionExpression:any

If a non-numeric operand is used, the result may be NaN which actually is a number as well.

Type Inference
[[type-inference-9]]

The inferred type of a multiplicative expression always is number:

ΓMultiplicativeExpression:number

8.1.14. Additive Expression

Syntax
AdditiveExpression returns Expression: MultiplicativeExpression
    (=>({AdditiveExpression.lhs=current} op=AdditiveOperator) rhs=MultiplicativeExpression)*;
enum AdditiveOperator: add='+' | sub='-';
Semantics

Req. IDE-111: Additive Expression Constraints (ver. 1)

For a given additive expression the following constraints must hold in N4JS mode:

  • The type of the operand can be any type:

ΓAdditiveExpression eExpression:any

In JavaScript it is possible to subtract two non-numerics, leading to NaN. Also undefined or null may be used. The real difference is what type is to be returned (string or number, see below).

8.1.14.1. Type Inference

The type of an additive expression is usually inferred to number, except for addition which may lead to string as well. The result for the addition operator is only be a number if both operands are numbers, booleans, null, or undefined. Using undefined in an additive expression leads to NaN which actually is a number from the type system’s point of view. Additional analysis may create errors in the latter case though.

We first define two helper rules to simplify the addition operator condition:

Ninnumber, int, boolean, null, undefined:T<:=NnbTnbnbT (μT=Union   ET.typeRefs:mnbEmnbTmnbΓe.lhs:LΓe.rhs:RnbLnbRtoNumexprtoNumΓe.lhs:LΓe.rhs:RmnbLmnbRmayNumexprmayNum

The type of an additive expression e is inferred as follows:

e.op='+'¬toNume¬mayNumeΓe:stringe.op='+'¬toNumemayNumeΓe:unionnumber,stringe.op='+'toNumeΓe:numbere.op'+'Γe:number

That is, if both operands are number, int, boolean, null, or even undefined, then the 'plus' is interpreted as mathematical addition and the result is a number. In other cases the 'plus' is interpreted as string concatenation and the result is a string. In case of union types, the result may be a union of number and string.

Adding two integers (int) leads to a number, since the result may not be represented as an (JavaScript) int anymore.

Example 88. Type of addition expression
1+2;            // number 3
"1"+"2";        // string "12"
"1"+2;          // string "12"
1+true;         // number 2
false+1;        // number 1
"1"+true;       // string "1true"
"1"+null;       // string "1null"
1+null;         // number 1
1+undefined;    // number NaN
"1"+undefined;  // string "1undefined"

Support new Symbol.toPrimitive.

8.1.15. Bitwise Shift Expression

Syntax
Cf. +[+<<ECMA11a,ECMA11a(p.p.76f)>>+]+
ShiftExpression returns Expression: AdditiveExpression
    (=>({ShiftExpression.lhs=current} op=ShiftOperator rhs=AdditiveExpression))*
;

ShiftOperator returns ShiftOperator:
      '>' '>' '>'? // SHR, USHR
    | '<' '<'  // SHL
    ;
Semantics

Req. IDE-112: Bitwise Shift Expression Constraints (ver. 1)

For a given bitwise shift expression e the following constraints must hold in N4JS mode: * The types of the operands can be any.

ΓBitwiseShiftExpression  Expression:any
Type Inference

The type returned by a bitwise shift expression is always number:

Γ (Expression (’<<’—’>>’—’>>>’) Expression):number

A non-numeric operand is interpreted as 0, except for true which is interpreted as 1; or objects implementing the symbol toPrimitive.

8.1.16. Relational Expression

Syntax
RelationalExpression returns Expression: ShiftExpression
    (=>({RelationalExpression.lhs=current} op=RelationalOperator) rhs=ShiftExpression)*;

RelationalExpressionNoIn returns Expression: ShiftExpression
    (=>({RelationalExpression.lhs=current} op=RelationalOperatorNoIn) rhs=ShiftExpression)*;

enum RelationalOperator:
    lt='<' | gt='>' | lte='<=' | gte='>=' | instanceof | in;
RelationalOperatorNoIn returns RelationalOperator:
    '<' | '>' | '<=' | '>=' | 'instanceof';
Semantics

For a given relational expression e with e.op{<,>,<=,>=} in N4JS mode, the following constraints must hold:

  • The operands must have the same type and the type must be either a number, string, or boolean:

    Γrhs:TTnumber,string,booleanΓlhs ('<'|'<='|'>'|'>=')rhs  lhs:T
    Γrhs:OOnumber,string,booleanT=unionnumber,string,booleanΓlhs ('<'|'<='|'>'|'>=')rhs  lhs:T
    Γlhs:TTnumber,string,booleanΓlhs ('<'|'<='|'>'|'>=')rhs  rhs:T
    Γlhs:OOnumber,string,booleanT=unionnumber,string,booleanΓlhs ('<'|'<='|'>'|'>=')rhs  rhs:T

Req. IDE-114: Instanceof Operator Constraints (ver. 1)

For a given relational expression e with e.op=instanceof, the following constraints must hold:

  • The right operand of the instanceof operator must be a Function [49]

In other words,

Γlhs ’instanceof’ rhs  rhs:typeClass

is contained in the the first type rule, an object type reference [50] or an enum type reference.

Γlhs ’instanceof’ rhs  rhs:FunctionΓlhs ’instanceof’ rhs  rhs:typeObjectΓlhs ’instanceof’ rhs  rhs:typeN4Enum

The type of a definition site structural classifier C is not of type C. Thus, the instanceof operator cannot be used for structural types. Use-site structural typing is also not possible since ~ would be interpreted (by the parser) as a binary operator.

Req. IDE-115: Operator Constraints (ver. 1)

For a given relational expression e with e.op=in, the following constraints must hold:

  1. The right operand of the in operator must be an Object:

    Γlhs ’in’ rhs  rhs:Object
  2. In N4JS mode, the left operand is restricted to be of type string or number:

    Γlhs ’in’ rhs  lhs:unionstring,number

A special feature of N4JS is support for interface type references in combination with the instance of operator. The compiler rewrites the code to make this work.

Example 89. instanceof with Interface

The following example demonstrates the use of the operator with an interface. This is, of course, not working in pure ECMAScript.

interface I {}

class A implements I {}
class B extends A {}
class C {}

function f(name: string, p: any) {
    if (p instanceof I) {
        console.log(name + " is instance of I");
    }
}

f("A", new A())
f("B", new B())
f("C", new C())

This will print out

A is instance of I
B is instance of I
Type Inference

The type of a relational expression always is boolean;

Γlhs ’¡’|’¡=’|’¿’|’¿=’|’instanceof’|’in’ rhs :boolean

8.1.17. Equality Expression

Syntax
EqualityExpression returns Expression: RelationalExpression
    (=>({EqualityExpression.lhs=current} op=EqualityOperator) rhs=RelationalExpression)*;

EqualityExpressionNoIn returns Expression: RelationalExpressionNoIn
    (=>({EqualityExpression.lhs=current} op=EqualityOperator) rhs=RelationalExpressionNoIn)*;


enum EqualityOperator: same='===' | nsame='!==' | eq='==' | neq='!=';
Semantics

There are no hard constraints defined for equality expressions.

In N4JSmode, a warning is created if for a given expression lhs(’===’—’!==’)rhs, neither Γlhs.upper<:rhs.upper nor Γrhs.upper<:lhs.upper and no interface or composed type is involved as the result is constant in these cases.

Note that a warning is only created if the upper bounds do not match the described constraints. This is necessary for wildcards. For example in

// with
class A{} class B extends A{}
function isFirst(ar: Array<? extends A>, b: B): boolean {
    return b === ar[0]
}

the type of array elements is ? extends A.
Neither ? extends A<:B nor B<:? extends A is true. This is why the upper bounds are to be used.

Type Inference

The inferred type of an equality expression always is boolean.

Γlhs ’==’|’!=’|’===’|’!==’ rhs :boolean

8.1.18. Binary Bitwise Expression

Syntax
BitwiseANDExpression returns Expression: EqualityExpression
    (=> ({BitwiseANDExpression.lhs=current} '&') rhs=EqualityExpression)*;

BitwiseANDExpressionNoIn returns Expression: EqualityExpressionNoIn
    (=> ({BitwiseANDExpression.lhs=current} '&') rhs=EqualityExpressionNoIn)*;

BitwiseXORExpression returns Expression: BitwiseANDExpression
    (=> ({BitwiseXORExpression.lhs=current} '^') rhs=BitwiseANDExpression)*;

BitwiseXORExpressionNoIn returns Expression: BitwiseANDExpressionNoIn
    (=> ({BitwiseXORExpression.lhs=current} '^') rhs=BitwiseANDExpressionNoIn)*;

BitwiseORExpression returns Expression: BitwiseXORExpression
    (=> ({BitwiseORExpression.lhs=current} '|') rhs=BitwiseXORExpression)*;

BitwiseORExpressionNoIn returns Expression: BitwiseXORExpressionNoIn
    (=> ({BitwiseORExpression.lhs=current} '|') rhs=BitwiseXORExpressionNoIn)*;
Semantics

For a given bitwise bitwise expression e the following constraints must hold in N4JS mode:

  • The types of the operands must be both number.

ΓBitwiseBitwiseExpression  Expression:number
Type Inference

The type returned by a binary bitwise expression is always number:

8.1.19. Binary Logical Expression

Syntax
LogicalANDExpression returns Expression: BitwiseORExpression
    (=> ({LogicalANDExpression.lhs=current} '&&') rhs=BitwiseORExpression)*;
LogicalANDExpressionNoIn returns Expression: BitwiseORExpressionNoIn
    (=> ({LogicalANDExpression.lhs=current} '&&') rhs=BitwiseORExpressionNoIn)*;

LogicalORExpression returns Expression: LogicalANDExpression
    (=> ({LogicalORExpression.lhs=current} '||') rhs=LogicalANDExpression)*;
LogicalORExpressionNoIn returns Expression: LogicalANDExpressionNoIn
    (=> ({LogicalORExpression.lhs=current} '||') rhs=LogicalANDExpressionNoIn)*;
Semantics

Req. IDE-117: Binary Logical Expression Constraints (ver. 1)

For a given binary logical expression e with e.lhs.type:L and e.rhs.type:R the following constraints must hold:

  • In N4JS mode L must not be undefined or null.

Type Inference

The evaluation relies on ECMAScript’s abstract operation ToBoolean [ECMA11a(p.p.43)]. A short-circuit evaluation strategy is used so that depending on the types of the operands, different result types may be inferred. In particular, the inferred type usually is not boolean ((cf. [ECMA11a(p.S11.11., p.p.83ff)] ). The type inference does not take this short-circuit evaluation strategy into account, as it will affect the result in case one of the types is null either or undefined, which is not allowed in N4JS mode.

Γlhs&&’—’——’rhs:unionΓlhsΓrhs

8.1.20. Conditional Expression

Syntax
ConditionalExpression returns Expression: LogicalORExpression
    (=> ({ConditionalExpression.expression=current} '?') trueExpression=AssignmentExpression  ':' falseExpression=AssignmentExpression)?;

ConditionalExpressionNoIn returns Expression: LogicalORExpressionNoIn
    (=> ({ConditionalExpression.expression=current} '?') trueExpression=AssignmentExpression  ':' falseExpression=AssignmentExpressionNoIn)?;
Semantics

Req. IDE-118: Conditional Expression Constraints (ver. 1)

For a given conditional expression e with

e.expression.type:C,
e.trueExpression.type:T,
e.false-Expression.type:F

the following constraints must hold:

  • A warning will be issued in N4JSmode if e.expression evaluates to a constant value. That is to say
    e.expressionfalsetruenullundefined or Cvoidundefined.

There are no specific constraints defined for the condition. The ECMAScript operation ToBoolean [ECMA11a(p.S9.2, p.p.43)] is used to convert any type to boolean.

Type Inference

The inferred type of a conditional expression is the union of the true and false expression (cf. [ECMA11a(p.S11.12, p.p.84)] ():

T=unionΓet,ΓefΓcond ’?’et ’:’ef:T
Example 90. Type of Conditional Expressions

Given the following declarations:

class A{}       class B extends A{}
class C{}       class D extends A{}
class G<T> { field: T; }

var ga: G<A>, gb: G<B>;
    a: A, b: B, c: C, d: D;
var boolean cond;

Then the type of the following conditional expression is inferred as noted in the comments:

cond ? a : a;                           // A
cond ? a : b;                           // union{A,B}
cond ? a : c;                           // union{A,C}
cond ? b : d;                           // union{B,D}
cond ? (cond ? a : b) : (cond ? c : d); // union{A,B,C,D}
cond ? (cond ? a : b) : (cond ? b : d); // union{A,B,D}
cond ? ga : gb;                         // union{G<A>,G<B>}

8.1.21. Assignment Expression

Syntax
AssignmentExpression <In, Yield>:
    lhs=Expression op=AssignmentOperator rhs=AssignmentExpression<In,Yield>
;
AssignmentOperator:
      '='
    | '*=' | '/=' | '%=' | '+=' | '-='
    | '<<=' | '>>=' | '>>>='
    | '&=' | '^=' | '|='
;
Semantics

Req. IDE-119: Simple Assignment (ver. 1)

For a given assignment assignment with

assignment.op=’=’

the following constraints must hold:

  1. [[assignment.lhs]]<:[[assignment.rhs]]

    In the following inference rule and the constraint, ’@’ is to be replaced with the right part of one of the assignment operators listed above, that is,

Req. IDE-120: Compound Assignment (ver. 1)

For a given assignment left op right, with op=’@=’ but not +=, both, left and right must be subtypes of number.
For operator +=,

  • if the left-hand side is a number, then left ’+’right must return a number as well. The right-hand side must, in fact, be a number (and not a boolean) here in order to avoid unexpected results.

  • if the left-hand side is a string, then left’+’right must return a string as well. That means that the right-hand side can be of any type.

The expected type for the left-hand side is union{number,string}.

The basic idea behind these constraints is that the type of the left-hand side is not to be changed by the compound assignment.

Req. IDE-121: Write Acccess (ver. 1)

For a given assignment expression assignExpr, the left-hand side must be writeable or a final data field and the assignment must be in the constructor. Let v be the bound variable (or field) with bindassignExpr.leftv

v.writeablev.finalv.expr=nullassignExpr.containingFunction=v.owner.constructorμassignExpr.left=PropertyAccessassignExpr.left.target="this"

The value of writeable is true for setters and usually for variables and data fields. Assignability of variables and data fields can be restricted via const or the @Final annotation. See Assignment Modifiers(data fields) and Const (const variables) for details.

Also see [Req-IDE-93] for read access constraint.

The left-hand side of an assignment expression may be an array or object literal and the assignment expression is then treated as a destructuring assignment. See Array and Object Destructuring for details.

Type Inference

Similarly to [ECMA11a(p.S11.1, p.p.84ff)], we define type inference for simple assignment (=) and compound assignment (op=) individually.

The type of the assignment is simply the type of the right-hand side:

Γright:TΓleft ’=’right:T

Compound assignments are reduced to the former by splitting an operator @=, in which @ is a simple operator, into a simple operator expression with operator @ and a simple assignment =. Since the type of the latter is the right-hand side, we can define:

Γleft ’@’right:TΓleft ’@=’right:T

8.1.22. Comma Expression

Syntax
CommaExpression <In, Yield>:
    exprs+=AssignmentExpression<In,Yield> ',' exprs+=AssignmentExpression<In,Yield>
    (','    exprs+=AssignmentExpression<In,Yield>)*
;
Semantics

All expressions will be evaluated even though only the value of the last expression will be the result.

Example 91. Comma Expression

Assignment expressions preceed comma expressions:

var b: boolean;
b = (12, 34, true); // ok, b=true
b =  12, 34, true ; // error, b=12 is invalid
Type Inference

The type of a comma expression cexpr is inferred to the last expression:

n=|cexpr.exprs|,Γcexpr.exprsn:TΓcexpr:T

8.2. ECMAScript 6 Expressions

8.2.1. The super Keyword

SuperLiteral: {SuperLiteral} 'super';

Apart from the use of keyword super in wildcards of type expressions (cf. Type Expressions), there are two use cases for keyword super: super member access and super constructor calls.

Example 92. Super Keyword

Two use cases for keyword super:

class B extends A {
    constructor() {
        // super call
        super();
    }
    @Override
    m();: void {
        // super member access
        super.m();
    }
}
Semantics

super can be used to access the supertype’s constructor, methods, getters and setters. The supertype is defined lexically, which is different from how this works.[51]

Note that in [ECMA15a] Chapter 12.3.5 The Super Keyword, super is defined as a keyword but the syntax and semantics are defined in conjunction of member access.

Req. IDE-122: Type of Super is Always Nominal (ver. 1)

The type referenced with the super literal is always nominal. This is a consequence of references to types in extend clauses to be nominal.

Γsuper:TT.typingStrategy=nominal

If the super literal s is used to access the super constructor of a class, all of the following constraints must hold:

  1. The super constructor access must be a call expression:

    μcexpr=CallExpressionc.target=cexpr
  2. The super constructor call must be the expression of an expression statement exprStmt:

    exprStmt=cexpr.containerμcexpr.container=ExpressionStatement
  3. The containing statement stmtExpr must be directly contained in a constructor body:

    μexprStmt.containingFunction=Constructor) exprStmt.container=exprStmt.containingFunction.body

  4. There must be no access to and not return statement before the containing statement exprStmt.

    Let si be the index of exprStmt in the constructor body:

    exprStmt.container.stmtssi=exprStmt

    Then, the following constraint must hold: [52]

    i<si:element*exprStmt.container.stmtsi: μiThisLiteral, ReturnStatement

Further constraints with regard to super constructor calls are described in Constructor and Classifier Type.

If the super literal s is used to access a member of the super class, all of the following constraints must hold, with c=s.container.container

  1. The super literal must be the receiver of a method call (cf. remarks below):

    μc=CallExpressionc.target=PropertyAccessExpressionc.target.target=s
  2. The super literal is used in a method or field accessor of a class:

μs..containingClass=Class 3. The super literal must not be used in a nested function expression:

+ μs.containingFunction=s.containingMethodOrFieldAccessor 4. If the return type of the method access via super is this, the actually bound this type will be the type of the calling class (and not of the class defining the method).

+

s.containingClass=Tμm=Methodm.returnType=thisfunction():T<:s.m

Req. IDE-125: Super Literal Usage (ver. 1)

For super literals, either [Req-IDE-123] or [Req-IDE-124] must hold, no other usage is allowed.

Consequences:

  • Since fields cannot be overridden (except for changing the access modifier), it is not possible nor allowed to access a field via super.

  • Super literals must not be used with index access (e.g., super["foo"])

  • It is not possible to chain super keywords. That is, it is not possible to call super.super.m().

  • It is not allowed to use the super literal in interfaces or non-methods/accessors.

  • Super cannot be used to call an overridden method of an implemented method from the overriding method in the implementing class.

  • In order to be able to access a super method of a method M of a class C, exactly one non-abstract super method M' in a super class S of C must exist. This is assured by the standard rules for binding identifiers.

If super is used to access a super member, the receiver type is not changed. This is important in particular for static methods as demonstrated in the following example:

Example 93. Super Call in Static Methods
class A {
    static foo(): void { console.log("A") }
    static bar(): void {
        this.foo();
    }
}

class B extends A {

    @Override
    static foo(): void { console.log("B") }
    @Override
    static bar(): void {
        A.bar();        (1)
        super.bar();    (2)
    }
}

B.bar();
1 The receiver (which is similar to the this-binding in ECMAScript) is changed to A.
2 Using super, the receiver is preserved, i.e. B.

8.3. ECMAScript 7 Expressions

8.3.1. Await Expression

In N4JS, await is implemented as a unary operator with the same precedence as yield in ECMAScript 6.

Constraints governing the use of await are given together with those for async in Asynchronous Functions.

8.4. N4JS Specific Expressions

8.4.1. Class Expression

A class expression in N4JS is similar to a class expression in ECMAScript 6 [ECMA15a(p.14.5)].

Class expressions are not part of version 0.3
Syntax

See Classes.

Semantics and Type Inference

The inferred type of a class expression simply is the class type as described in Constructor and Classifier Type.

8.4.2. Cast (As) Expression

Syntax
CastExpression <Yield> returns Expression: expression=Expression 'as' targetTypeRef=TypeRefForCast;

TypeRefForCast returns StaticBaseTypeRef:
      ParameterizedTypeRef
    | ThisTypeRef
    | ConstructorTypeRef
    | ClassifierTypeRef
    | FunctionTypeExpression
    | UnionTypeExpression
    | IntersectionTypeExpression
8.4.2.1. Semantics and Type Inference

The inferred type of the type cast expression is the target type:

Γexpr "as" T:T

The type cast returns the expression without further modifications. Type casts are simply removed during compilation so there will be no exceptions thrown at the cast until later when accessing properties which may not be present in case of a failed cast.

An error is issued if the cast is either unnecessary or cannot succeed. See further details in Type Cast.

8.4.3. Import Calls

Import calls as specified by the corresponding ECMA TC39 proposal are available in N4JS. Such an import call has the form

import(moduleSpecifier)

and may appear in the source code wherever an expression may appear. It’s argument need not be a string literal, as is the case with module specifiers of ordinary imports; instead, any expression that evaluates to a string at runtime is accepted. Hence, it can be used to import from a target module that is not yet known at compile time.

A note on terminology: import calls covered in this section are sometimes referred to as "dynamic import". In N4JS that term is already used for imports of the form import * as N+ from "…​", i.e. compile-time imports that do not require type information of the module imported from, see Dynamic Imports, and stems from the term "dynamic type" (see Dynamic). To avoid confusion, we will usually avoid referring to import calls as a "dynamic import".

8.5. Compile-Time Expressions

A compile-time expression is an expression that can be fully evaluated at compile time. Not all expressions introduced in the previous sections qualify as compile-time expressions. Some forms of expressions always qualify (e.g. a string literal is always a compile-time expression), some never (e.g. call expressions), and for some expressions the operands must be of a certain value. The latter applies, for example, to divison: 5 / 0 is a valid ECMAScript expression (evaluating to NaN) but is not a compile-time expression. So it’s the actual compile-time value of the divisor that makes the difference, here. In any case, if an expression has operands, it is a compile-time expression only if all operands are compile-time expressions.

The value a compile-time expression evaluates to at compile-time is called compile-time value. So, an expression has a compile-time value if and only if it is a compile-time expression.

The following expressions are called compile-time expressions:

  • undefined (but not NaN or Infinity).

  • the null literal.

  • all boolean, numeric, and string literals.

  • template string literals, iff all embedded expressions are compile-time expressions.

  • a parenthesis expression, iff its nested expression is a compile-time expression.

  • unary expressions in case of the following operators:

    • ! iff the operand is a compile-time expression and evaluates to a boolean value.

    • + iff the operand is a compile-time expression and evaluates to a numeric value.

    • - iff the operand is a compile-time expression and evaluates to a numeric value.

    • void.

  • binary expressions in case of the following operators:

    • + iff both operands are compile-time expressions and

      • both evaluate to numeric values, or

      • at least one evaluates to a string value.

    • -, * iff both operands are compile-time expressions and evaluate to numeric values.

    • /, % iff both operands are compile-time expressions and evaluate to numeric values and the right-hand operand is non-zero (i.e. division by zero is disallowed in compile-time expression, because NaN is not a supported compile-time value).

    • &&, || iff both operands are compile-time expressions and evaluate to boolean values.

  • a tertiary conditional expression, iff the first operand is a compile-time expression evaluating to a boolean value B and

    • in case B is true, the second operand is a compile-time expression.

    • in case B is false, the third operand is a compile-time expression.

  • an identifier reference to a const variable, iff its initializer expression is a compile-time expression.

  • a property access expression, iff it is direct (see Direct Property Access) and refers to

    • a built-in symbol, e.g. Symbol.iterator,

    • a literal of a @StringBased enum, or

    • a const field with a compile-time initializer expression.

In all other cases, the expression is not a compile-time expression.

Every expression in the code may be a compile-time expression, but in most places this has no particular effect and is simply ignored. They are of significance only in computed property names, in index access expressions, as initializers of const variables and fields (as stated above) and when nested as an operand inside an expression at these locations.

Quick Links