Last Updated: 2019-08-08

Authors:
Jens von Pilgrim, Jakub Siberski, Mark-Oliver Reiser, Torsten Krämer, Ákos Kitta, Sebastian Zarnekow, Lorenzo Bettini, Jörg Reichert, Kristian Duske, Marcus Mews, Minh Quang Tran, Luca Beurer-Kellner

Abstract

This document contains the N4JS Specification.

1. Introduction

This specification defines the N4JS language.

In general, the N4JS JavaScript dialect used is identical to the standard ECMAScript as defined in the 6th edition of ECMA-262, also known as ECMAScript 2015, referred to as [ECMA15a].

1.1. Notation

1.1.1. Grammar Notation

For the specification of the syntax and structure of elements, we use a slightly augmented similar to the grammar language of Xtext Grammar Language.

Similar to [ECMA11a], we define types with properties only for the purpose of explanation and usage within this specification. We use the Xtext notation style to assign values to meta-properties. Particularly, we use the Xtext notation for collection (+=) and boolean (?=) values. These properties are written in italics. Enumerations are defined similar to Xtext. In order to allow the specification of default values, which are often defined by omitting the value, we always define the literal explicitly if it can be defined by the user.

The following lists informally defines the grammar:

Terminal

Terminals (or terminal strings) are enclosed in single quotes, e.g., terminal.

Enumerations

Rules which contain only terminals used as values for properties are marked with enum for enumeration.

Properties

Values of non-terminals, e.g., other rules, can be assigned to properties. The property name and the assignment are not part of the original syntax and only used for the meta description. E.g., name=Identifier.

Collection Properties

If a property is a collection, values are added to that list via +=. E.g.,property+=Value .

Boolean Properties

Boolean properties are set to false by default, if the value (usually a terminal) is found, the boolean value is set to true. Often, the name of the property is similar to the terminal. E.g., final?='final'?.

Properties of a non-terminal are sometimes listed again below the grammar. In that case, often pseudo properties are introduced which are derived from other properties and which are only used for simplification.

1.1.2. Type Judgments and Rules and Constraints Notation

1.1.2.1. Typing Rules and Judgments

Definition: Rule

We use the common notation for rules such as type inference rules [1], that is

$\frac{premises}{conclusion}\phantom{\rule{5.0mm}{0ex}}\text{rule name}$

$premises$ is the rule’s premises (e.g., the expression to be inferred), $conclusion$ the result of the rule. $rulename$ is an optional condition which may be omitted.

Both parts of the rule may contain multiple expressions, which are concatenated via 'and'.

For example, the following

$\frac{{P}_{1}\phantom{\rule{5.0mm}{0ex}}{P}_{2}\phantom{\rule{5.0mm}{0ex}}{P}_{3}}{C}$

if ${P}_{1}$, ${P}_{2}$, and ${P}_{3}$ are all true, then $C$ is true as well.

The following judgments (with relation symbols) are used:

subtype $<$

-

type $\text{:}$

in which the left hand side is a declaration or expression, and the right hand side a type. We also use $\left[\phantom{\rule{-0.167em}{0ex}}\left[.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}\right]\phantom{\rule{-0.167em}{0ex}}\right]$ as a function returning the (inferred) type of an expression.

expectedTypeIn $⊲:$

a relation with three arguments: $container⊲expression:type$ means, that $expression$ is expected to be a subtype of $type$ inside $container$

The following statement, for example, defines transitivity of subtypes (in a simplified manner):

$\frac{\Gamma ⊢B\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}A\phantom{\rule{5.0mm}{0ex}}\Gamma ⊢C\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}B}{\Gamma ⊢C\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}A}$

$⊢$ is the context containing (bound) type variables etc., $⊢$ can be read as entails. Thus, the rule can be read as follows:

if the type B is a subtype of type A in context $\Gamma$ (i.e. with constraints on type variables specified in $\Gamma$), and if type C is a subtype of B, then C is also a subtype of A in context $\Gamma$.

In rules, we sometimes omit the environment if it is not needed. New information is sometimes added to the environment, in particular, substitutions (that is binding type variables to a type). The set of substitutions is written with $\theta$ (theta). If new substitutions are explicitly added to that set, we write $\theta \left(V←T\right)$ ($V$ is substituted with type $T$). Often, these bindings are computed from a parameterized type reference which declares type arguments which are bound to the type variables of the generic declaration. In this case we simply write $\theta \left(p\right)$, in which $p$ is the parameterized type declaration. As these new substitutions must become part of a (newly) created environment, we then usually write $\Gamma ←\theta \left(p\right)$. These substitutions are usually omitted.

1.1.2.2. Types of an Element

A variable or other typed element may be associated with three types:

1. Declared type: the type explicitly specified in the code, e.g., var s: string.

2. Inferred type: the type inferred by the type inferencer, e.g., var s = "Hello" infers the type of s to string. I.e. $\Gamma ⊢s:string$ will be true, or $\left[\phantom{\rule{-0.167em}{0ex}}\left[s\right]\phantom{\rule{-0.167em}{0ex}}\right]\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}string$. If an element is annotated with a type ,i.e. it has a declared type, the inferred type will always be the declared type.

3. Actual type: the actual type of a variable during runtime. This type information is not available at compile time and ignored in this specification.

These types are not type declarations but type references, in fact, as they may be parameterized. For the sake of simplicity, we often omit the $Ref$ suffix to shorten formulas. Consequently, we define the following properties and pseudo properties for typed elements such as variables:

declaredType$Ref$

The explicitly declared type, this is usually a real property of the construct. Not all elements allow the specification of a declared type, such as expressions.

inferredType$Ref$ or $\left[\phantom{\rule{-0.167em}{0ex}}\left[.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}\right]\phantom{\rule{-0.167em}{0ex}}\right]$

This pseudo property is the inferred type computed by the type inferencer.

type$Ref$

A pseudo property for elements with a $declaredType$ property. It is similar to the inferred type, i.e. $e.type=\left[\phantom{\rule{-0.167em}{0ex}}\left[e\right]\phantom{\rule{-0.167em}{0ex}}\right]$

1.2. Auxiliary Functions

This section describes some auxiliary functions required for definition of type inference rules later on.

1.2.1. Binding

Binding an identifier (variable reference) to a variable declaration (or variable definition) is not part of this specification as this is standard ECMAScript functionality. However, some valid ECMAScript bindings are permitted due to visibility constraints.

Definition: Binding Relation

We define a pseudo relation

$bind:VariableReference×VariableDeclaration$

which binds a reference, i.e. an identifier, to a declaration (e.g.,variable declaration).

Binding of variable references to declaration is defined by ECMAScript already. Type references only occur in type expressions, how these are handled is explained in Type Expressions.

We usually omit this binding mechanism in most rules and use the reference similarly to the declaration or definition it is bound to. If a variable reference $r$, for example, is bound to a variable declaration $D$, i.e. $bind\left(r,D\right)$, we simply write $r.type$ instead of $bind\left(r,D\right),D.type$ to refer to the type expression (of the variable).[2]

A DeclaredType references the type declaration by its simple name that has been imported from a module specifier. We define the method $bind$ for declared types as well:

We define a pseudo relation

$bind:DeclaredType×Class|Interface|Enum$

which binds a type reference, i.e. a simple name, to the type declaration.

1.2.2. Merging Types

In some cases we have to merge types, e.g., types of a union type or item types of an array. For that purpose, we define a method $merge$ as follows.

Definition: Merge Function

We define a pseudo function

$merge:Type×.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}×Type⇒\mathcal{P}\left(Type\right)$

The idea of this function is to remove duplicates. For example; if a union type contains two type expressions $t{e}_{1}$ and $t{e}_{k}$, and if $\tau \left(t{e}_{1}\right)=\tau \left(t{e}_{2}\right)$, then $merge\left(\tau \left(t{e}_{1}\right),\tau \left(t{e}_{2}\right)\right)$ contains only one element. The order of the elements is lost, however.

1.2.2.1. Logic Formulas

In general, we use a pragmatic mixture of pseudo code, predicate logic, and OCL. Within constraints (also within the inference rules), the properties defined in the grammar are used.

In some rules, it is necessary to type the rule variables. Instead of explicitly checking the metatype (via $\mu \left(X\right)=:MetaType$), we precede the variable with the type, that is: $MetaTypeX$.

Instead of "type casting" elements, often properties are simply accessed. If an element does not define that element, it is either assumed to be false or null by default.

If a property $p$ is optional and not set, we write $p=null$ to test its absence. Note that $p=null$ is different from $p=Null$, as the latter refers to the null type. Non-terminals may implicitly be subclasses. In that case, the concrete non-terminal, or type, of a property may be subject for a test in a constraint.

1.2.3. Symbols and Font Convention

Variables and their properties are printed in italic when used in formulas (such as rules). A dot-notation is used for member access, e.g. $v.name$. Also defined functions are printed in italic, e.g., $acc\left(r,D\right)$. Properties which define sets are usually ordered and we assume 0-indexed access to elements, the index subscripted, e.g., $v.method{s}_{i}$.

We use the following symbols and font conventions:

$\wedge$, $\vee$, $\oplus$, $¬$

Logical and, or, exclusive or (xor), and not.

$⇒$, $⇔$, $\text{if}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}$, $\text{then}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}$, $\text{else}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}$

Logical implication, if and only if, and if-then-else.

$\text{true}$, $\text{false}$, $\text{null}$, $\varnothing$

Boolean true, boolean false, null (i.e., not specified, e.g., $v.sup=$ means that there are is no $sup$ (super class) specified), empty set.

$\in$, $\notin$, $\cup$, $\cap$, $|x|$

Element of, not an element of, union set, intersection set, cardinality of set x.

$\mathcal{P}\left(X\right)$

Power set of $X$, i.e. $\mathcal{P}\left(X\right)=\left\{U:U\subseteq X\right\}$.

$\exists$, $\nexists$, $\forall$

Exists, not exists, for all; we write $\exists x,.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}},z:P\left(x,.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}},z\right)$ and say

"there exists $x,.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}},z$ such that predicate $P$ is true".

Note that $\nexists x:P\left(x\right)⇔\forall x:¬P\left(x\right)$.

$\mu \left(.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}\right)$

(mu) read "metatype of"; metatype of a variable or property, e.g.,

$\text{if}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}\mu \left(x\right)=:Class\text{then}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}\text{else}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}$
$\overline{x}$

Sequence of elements ${x}_{1},.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}},{x}_{n}$. E.g., if we want to define a constraint that the owner of a members of a class $C$ is the class, we simply write

$C.\overline{members}.owner=C$

$\forall m\in C.members:m.owner=C$

or even more complicated with index variables.

Sequences are 1-based, e.g., a sequence $s$ with length $|s|=n$, has elements ${s}_{1},.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}},{s}_{n}$.

2. Grammar

2.1. Lexical Conventions

As a super language on top of ECMAScript, the same lexical conventions are supported as described in [ECMA11a(p.S7)] within strict mode. Some further constraints are defined, however, restricting certain constructs. These constraints are described in the following.

2.1.1. Identifier Names and Identifiers

As a reminder, identifiers are defined as follows in the ECMAScript specification:

IdentifierName: IdentifierStart* IdentifierPart;
IdentifierStart : UnicodeLetter | '_';
\ UnicodeEscapeSequence

N4JS supports a limited form of computed-names for member declarations:

N4JSPropertyComputedName:
'[' (SymbolLiteralComputedName | StringLiteralComputedName) ']'
;

SymbolLiteralComputedName: N4JSIdentifier '.' N4JSIdentifier ;

StringLiteralComputedName: STRING ;

As can be seen, a computed-name must be either

• a symbol reference, e.g., Symbol.iterator

• a string literal, i.e., a compile time known constant. This notation is useful when interoperating with libraries that define members whose names contain special characters (e.g., a field name starting with commercial-at)

In N4JS, identifiers are further constrained in order to avoid ambiguities and to make code more readable. Some of these constraints will lead to errors, others only to warnings. They do not apply for identifiers declared in definitions file (n4jsd) in order to enable declaration of external entities.

Req. IDE-1: N4JS Identifier Restrictions (ver. 1)

1. If the following constraints do not hold, errors are created.

5.2.3. Methods

Methods are simply JavaScript functions. They are defined similarly to methods as proposed in [ECMA15a(p.S13.5)] except for the type information and some modifiers.

5.2.3.1. Syntax
Syntax Method Declaration
N4MethodDeclaration <Yield>:
=> ({N4MethodDeclaration}
annotations+=Annotation*
accessModifier=N4JSMemberAccessModifier?
(abstract?=’abstract’ | static?=’static’)?
TypeVariables?
(
generator?='*' LiteralOrComputedPropertyName<Yield> -> MethodParamsReturnAndBody <Generator=true>
|   AsyncNoTrailingLineBreak LiteralOrComputedPropertyName<Yield> -> MethodParamsReturnAndBody <Generator=false>
)
) ';'?
;

fragment MethodParamsAndBody <Generator>*:
StrictFormalParameters<Yield=Generator>
(body=Block<Yield=Generator>)?
;

fragment MethodParamsReturnAndBody <Generator>*:
StrictFormalParameters<Yield=Generator>
(':' returnTypeRef=TypeRef)?
(body=Block<Yield=Generator>)?
;

fragment LiteralOrComputedPropertyName <Yield>*:
name=IdentifierName | name=STRING | name=NumericLiteralAsString
| '[' (=>((name=SymbolLiteralComputedName<Yield> | name=StringLiteralAsName) ']') | computeNameFrom=AssignmentExpression<In=true,Yield> ']')
;

SymbolLiteralComputedName <Yield>:
BindingIdentifier<Yield> ('.' IdentifierName)?
;

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

IdentifierName: IDENTIFIER | ReservedWord | N4Keyword;
NumericLiteralAsString: DOUBLE | INT | OCTAL_INT | HEX_INT | SCIENTIFIC_INT;
StringLiteralAsName: STRING;

fragment AsyncNoTrailingLineBreak *: (declaredAsync?='async' NoLineTerminator)?;  // See Asynchronous Functions

fragment StrictFormalParameters <Yield>*:
'(' (fpars+=FormalParameter<Yield> (',' fpars+=FormalParameter<Yield>)*)? ')'
;

FormalParameter <Yield>:
{FormalParameter} BindingElementFragment<Yield>
;

fragment BindingElementFragment <Yield>*:
(=> bindingPattern=BindingPattern<Yield>
| annotations+=Annotation*
(
)
)
('=' initializer=AssignmentExpression<In=true, Yield>)?
;

fragment ColonSepTypeRef*:
':' declaredTypeRef=TypeRef
;
5.2.3.2. Properties

Methods have all the properties of members and the following additional properties can be explicitly defined:

abstract

Method is declared but not defined.

typePars

Collection of type parameters of a generic method; empty by default.

returnTypeRef

Return type of the method, default return type is $Void$. The type of the method as a member of the owning classifier is not the method’s return type but is instead a function type.

fpars

List of formal parameters, may be left empty.

body

The body of the method (this is not available in the pure types model)

The following pseudo properties are defined via annotations:

final

Boolean flag set to true if annotation @Final is set. The flag indicates that method must not be overridden in subclasses; see Final Methods.

declaresOverride

Flag set to true if annotation @Overrides is set. The flag indicates that method must override a method of a superclass; see Overriding of Members.

Additionally, we define the following pseudo properties:

overrides

True if method overrides a super method or implements an interface method, false otherwise.

typeRef

Type of the method. This is, in fact, a function type (and not the return type).

The following pseudo property is set to make methods compatible with properties of an object literal, however it cannot be changed:

enumerable

Boolean flag reflecting the property descriptor $enumerable$, this is always set to false for methods.

5.2.3.3. Semantics

Since methods are ECMAScript functions, all constraints specified in Function Type apply to methods as well. This section describes default values and function type conformance which is required for overriding and implementing methods.

In addition, method declarations and definitions have to comply with the constraints for naming members of classifiers (cf. [Req-IDE-52]) and with the constraints detailed in the following sections on final methods (Final Methods), abstract methods (Abstract Methods and method overriding and implementation (Overriding of Members, Implementation of Members).

The following constraints are defined for methods in ECMAScript 6 [ECMA15a(p.207)]

Req. IDE-53: Method Definition ECMAScript 6 (ver. 1)

• It is a Syntax Error if any element of the BoundNames of StrictFormalParameters also occurs in the VarDeclaredNames of FunctionBody.

• It is a Syntax Error if any element of the BoundNames of StrictFormalParameters also occurs in the LexicallyDeclaredNames of FunctionBody.

Methods – like functions – define a variable execution environment and therefore provide access to the actual passed-in parameters through the implicit arguments variable inside of their bodies (c.f. Arguments Object).

Methods are similar to function definitions but they must not be assigned to or from variables. The following code issues an error although the type of the method would be compatible to the type of the variable v:

class C {
m(): void {}
}
var v: {function():void} = new C().m;

Req. IDE-54: Method Assignment (ver. 1)

1. In contrast to ECMAScript 2015, methods are defined as readonly, that is, it is not possible to dynamically re-assign a property defined as method with a new value. This is because assigning or re-assigning a method breaks encapsulation. Methods are the Acronyms of a class, their implementation is internal to the class.

2. When assigning a method to a variable, a warning is issued since this would lead to an detached this reference inside the method when it is called without explicitly providing the receiver. No warning is issued only if it is guaranteed that no problems will occur:

1. The method’s body can be determined at compile time (i.e., it has been declared @Final) and it lacks usages of this or super. This is true for instance and static methods.

2. The method is the constructor.

 The following code demonstrates problems arising when methods are assigned to variables in terms of function expressions. Given are two classes and instances of each class as follows:
class C {
m(): void { }
static k(): void {}
}
class D extends C {
@Override m(): void { this.f()}
f(): void {}

@Override static k(): void { this.f()}
static f(): void {}
}
var c: C = new C();
var d: C = new D(); // d looks like a C

Assigning an instance method to a variable could cause problems, as the method assumes this to be bound to the class in which it is defined. This may work in some cases, but will cause problems in particular in combination with method overriding:

var v1: {@This(C)function():void} = c.m;
var v2: {@This(C)function():void} = d.m;

v1.call(c);
v2.call(c);

Calling c.m indirectly via v1 with c as this object will work. However, it won’t work for v2: the method is overridden in D, and the method in expects other methods available in D but not in C. That is, the last call would lead to a runtime error as method f which is called in D.m won’t be available.

The same scenario occurs in case of static methods if they are retrieved polymorphically via the variables of type constructor{C}:

var ctor: constructor{C} = C;
var dtor: constructor{C} = D;

var v3: {@This(constructor{C})function():void} = ctor.k;
var v4: {@This(constructor{C})function():void} = dtor.k;

In both cases, the problem could be solved by restricting these kinds of assignments to final methods only. In the static case, the problem would also be solved by accessing the static method directly via the class type (and not polymorphically via the constructor). Both restrictions are severe but would be necessary to avoid unexpected runtime problems.

The following example shows a problem with breaking the encapsulation of a class.

class C {
x: any = "";
f(): void { this.g(this); }
g(c: C): void { c.h(); }
h(): void {}
}
class D extends C {

@Override f(): void {
this.g(this.x);
}
@Override g(c: any) {
// do nothing, do not call h())
}
}

var c = new C();
var d = new D();

var v5: {@This(C)function():void} = c.f;
var v6: {@This(C)function():void} = d.f;

v5.call(c)
v6.call(c)

In D, method g is overridden to accept more types as the original method defined in C. Calling this new method with receiver type C (as done in the last line) will cause problems, as in D not only f has been adapted but also g. Eventually, this would lead to a runtime error as well.

5.2.3.4. Final Methods

By default, methods can be overridden. To prevent a method from being overridden, it must be annotated with @Final.

Of course, a method cannot be declared both abstract and final (cf. [Req-IDE-46]). Private methods are implicitly declared final. Because static methods can be overridden in subclasses (which is different to Java), they also can be marked as final.

Default methods in interfaces, cf. Default Methods in Interfaces, may also be declared @Final.

Example 36. Final Methods in Interfaces

If a method in an interface is provided with a body, it may be declared final. This will ensure that the given method’s body will be in effect for all instances of the interface. Note that this means that;

1. a class implementing that interface must not define a method with the same name and

2. a class inheriting a method of that name cannot implement this interface.

The latter case is illustrated here:

interface I {
@Final m(): void {}
}

class C1 {
m(): void {}
}

// error at "I": "The method C1.m cannot override final method I.m."
class C2 extends C1 implements I {
}
5.2.3.5. Abstract Methods

A method can be declared without defining it, i.e. without providing a method body, and is then called an abstract method. Such methods must be declared with modifier abstract and have their property $abstract$ set to true. Constraints for abstract methods are covered in [Req-IDE-46] (see Abstract Classes).

In interfaces, methods are always abstract by default and they do not have to be marked as abstract. If a method in an interface provides a body, then this is the default implementation. See Implementation of Members about how the default implementation may be mixed in the consumer.

5.2.3.6. Generic Methods

Methods of generic classes can, of course, refer to the type variables defined by type parameters of the generic class. These type variables are used similarly to predefined or declared types. Additionally, methods may be declared generic independently from their containing class. That is to say that type parameters (with type variables) can be defined for methods as well, just like for generic functions (see Generic Functions).

For a given generic method M of a class C, the following constraint must hold:

Since type variables can be used similarly to types in the scope of a generic class, a generic method may refer to a type variable of its containing class.

class C {
<T> foo(p: T p): T { return p;}
};

If a generic type parameter is not used as a formal parameter type or the return type, a warning is generated unless the method overrides a member inherited from a super class or interface.

5.2.4. Default Methods in Interfaces

If a method declared in an interface defines a body, then this is the so-called default implementation and the method is called a default method. This will be mixed into an implementor of the interface if, and only if, neither the implementing class nor any of its direct or indirect superclasses already provides an implementation for this method; for details see Member Consumption. Since the implementor is not known, some constraints exist for the body. I.e., no access to super is possible, cf. [Req-IDE-124].

In order to declare an interface to provide a default implementation in a definition file, annotation @ProvidesDefaultImplementation can be used, cf. [Req-IDE-167].

When a method in an interface is provided with a default implementation, it may even be declared @Final, see Final Methods.

5.2.4.1. Asynchronous Methods

N4JS implements the async/await concept proposed for ECMAScript 7, which provides a more convenient and readable syntax for writing asynchronous code compared to using built-in type Promise directly. This concept can be applied to methods in exactly the same way as to declared functions. See Asynchronous Functions and Asynchronous Arrow Functions for details.

5.2.5. Constructors

A constructor is a special function defined on a class which returns an instance of that class. The constructor looks like a normal method with name "constructor". The constructor can be defined explicitly or implicitly and every class has an (implicit) constructor.

For a given a class C, the constructor is available via two properties:

$ownedCtor$

the explicitly defined constructor (if any).

$ctor$

the explicit or implicit constructor.

If C is provided with an explicit constructor, we have $C.ctor=C.ownedCtor$ and $C.ownedCtor\in C.ownedMembers$. Note that $C.ctor\notin C.ownedMethods$ in all cases.

The return type of the constructor of a class C is C. If C has type parameters ${T}_{1},.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}},{T}_{n}$, then the return type is $C<{T}_{1},.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}},{T}_{n}>$. The constructor is called with the operator. Since the return type of a constructor is implicitly defined by the class, it is to be omitted. By this definition, a constructor looks like the following:

class C {
public constructor(s: string) {
// init something
}
}

Constructors define a variable execution environment and therefore provide access to the actual passed-in parameters through the implicit variable inside of their bodies (c.f. Arguments Object).

Req. IDE-56: Defining and Calling Constructors (ver. 1)

For a constructor $ctor$ of a class C, the following conditions must hold:

1. $ctor$ must neither be abstract nor static nor final and it must not be annotated with @Override.

2. If a class does not explicitly define a constructor then the constructor’s signature of the superclass constructor is assumed.

3. If a class defines a constructor with formal parameters then this constructor has to be called explicitly in constructors defined in subclasses.

4. If a super constructor is called explicitly, this call must be the only expression of an expression statement which has to be the first statement of the body.

5. Constructors may appear in interfaces, but some restrictions apply:

1. constructors in interfaces must not have a body.

2. constructors in interfaces or their containing interface or one of its direct or indirect super interfaces must be annotated with @CovariantConstructor.

6. A constructor must not have an explicit return type declaration.

7. The implicit return type of a constructor is this?.

8. A constructor must not have any type parameters.

Properties of object literals may be called constructor. However they are not recognized as constructors in these cases.

1. Required attributes must be initialized:
$\forall a\in C.attr:a.required⇒\exists e\in r.elements:a.name=e.name$

Note on syntax: ECMAScript 6 defines constructors similarly, [ECMA15a(p.S13.5)]. In ECMAScript 6 the super constructor is not called automatically as well.

The super literal used in order to call super methods is further described in The super Keyword.

5.2.5.1. Structural This Type in Constructor

The use of a structural this reference as a formal parameter type is possible only in constructors. This parameter can be annotated with @Spec which causes the compiler to generate initialization code.

Simply using this as a type in the constructor causes the constructor to require an object providing all public fields of the class for initialization purposes. The fields have to be set manually as shown in the following code snippet.

class A{
public s: string;
public constructor(src: ~~this) {
this.s = src.s;
}
}

Remarks:

• The type of the formal parameter ~~this refers to the structural field type, see Structural Typing for details on structural typing. It contains all public fields of the type.

• Subclasses may override the constructor and introduce additional parameters. They have to call the super constructor explicitly, however, providing a parameter with at least all required attributes of the superclass. Usually the type this is replaced with the actual subclass, but in the case of a super() call the this type of structural formal parameters is replaced with the this type of the superclass, hence only required fields of the superclass must be present.

As with other structural references, it is possible to add the structural reference with additional structural members, which can be used to initialize private fields which become not automatically part of the structural field type. For example:

class A{
public s: string;
private myPrivateNumber: number;
public constructor(src: ~~this with { x: number; }) {
this.s = src.s;
this.myPrivateNumber = src.x;
}
}

Defining additional members may become a problem if a subclass defines public fields with the same name, as the ~~this type will contain these fields in the subclass. This is marked as an error in the subclass.

If the structural this type is used in a constructor of a class C, and if this structural reference contains an additional structural member $SM$, the following constraints must hold true:

1. For any subclass S of C, with $S.ctor=C.ctor$ (the subclass does not define its own constructor), S must not contain a public member with same name as $SM$:

$S<:C,S.ctor=C.ctor$ $\phantom{\rule{3.0em}{0ex}}⇒\nexists M\in S.members:$ $\phantom{\rule{5.0em}{0ex}}M.acc=\text{public}\wedge M.name=SM.name$

2. C itself must not contain a public member with same name as $SM$:

$\nexists M\in C.members:M.acc=\text{public}\wedge M.name=SM.name$
Example 37. Field name conflicts with structural member name

The situation described in [Req-IDE-58] is demonstrated in the following code fragment:

class A {
private myPrivateNumber: number;
public constructor(src: ~~this with { x: number; }) {
this.myPrivateNumber = src.x;
}
}

class B extends A {
public x: number; // will cause an error message
}
5.2.5.2. @Spec Constructor

The tedious process of copying the members of the parameter to the fields of the class can be automated via the @Spec annotation if the argument has ~i~this structural initializer field typing. More details about this typing can be found in Structural Read-only, Write-only and Initializer Field Typing. This can be used as shown in the following listing:

class A {
public field: string;
public constructor(@Spec spec: ~i~this) {}
}
let a = new A({field: 'hello'});
console.log(a.field); // prints: hello

The code for initializing the public field of A is automatically generated, thanks to the @Spec annotation being given in the constructor.

Req. IDE-59: @Spec Constructor (ver. 1)

1. Annotation @Spec may only appear on a formal parameter of a constructor. Such a formal parameter is then called @Spec parameter or simply spec parameter and its owning constructor is referred to as a @Spec constructor or spec constructor. An argument to the spec parameter is called spec object.

2. Only a single formal parameter of a constructor may be annotated with @Spec.

3. If a formal parameter is annotated with @Spec, the parameter’s type must be ~i~this (i.e. a use-site structural initializer field type of this, see Structural Read-only, Write-only and Initializer Field Typing).

4. Using the data provided in the spec object, i.e. in the argument to the spec parameter, a spec constructor will automatically initialize

1. all owned data fields and owned setters of the containing class, and

2. all data fields and setters from interfaces implemented by the containing class

if and only if those members are also part of the spec parameter’s structural initializer field type.

5. Fields explicitly added to the spec parameter, e.g. @Spec spec: ~i~this with {name:string}, are used for initialization if a non-public field of the same name exists in the class, either as an owned member or from an implemented interface. The type of such an additional field must be a subtype of the declared type of the field being initialized:

$\forall s\in ctor.fpar.structuralMembers,ctor.fpar.spec:$
$\phantom{\rule{2.0em}{0ex}}\exists f\in ctor.owner.ownedFields⇒\Gamma ⊢s\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}f$

6. Even if the @Spec annotation is used, the super constructor must be invoked explicitly (as usual).

It follows from no. 4 above that

1. non-public data fields and setters are never initialized (because they will never be part of the spec parameter’s structural initializer field type),

2. properties provided in the spec object but not defined in the parameter’s structural initializer field type, are not used for initialization, even if a (protected or private) field of the same name exists in the class,

3. data fields and setters inherited from a super class are never initialized by a spec constructor (instead, this will happen in the spec constructor of the super class).

The last of these implications will be detailed further at the end of the coming section.

@Spec Constructors and Inheritance

Spec constructors are inherited by subclasses that do not have a constructor and, when creating instances of the subclass, will then require properties for writable public fields of the subclass in the spec object and include initialization code for them.

class A {
public fa;
public constructor(@Spec spec: ~i~this) {}
}
class B extends A {
public fb;
}

const b = new B({fa: 'hello', fb: 'world'}); // requires & initializes fb too!
console.log(b.fa); // prints: hello
console.log(b.fb); // prints: world

Public writable fields from implemented interfaces are included as well, i.e. required as property in spec object and initialized by auto-generated code in the @Spec constructor:

interface I {
public fi;
}
class B implements I {
public fb;
public constructor(@Spec spec: ~i~this) {}
}

const a = new B({fb: 'hello', fi: 'world'}); // requires & initializes fi too!
console.log(a.fb); // prints: hello
console.log(a.fi); // prints: world

When having a spec constructor in a class B that extends a super class A without an owned or inherited spec constructor, it should be noted that the ~i~this type will require properties for public writable fields of A, but the initialization code automatically generated due to the @Spec annotation will not initialize those members. For public writable fields from an interface I implemented by B, however, both a property will be required by ~i~this and initialization code will be generated in the @Spec constructor. This is illustrated in the following code example.

class A {
public fa;
}
interface I {
public fi;
}
class B extends A implements I {
public fb;
public constructor(@Spec spec: ~i~this) { // <- fa, fi, fb required in spec object
// Constructor is responsible for initializing fa, fi, fb.
// The @Spec annotation will generate initialization code
// for fb and fi, but not for fa!
}
}

let b = new B({
fa: 'hello', // <- fa is required (removing it would be a compile error)
fi: 'world',
fb: '!!'
});

console.log(b.fa); // undefined
console.log(b.fi); // world
console.log(b.fb); // !!

The rationale for this different handling of fields from super classes and implemented interfaces is 1. fields from an implemented interface are not seen as inherited but rather implemented by implementing class, so from the @Spec annotation’s perspective the field is a field of the implementing class, and 2. in case of a field inherited from a super class the correct way of initialization may depend on details of the super class and has to be taken care of by custom code in the constructor of the subclass (usually by invoking the non-@Spec constructor of the superclass with super).

Special Use Cases

The following examples illustrate further details of other use cases of spec constructors.

Example 38. Anonymous Interface in Constructor

The base class A in the examples redefines the constructor already defined in N4Object. This is not generally necessary and is only used here to make the example legible.

class A {
public s: string;
public constructor(@Spec spec: ~i~this) {
// initialization of s is automatically generated
}
}
class B extends A {
public t: string;
private n: number;
public constructor(spec: ~~this with {n: number;}) {
super(spec);    // only inherited field s is set in super constructor
}
}
Example 39. Spec Object and Subclasses
class A1 {
public s: string;
public n: number;
public constructor(@Spec spec: ~i~this) {}
}
class B extends A1 {
public constructor() {
super({s:"Hello"}); // <-- error, n must be set in object literal
}
}
class C extends A1 {
public constructor() {
super({s:"Hello"}); // <-- error, n must be set in object literal
this.n = 10; // <-- this has no effect on the super constructor!
}
}

class A2 {
public s: string;
public n: number?; // now n is optional!
public constructor(@Spec spec: ~i~this) {}
}
class D extends A2 {
public constructor() {
super({s:"Hello"}); // and this is ok now!
this.n = 10; // this explains why it is optional
}
}

class A3 {
public s: string;
public n: number = 10; // now n is not required in ~~this
public constructor(@Spec spec: ~i~this) {}
}
class E extends A3 {
public constructor() {
super({s:"Hello"}); // and this is ok now!
}
}

The last case (class E) demonstrates a special feature of the typing strategy modifier in combination with the this type, see Structural Typing for details.

The constructor in class B contains an error because the super constructor expects all required attributes in A1 to be set. The additional initialization of the required field A1.n as seen in C does not change that expectation. In this example, the field n should not have been defined as required in the first place.

Optional fields like n? in class A2 or fields with default values like n=10 in class A3 are not required to be part of the spec object.

Example 40. Superfluous Properties in @Spec Constructors

Each non-$\text{public}$ field has to be set in the constructor via the $\text{with}$ to the parameter otherwise properties are not used to set non-$\text{public}$ fields.

class C {
public s: string;
n: number;
constructor(@Spec spec: ~i~this) {}
}

// n is ignored here
new C( { s: "Hello", n: 42 });

// but:
var ol = { s: "Hello", n: 42 };
// "ol may be used elsewhere, we cannot issue warning here" at "ol"
new C(ol) ;

// of course this is true for all superfluous properties
// weird is not used in constructor
new C( { s: "Hello", weird: true } );
Restriction when initializing interface fields via @Spec constructor

In most cases, interface definitions in n4jsd files simply declare functions and fields that are supposed to be provided by the runtime environment. As a result, there are restrictions as to whether fields of interfaces defined in n4jsd files can initialized via @Spec constructors or not. In particular, fields of an interface declared in a n4jsd file cannot be initialized via @Spec constructor if the interface

1. is a built-in or

2. does not have an @N4JS annotation

The following example illustrates this restriction.

Example 41. Interface fields that cannot be initialized via @Spec constructors
Inf.n4jsd
export external interface I  {
public m: string;
}

@N4JS
export external interface J  {
public n: string;
}
Test.n4js
import { I } from "Inf";
// I is an external interface WITHOUT @N4JS annotation
class C implements I {
constructor(@Spec spec:~i~this) {}
}

// J is an external interface with @N4JS annotation
class D implements J {
constructor(@Spec spec:~i~this) {}
}

// XPECT warnings --> "m is a property of built-in / provided by runtime / external without @N4JS annotation interface I and can not be initialized in Spec constructor." at "m"
let c:C = new C({m: "Hello"});

// XPECT nowarnings
let d:D = new D({n: "Bye"});

console.log(c.m)
console.log(d.n)

/* XPECT output ---
<==
stdout:
undefined
Bye
stderr:
==>
--- */

In this example, the interface I is defined in the Inf.n4jsd file without the @N4JS annotation. As a result, its field m cannot be initialized via the @Spec constructor and hence the output of console.log(c.m) is undefined. On the other hand, since the interface J is declared with the annotation @N4JS, it is possible to initialize its field n in the @Spec constructor. That’s why the result of console.log(d.n) is Bye.

5.2.5.4. Covariant Constructors

Usually, the constructor of a subclass need not be override compatible with the constructor of its super class. By way of annotation @CovariantConstructor it is possible to change this default behavior and enforce all subclasses to have constructors with override compatible signatures. A subclass can achieve this by either inheriting the constructor from the super class (which is usually override compatible, with the special case of @Spec constructors) or by defining a new constructor with a signature compatible to the inherited constructor. The same rules as for method overriding apply.

The @CovariantConstructor annotation may be applied to the constructor, the containing classifier, or both. It can also be used for interfaces; in fact, constructors are allowed in interfaces only if they themselves or the interface is annotated with @CovariantConstructor (see [Req-IDE-60]).

Definition: Covariant Constructor

A classifier C is said to have a covariant constructor if and only if one of the following applies:

1. C has a direct super class ${C}^{\text{'}}$ and ${C}^{\text{'}}$ is annotated with @CovariantConstructor or ${C}^{\text{'}}$ has a constructor annotated with @CovariantConstructor.

2. C has a directly implemented interface I and I is annotated with @CovariantConstructor or I has a constructor annotated with @CovariantConstructor.

3. C has a direct super class or directly implemented interface that has a covariant constructor (as defined here).

Note that C does not need to have an owned(!) constructor; also a constructor inherited from a super class can be declared covariant.

The following rules apply to covariant constructors.

Req. IDE-60: Covariant Constructors (ver. 1)

1. Annotation @CovariantConstructor may only be applied to classes, interfaces, and constructors. Annotating a constructor with this annotation, or its containing classifier, or both have all the same effect.

2. Given a class C with an owned constructor $ctor$ and a super class $Sup$ that has a covariant constructor (owned or inherited, see Covariant Constructors), then $Sup.constructor$ must be accessible from C,

1. $ctor$ must be override compatible with $S.constructor$:

$overrideCompatible\left(ctor,S.constructor\right)$

This constraint corresponds to [Req-IDE-72] except for the Override annotation which is not required here.

3. Given a classifier C implementing interface I and I has a covariant constructor (owned or inherited, see Covariant Constructors), we require

1. $I.constructor$ must be accessible from C,

2. an implementation-compatible constructor $ctor$ must be defined in C with

$overrideCompatible\left(ctor,I.constructor\right)$

This constraint corresponds to [Req-IDE-74] except for the @Override annotation, which is not required, here.

3. Given a classifier C without an owned constructor and an extended class or interface $Sup$ that has a covariant constructor (owned or inherited, see Covariant Constructors), we require the inherited constructor $ctor$ of C within the context of C to be override compatible to itself in the context of $Sup$. Using notation $m\left[T\right]$ to denote that a member M is to be treated as defined in container type T, which means the this-binding is set to T, we can write:

$overrideCompatible\left(ctor\left[C\right],ctor\left[Sup\right]\right)$

This constraint does not correspond to any of the constraints for the redefinition of ordinary members.

The following example demonstrates a use case for covariant constructors. It shows a small class hierarchy using covariant constructors, Cls and Cls2, together with a helper function createAnother that creates and returns a new instance of the same type as its argument value.

Example 42. Covariant Constructors
class A {}
class B extends A {}

@CovariantConstructor
class Cls {
constructor(p: B) {}
}
class Cls2 extends Cls {
constructor(p: A) { // it's legal to generalize the type of parameter 'p'
super(null);
}
}

function <T extends Cls> createAnother(value: T, p: B): T {
let ctor = value.constructor;
return new ctor(p);
}

let x = new Cls2(new A());
let y: Cls2;

y = createAnother(x, new B());

In the code of Covariant Constructors, we would get an error if we changed the type of parameter p in the constructor of Cls2 to some other type that is not a super type of B, i.e. the type of the corresponding parameter of Cls’s constructor. If we removed the @CovariantConstructor annotation on Cls, we would get an error in the new expression inside function createAnother.

The next example illustrates how to use @CovariantConstructor with interfaces and shows a behavior that might be surprising at first sight.

Example 43. Covariant Constructors in Interfaces
@CovariantConstructor
interface I {
constructor(p: number)
}

class C implements I {
// no constructor required!
}

class D extends C {
// XPECT errors --> "Signature of constructor of class D does not conform to overridden constructor of class N4Object: {function(number)} is not a subtype of {function()}." at "constructor"
constructor(p: number) {}
}

Interface I declares a covariant constructor expecting a single parameter of type number. Even though class C implements I, it does not need to define an owned constructor with such a parameter. According to [Req-IDE-60], it is enough for C to have a constructor, either owned or inherited, that is override compatible with the one declared by I. Class C inherits the default constructor from N4Object, which does not have any arguments and is thus override compatible to I’s constructor.

In addition, subclasses are now required to have constructors which are override compatible with the constructor of class C, i.e. the one inherited from N4Object. Covariant Constructors in Interfaces shows that this is violated even when repeating the exact same constructor signature from interface I, because that constructor now appears on the other side of the subtype test during checking override compatibility.

5.2.6. Data Fields

A data field is a simple property of a class. There must be no getter or setter defined with the same name as the data field. In ECMAScript 6, a class has no explicit data fields. It is possible, however, to implicitly define a data field by simply assigning a value to a variable of the this element (e.g. this.x = 10 implicitly defines a field x). Data fields in N4JS are similar to these implicit fields in ECMAScript 6 except that they are defined explicitly in order to simplify validation and user assistance.

5.2.6.1. Syntax
N4FieldDeclaration <Yield>:
{N4FieldDeclaration}
FieldDeclarationImpl<Yield>
;

fragment FieldDeclarationImpl <Yield>*:
(declaredModifiers+=N4Modifier)* BogusTypeRefFragment?
declaredName=LiteralOrComputedPropertyName<Yield>
(declaredOptional?='?')?
ColonSepTypeRef?
('=' expression=Expression<In=true,Yield>)?
Semi
;
5.2.6.2. Properties

Fields have the following properties which can be explicitly defined:

declaredOptional

Tells whether the accessor was declared optional.

typeRef

Type of the field; default value is $Any$.

expr

Initializer expression, i.e. sets default value.

static

Boolean flag set to true if field is a static field.

const

Boolean flag set to true if field cannot be changed. Note that const fields are automatically static. Const fields need an initializer. Also see Assignment Modifiers.

 $const$ is not the (reversed) value of the property descriptor $writable$ as the latter is checked at runtime while const may or may not be checked at runtime.

The following pseudo properties are defined via annotations for setting the values of the property descriptor:

enumerable

Boolean flag reflecting the property descriptor $enumerable$, set via annotation @Enumerable(true|false). The default value is $\text{true}$.[32]

declaredWriteable

Boolean flag reflecting the property descriptor $writeable$, set via annotation @Writeable(true|false). The default value is $\text{true}$.[33]

final

Boolean flag making the field read-only, and it must be set in the constructor. Also see Assignment Modifiers.

Derived values for fields
readable

Always true for fields.

abstract

Always false for fields.

writeable

Set to false if field is declared const or final. In the latter case, it may be set in the constructor (cf. Assignment Modifiers).

5.2.6.2.1. Semantics

Req. IDE-61: Attributes (ver. 1)

For any attribute $a$ if a class C, the following constraints must hold:

1. A required data field must not define an initializer:
$a.required⇒a.init=null$

2. There must be no other member with the same name of a data field f. In particular, there must be no getter or setter defined with the same name:

If a subclass should set a different default value, this has to be done in the constructor of the subclass.

For the relation of data fields and field accessors in the context of extending classes or implementing interfaces see Redefinition of Members.

5.2.6.2.2. Type Inference

The type of a field is the type of its declaration:

$\frac{}{\Gamma ⊢f:\Gamma ⊢d}$

The type of a field declaration is either the declared type or the inferred type of the initializer expression:

$\phantom{\rule{3.0mm}{0ex}}\frac{d.declaredType\ne \text{null}\phantom{\rule{3.0mm}{0ex}}T=d.declaredType}{\Gamma ⊢d:T}$
$\phantom{\rule{3.0mm}{0ex}}\frac{d.declaredType=\text{null}\phantom{\rule{3.0mm}{0ex}}d.expression\ne \text{null}}{\Gamma ⊢d:T}$
$\phantom{\rule{3.0mm}{0ex}}E=\Gamma ⊢d.expression\phantom{\rule{3.0mm}{0ex}}E\notin \left\{\text{null, undefined}\right\}\phantom{\rule{3.0mm}{0ex}}T=E\right\}$
$\phantom{\rule{3.0mm}{0ex}}\frac{else}{\Gamma ⊢d:\text{any}}$

If the type contains type variables they are substituted according to type parameters which are provided by the reference:

5.2.6.3. Assignment Modifiers

Assignment of data fields can be modified by the assignment modifiers const (similar to constant variable declarations, see Const) and @Final.

Req. IDE-62: Const Data Fields (ver. 1)

For a data field f marked as const, the following constraints must hold:

1. An initializer expression must be provided in the declaration (except in n4jsd files):
$f.expr\ne \text{null}$

2. A constant data field is implicitly static and must be accessed only via the classifier type. It is not possible, therefore, to use the this keyword in the initializer expression of a constant field:
$\nexists sub\in f.exp{r}^{*}:sub="this"$

3. A constant data field must not be annotated with @Final:
$f.const\to ¬f.final$

4. Constant data fields are not writeable (cf. [Req-IDE-68]):
$f.const\to ¬f.writeable$

Req. IDE-63: Final Data Fields (ver. 1)

For a data field f marked as @Final, the following constraints must hold:

1. A final data field must not be modified with const or static:
$f.final\to ¬f.const\wedge ¬f.declaredStatic$

2. A final data field is not writeable:
$f.final\to ¬f.writeable$
A final field may, however, be set in the constructor. See [Req-IDE-68] for details.

3. A final data field must be either initialized by an initializer expression or in the constructor. If the field is initialized in the constructor, this may be done either explicitly or via a spec style constructor.

$\begin{array}{c}\phantom{\rule{3.0mm}{0ex}}f.expr\ne \text{null}\\ \phantom{\rule{3.0mm}{0ex}}\vee \left(\exists assignExp:assignExpr.containingFunction=f.owner.constructor\\ \phantom{\rule{3.0mm}{0ex}}\phantom{\rule{3.0em}{0ex}}\wedge assignExpr.left.target=\text{"this"}\\ \phantom{\rule{3.0mm}{0ex}}\phantom{\rule{3.0em}{0ex}}\wedge bind\left(assignExpr.left.property,f\right)\right)\\ \phantom{\rule{3.0mm}{0ex}}\vee \left(f.public\wedge \exists fpar\in f.owner.constructor.fpars:\\ \phantom{\rule{3.0mm}{0ex}}\phantom{\rule{3.0em}{0ex}}fpar.spec\wedge \exists sm\in structuralMembers:sm.name=f.name\right)\end{array}$
5.2.6.4. Field Accessors (Getter/Setter)

Instead of a simple data field, a field can be defined by means of the getter and setter accessor methods. These accessor methods are similar to the accuser methods in object literals:

5.2.6.4.1. Syntax
N4GetterDeclaration <Yield>:
=> ({N4GetterDeclaration}
(declaredModifiers+=N4Modifier)*
(body=Block<Yield>)? ';'?
;

BogusTypeRefFragment? 'get' -> declaredName=LiteralOrComputedPropertyName<Yield>
(declaredOptional?='?')?
'(' ')'
ColonSepTypeRef?
;

N4SetterDeclaration <Yield>:
=>({N4SetterDeclaration}
(declaredModifiers+=N4Modifier)*
'set'
->declaredName=LiteralOrComputedPropertyName <Yield>
)
(declaredOptional?='?')?
'(' fpar=FormalParameter<Yield> ')' (body=Block<Yield>)? ';'?
;

Notes with regard to syntax: Although ECMAScript 6 does not define fields in classes, it defines getter and setter methods similarly (cf. [ECMA15a(p.S13.3, p.p.209)]).

Example 44. Getter and Setter

The getter and setter implementations usually reference data fields internally. These are to be declared explicitly (although ECMAScript allows creating fields on the fly on their first usage). The following example demonstrates a typical usage of getter and setter in combination with a data field. The getter lazily initializes the field on demand. The setter performs some notification.

Getter Setter
class A {}

class C {
private _data: A = null;

public get data(): A {
if (this._data==null) {
this._data = new A();
}
return this._data;
}

public set data(data: A) {
this._data = data;
this.notifyListeners();
}

notifyListeners(): void {
// ...
}
}
5.2.6.4.2. Properties

Properties for field accessors:

declaredOptional

Tells whether the accessor was declared optional.

readable

Derived value: true for getters and false for setters.

writable

Derived value: false for getters and true for setters.

5.2.6.4.3. Semantics

There must be no field or method with the same name as a field accessor (follows from [Req-IDE-52]). In addition, the following constraints must hold:

Req. IDE-64: Field Accessors (ver. 1)

• The return type of a getter must not be void.

• The type of the parameter of a setter must not be void.

• If a getter $g$ is defined or consumed (from an interface) or merged-in (via static polyfill) in a class C and a setter S with $s.name=g.name\wedge s.static=g.static$ is inherited by C from one of its super classes, then C must define a setter ${s}^{\text{'}}$ with ${s}^{\text{'}}.name=g.name\wedge {s}^{\text{'}}.static=g.static$ [34].

• A setter must have exactly one formal parameter, i.e. variadic or default modifiers are not allowed.

The same applies to setters, accordingly.

 A getter and setter with the same name need not have the same type, i.e. the getter’s return type need not be the same as a subtype of the type of the setter’s parameter (the types can be completely unrelated).[35]

Getters and setters – like functions – define a variable execution environment and therefore provide access to the actual passed-in parameters through the implicit arguments variable inside of their bodies (c.f. Arguments Object).

5.2.6.5. Optional Fields

Data fields and field accessors of a classifier C can be declared optional, meaning that a structural subtype of C need not provide this field, but if it does, the field must be of correct type. However, to ensure overall type safety, the scope of application of this optionality is limited to a small number of specific use cases, as described in the following.

5.2.6.5.1. Syntax

To denote a data field or accessor as optional, a question mark is placed right after the name:

Syntax of optional fields
class C {
public field?: string;

public get getter?(): number {
return 42;
}
public set setter?(value: number) {}
}

The detailed grammar is given in the sections for data fields, cf. Syntax, and field accessors, cf. Syntax.

5.2.6.5.2. Semantics

It is important to note that the optionality of a field is, by default and in most cases, ignored and has an effect only in certain special cases.

The effect of a field being optional is defined by the following requirement.

Req. IDE-240500: Optional Fields (ver. 1)

By default, a data field, getter, or setter that is declared optional is handled in the exact same way as if no optionality were involved (i.e. by default, optionality is ignored).

Optionality has an effect only in case of structural subtype checks $L<:R$ in which the left-hand side is one of the following:

1. an object literal.

2. a new expression.

3. an instance of a final class, i.e. the type of the value on left-hand side must be nominal and refer to a final class.

4. a reference to a const variable if its initializer expression is one of the following:

1. an object literal.

2. a new expression.

3. an instance of a final class (as explained above).

4. an ternary expression

and then

• in cases 1 and 4a, both fields and accessors (getters and setters) are optional. That means, an optional data field, getter, or setter of $R$ needs not be present in $L$.

• in cases 2, 3, 4b, and 4c, only getters are optional, setters are not optional. That means, an optional getter of $R$ needs not be present in $L$ and an optional field of $R$ requires only a setter in $L$. Note that these cases are more restricted than the cases 1 and 4a.

Moreover, optionality has an effect in case of ternary expression $L<:R$ in which the left-hand side is a ternary expression, e.g. l = b? trueExpr : falseExpr whose trueExpr or falseExpr possibly recursively contains an expression of the kind mentioned above. In this case, the optionality effect is the more restricted optinality of trueExpr and falseExpr.

If, according to these rules, a data field / getter / setter of $R$ need not be present in $L$ but a member with the same name and access is actually present in $L$, that member in $L$ must be a data field / getter / setter of the same type / a subtype / a super type, respectively. In other words, if a not actually required member is present in the subtype, ordinary rules for member compatibility apply as if no optionality were involved (cf. general subtyping rules for structural types).

In other words, in object literals (cases 1 and 4a) neither optional getters, optional setters, nor optional data fields are required. However, in case of new expressions and instances of final classes (cases 2, 3, 4b, 4c) only optional getters are not required in a subtype; optional setters are required as normal (i.e. optionality ignored) and optional data fields require at least a setter.

The following table summarizes the most common cases and shows how this relates to the different forms of structural typing.

 let x: ΔC = {}; let x: ΔC = new D0(); let x: ΔC = new DG(); let x: ΔC = new DS(); Δ Case Comment ~ ~~ ~w~ ~r~ ~i~ may have setter never has setter ✓ ✓ ✓ ✓ ✓ 1 nothing mandatory ✓ ✓ 2 setters mandatory ✓ ✓ 2 setters mandatory ✓ ✓ ✓ ✓ ✓ 2 setters mandatory none D0 not final none fooSF0() not nominal ✓ ✓ 3 setters mandatory

In the table, a "✓" means that the particular example is valid; in all other cases an error would be shown in N4JS source code. Here are the classes and functions used in the above table:

Classes and functions used in table
class C {
public field?: string;
}

class D0 {}

class DG {
public get field(): string { return "hello"; }
}

class DS {
public set field(value: string) {}
}

@Final class F0 {}

function fooD0(): D0   { return new D0(); }
function fooSF0(): ~F0 { return new F0(); }
function fooF0(): F0   { return new F0(); }

It follows from the above definitions in Requirements [Req-IDE-240500] that cases 4a and 4b are not transitive across a chain of several const variables, whereas case 4c is transitive. For example:

Transitivity of the use cases of optional fields
class C {
public get getter?(): string {return null;}
}
class D {}
@Final class F {}

let c: ~C;

// no transitivity via several const variables in use case "object literal":

const ol1 = {};
const ol2 = ol1;

// XPECT errors --> "~Object is not a structural subtype of ~C: missing getter getter." at "ol2"
c = ol2;

// no transitivity via several const variables in use case "new expression":

const new1 = new D();
const new2 = new1;

// XPECT errors --> "D is not a structural subtype of ~C: missing getter getter." at "new2"
c = new2;

// BUT: we do have transitivity via several const variables in use case "final nominal type":

const finalNominal1 = new F();
const finalNominal2 = finalNominal1;

// XPECT noerrors -->
c = finalNominal1;
// XPECT noerrors --> "transitivity applies in this case"
c = finalNominal2;

The following example demonstrates how optionality behaves in ternay expressions.

Optional fields in ternay expressions
interface ~I {
public m?: int;
}

class ~C { }

@Final class F { }

let b: boolean;
const cc: C = {}
let f1 = new F();
let f2: ~F = {};

// True expression is a const object literal, so both fields and accessors in I are optional.
// False expression is a new expression, so only getters in I are optionals.
// As a result, only getters in I are optional.
// XPECT errors --> "C is not a structural subtype of I: missing field m." at "b? cc : new C()"
var te1: I = b? cc : new C()

// No errors because both true and false expressions are object literal constants and hence
// Both fields and accessors in I are optional.
// XPECT noerrors
var te2: I = b? cc : {}
5.2.6.5.3. Background

The following example illustrates why optionality of fields has to be restricted to the few special cases defined above (i.e. object literals, new expressions, etc.).

Problem 1 of optional fields
class C {
public field?: string = "hello";
}

class D {}
class DD extends D {
public field: number = 42;
}

let c: ~C;
let d: D;

d = new DD();

c = d;  // without the restrictive semantics of optional fields, this assignment would be allowed (but shows compile-time error in N4JS)

console.log(c.field); // prints 42 even though the type is string
c.field.charAt(0); // exception at runtime: c.field.charAt is not a function

In the last line of the above example, c.field is actually 42 but the type systems claims it is of type string and thus allows accessing member charAt of type string which is undefined at runtime the actual value 42.

The next example shows why cases 2 and 3 (i.e. new expressions and instances of final classes) have to be handled in a more restrictive manner than case 1 (i.e. object literals).

Problem 2 of optional fields
class C {
public field?: string;
}

class D {}

let c: ~C;

c = new D(); // error: new expression but D is missing setter

c.field = "hello";

In the previous code, if c = new D() were allowed, we would add a new property field to the instance of class D in the last line, which N4JS aims to avoid in general, unless unsafe language features such as dynamic types are being employed.

5.2.7. Static Members

Static data fields, field accessors and methods are quite similar to instance members, however they are not members of instances of the type but the type itself. They are defined similarly to instance members except that they are specified with the modifier static. Since they are members of the type, the this keyword is not bound to instances of the class, but again to the type itself. This is similar as in ECMAScript 6 ([ECMA15a(p.14.5.15)]). Since static members are not instance but type members, it is even possible that a static member has the same name as an instance member.

Note that static members are not only allowed in classes but also in interfaces, but there are important differences (for example, no inheritance of static members of interfaces, cf. Section Static Members of Interfaces).

Req. IDE-65: Static member not abstract (ver. 1)

For a static field accessor or method S, the following constraint must hold:

• $s.static⇔¬s.abstract$

Like instance methods, static methods of classes are inherited by subclasses and it is possible to override static methods in subclasses. The very same override constraints are valid in this case as well.

5.2.7.1. Access From and To Static Members

Req. IDE-66: Accessing Static Members (ver. 1)

Let M be a static member of class C. Except for write-access to fields, which will be explained later, you can access M via:

1. The class declaration instance, i.e. the classifier or constructor type, constructor{C}, i.e. C.m

2. The class declaration instance of a subtype, i.e. the classifier or constructor type, i.e. D.m, if D is a subclass of C.

3. v.m, if v is a variable of type C (i.e. classifier type as defined in Constructor and Classifier Type) or a subtype thereof.

4. this.m inside the body of any static method declared in C or any sub-class of C.

5. Via a type variable T which upper bound is a subclassof C e.g., function <T extends C> f(){T.m}

Req. IDE-67: Static Member Access (ver. 1)

It is not possible to access instance members from static members. This is true in particular for type variables defined by a generic classifier.

For static data fields and static setter f the following constraint must hold:

• For every assign expression $assignExpr$ with $f.static\wedge assignExpr.left=T.f⇒T=f.owner$.

• For every writing unary expression $u$ with $u.op\in \left\{++,--\right\}\wedge f.static\wedge u.expression=T.f⇒T=f.owner$.

In the special case of m being a static data field, write-access is only possible via the defining type name C.m. In the list above, only the first line can be used when assigning values to a field. Note that this only applies to fields and set-accessors.[36]

It is even possible to call a static field accessor or method of a class using dynamic polymorphism, as demonstrated in the following example:

Example 45. Static members of classes, inheritance and polymorphism
class A {
static m(): void { console.log('A#m'); }

static foo(): void { console.log('A#foo'); }

static bar(): void {
this.foo();
}
}

class B extends A {
@Override
static foo(): void { console.log('B#foo'); }
}

A.m(); // will print "A#m"
B.m(); // will print "A#m" (m is inherited by B)

var t: type{A} = A;
t.foo(); // will print "A#foo"
t = B;
t.foo(); // will print "B#foo"

// using 'this':

A.bar(); // will print "A#foo"
B.bar(); // will print "B#foo"

This is quite different from Java where static methods are not inherited and references to static methods are statically bound at compile time depending on the declared type of the receiver (and not its value):

Example 46. Static members in Java
// !!! JAVA CODE !!!
public class C {

static void m() { System.out.println("C#m"); }

public static void main(String[] args) {
final C c = null;
c.m();  // will print "C#m" (no NullPointerException at runtime)
}
}
5.2.7.2. Generic static methods

It is not possible to refer to type variables of a generic class, as these type variables are never bound to any concrete types. A static method can, however, be declared generic. Generic static methods are defined similarly to generic instance methods. Since they cannot refer to type variables of a generic class, the constraint to avoid type variables with equal names (see [Req-IDE-55]) does not need to hold for generic static methods.

5.2.7.3. Static Members of Interfaces

Data fields, field accessors and methods of interfaces may be declared static. A few restrictions apply:

Req. IDE-69: Static Members of Interfaces (ver. 1)

1. Static members of interfaces may only be accessed directly via the containing interface’s type name (this means, of the four ways of accessing static members of classes defined in [Req-IDE-66] above, only the first one applies to static members of interfaces).

2. The this literal may not be used in static methods or field accessors of interfaces and it may not be used in the initializer expression of static fields of interfaces. See [Req-IDE-173].

3. The super literal may not be used in static methods or field accessors of interfaces (in fact, it may not be used in interfaces at all, cf. [Req-IDE-123]).

Note that the this type as a return type for methods is only allowed for instance methods and as an argument type only in constructors (structurally typed). There is no need to disallow these cases for static interface methods in the constraints above.

In general, static members may not be abstract, cf. [Req-IDE-46], which applies here as well. Static methods and field accessors of interfaces, therefore, always have to provide a body.

Static members of interfaces are much more restricted than those of classes. Compare the following example to Static Polymorphism for classes above:

Example 47. Static members of interfaces
interface I {
static m(): void { console.log('I#m'); }
}

interface J extends I {}

I.m(); // prints "I#m"
J.m(); // ERROR! (m is not inherited by J)

var ti: type{I} = I;
ti.m(); // ERROR! (access to m only allowed directly via type name I)
ti = J;
ti.m(); // ERROR! (access to m only allowed directly via type name I)

The last line in is the reason why access to static members has to be restricted to direct access via the type name of the containing interfaces.

5.2.8. Redefinition of Members

Members defined in classes or interfaces can be redefined by means of being overridden or implemented in subclasses, sub-interfaces, or implementing classes. Fields and methods with default implementation defined in interfaces can be consumed by the implementor, but certain restrictions apply.

Req. IDE-70: Override Compatible (ver. 1)

A member M is override compatible to a member S if and only if the following constraints hold:

1. The name and static modifiers are equal:
$M.name=S.name\wedge M.static=S.static$

2. The metatypes are compatible:

$\mu \left(S\right)=\text{Method}\phantom{\rule{3.0mm}{0ex}}\to \phantom{\rule{3.0mm}{0ex}}\mu \left(M\right)=\text{Method}$
$\mu \left(S\right)=\text{Field}\phantom{\rule{3.0mm}{0ex}}\to \phantom{\rule{3.0mm}{0ex}}\mu \left(M\right)\in \text{Field, Getter, Setter}$
$\mu \left(S\right)=\text{Getter}\phantom{\rule{3.0mm}{0ex}}\to \phantom{\rule{3.0mm}{0ex}}\mu \left(M\right)\in \text{Field, Getter}$
$\mu \left(S\right)=\text{Setter}\phantom{\rule{3.0mm}{0ex}}\to \phantom{\rule{3.0mm}{0ex}}\mu \left(M\right)\in \text{Field, Setter}$

3. The overridden member must not be declared final:
$¬S.final$

4. Overridden member declared const can only be overridden (redefined) by const members:
$S.const⇔M.const$

5. It is not possible to override a non-final / non-const field or a setter with a final / const field:
$\left(\mu \left(S\right)=\text{Field}\wedge ¬\left(S.final\vee S.const\right)\right)\vee \mu \left(S\right)=\text{Setter}⇒¬\left(\mu \left(M\right)=\text{Field}\wedge \left(M.final\vee M.const\right)\right)$

6. It is not possible to override a non-abstract member with an abstract one:
$¬M.abstract\vee S.abstract$

7. The types are compatible:

$\left(\mu \left(M\right)\in \text{Method, Getter, Field}\wedge \mu \left(S\right)\ne \text{Setter}\right)\phantom{\rule{3.0mm}{0ex}}\to \Gamma ⊢M\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}S$
$\left(\mu \left(M\right)\in \text{Setter, Field}\wedge \mu \left(S\right)\ne \text{Getter}\wedge ¬S.const\right)\phantom{\rule{3.0mm}{0ex}}\to \Gamma ⊢S\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}M$4

8. The access modifier is compatible:
$M.acc\ge S.acc$

We define a relation $overrideCompatible\left(M,S\right)$ accordingly.

Members overriding or implementing other members must be declared as override. If a member does not override another, however, it must not be declared as override.

Req. IDE-71: Non-Override Declaration (ver. 1)

If and only if a member M of a class C (extending a class S and interfaces ${I}_{i}$) does not override or implement another member, then it must not be declared as override. That is the following constraint must hold:

$\phantom{\rule{3.0mm}{0ex}}¬M.override$
$\phantom{\rule{3.0mm}{0ex}}\wedge$
$\phantom{\rule{3.0mm}{0ex}}\nexists {M}^{\text{'}}\in C.super.members\cup {\bigcup }_{i=1}^{n}{I}_{i}.members:$
$\phantom{\rule{3.0mm}{0ex}}{M}^{\text{'}}.name=M.name\wedge {M}^{\text{'}}.static=M.static$
$\phantom{\rule{3.0mm}{0ex}}\wedge {M}^{\text{'}}.acc>\text{private}$

5.2.8.1. Overriding of Members

In general, the N4JS platform supports overriding members by redefining them in sub-classes. This definition allows for overriding of static methods, but it does not apply to constructors because $C.ctor\notin C.ownedMethods$.

Req. IDE-72: Overriding Members (ver. 1)

Given a class C and a superclass $Sup$. If for an instance or static member M defined in C a member S exists with null then we call M the overriding member and S the overridden member. In that case the following constraints must hold:

1. S must be accessible from C

2. M must be override compatible with S:
$overrideCompatible\left(M,S\right)$

3. If S is a field and M is an accessor, then an additional accessor ${M}^{\text{'}}$ must exists so that $M,{M}^{\text{'}}$ are an accessor pair for S:

$\phantom{\rule{3.0mm}{0ex}}\mu \left(S\right)=\text{Field}\wedge \mu \left(M\right)=Accessor$
$\phantom{\rule{3.0mm}{0ex}}\to \exists {M}^{\text{'}}\in C.member:$
$\phantom{\rule{3.0mm}{0ex}}\phantom{\rule{4.0em}{0ex}}overrideCompatible\left({M}^{\text{'}},S\right)\wedge \left\{\mu \left(M\right),\mu \left({M}^{\text{'}}\right)\right\}=\text{Getter,Setter}$

4. M must be declared as override:
M.override

Remarks:

• An overridden method, getter, or setter may called via super. Note that this is not possible for fields.

• There is no ’hiding’ of fields as in Java, instead there is field overriding.

• It is not possible to override a field with a consumed getter and an overridden setter, because the getter is not consumed if there exists a field in a superclass. In this case, the consuming and extending class needs to define the accessor pair explicitly. The same is true for other combination of accessors and fields.

• Overriding a field usually makes only sense if the visibility of the field is to be increased.

5.2.8.2. Implementation of Members

For the following constraints, we define two helper sets ${M}_{C}$ and ${M}_{I}$ as follows:

Given a C, and interface ${I}_{1},.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}},{I}_{n}$, implemented by C, with

${M}_{C}\phantom{\rule{3.0mm}{0ex}}=C.ownedMembers\cup \left\{m\in C.superType.members|m.acc>\text{private}\right\}$
${M}_{I}\phantom{\rule{3.0mm}{0ex}}={\bigcup }_{i=1}^{n}{I}_{i}.members$

Note that these sets already contain only non-private data fields.

5.2.8.2.1. Member Consumption

A member M defined in an interface I is consumed by an implementor C, if it becomes a member of the class, that is, $M\in C.members$.

A member M is consumed if there is no member defined in the implementor with the same name and if there is no non-private, non-abstract member with that name inherited by the implementor from its superclass. [37]

If the implementor defines the member itself, then the member is implemented rather than consumed.

The concrete rules are described in the following;

It is not always possible to directly consume a member. In general, a rather conservative strategy is used: if two implemented interfaces define the same (non-abstract) member then the implementor must redefine the member in order to solve conflicts. Even if the two conflicting members have the same types, the implementor must redefine them as we generally assume semantic differences which the consumer has to be aware of. Data fields defined in interfaces, in particular, are assumed to be concrete. It is not, therefore, possible to consume a field defined in two implemented interfaces.

Req. IDE-73: Consumption of Interface Members (ver. 1)

Given a classifier C [38], and interfaces ${I}_{1},.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}},{I}_{n}$ implemented (or extended) by C, and sets ${M}_{C}$ and ${M}_{I}$ as defined in [interface_and_class_member_sets]. A non-static member M defined in any interface ${I}_{i}$ is merged into the consumer (C), if for all other (possible) members ${M}^{\text{'}}$ of C

$\forall {M}^{\text{'}}\in {M}_{C}\cup {M}_{I}\setminus \left\{M\right\}:M.name={M}^{\text{'}}.name\wedge ¬{M}^{\text{'}}.static$

the following constraints hold:

1. The other member’s meta type matches the meta type of the merge candiate:

$\mu \left(M\right)=\text{Method}\phantom{\rule{3.0mm}{0ex}}\to \mu \left({M}^{\text{'}}\right)=\text{Method}$
$\mu \left(M\right)\ne \text{Method}\phantom{\rule{3.0mm}{0ex}}\to \mu \left({M}^{\text{'}}\right)\in \text{Field, FieldAccessor}$

2. The other member is abstract and not owned by the consumer:

$\phantom{\rule{3.0mm}{0ex}}\mu \left(M\right)=\mu \left({M}^{\text{'}}\right)\vee \mu \left(M\right)=\text{Field}$
$\phantom{\rule{3.0mm}{0ex}}\phantom{\rule{2.0em}{0ex}}\to {M}^{\text{'}}.abstract\wedge {M}^{\text{'}}\notin C.ownedMembers$

3. The merge candidate’s access modifier is not less than the modifier of the other member:

$\phantom{\rule{3.0mm}{0ex}}\mu \left(M\right)=\mu \left({M}^{\text{'}}\right)\vee \mu \left(M\right)=\text{Field}$
$\phantom{\rule{3.0mm}{0ex}}\phantom{\rule{2.0em}{0ex}}\to M.acc\ge {M}^{\text{'}}.acc$

4. The merge candidate’s type compatible with the other member:

$\mu \left(M\right)\in \left\{\text{Method, Getter, Field}\right\}\wedge \mu \left({M}^{\text{'}}\right)\ne \text{Setter}\phantom{\rule{3.0mm}{0ex}}\to \Gamma ⊢M\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}{M}^{\text{'}}$
$\mu \left(M\right)\in \left\{\text{Setter, Field}\right\}\wedge \mu \left({M}^{\text{'}}\right)\ne \text{Getter}\phantom{\rule{3.0mm}{0ex}}\to \Gamma ⊢{M}^{\text{'}}\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}M$

5.2.8.2.2. Member Implementation

Req. IDE-74: Implementation of Interface Members (ver. 1)

For any non-static abstract member M defined in an interface I implemented (or extended) by a classifier C, M must be accessible from C and one or two member(s) in C must exist which are implementation-compatible with M. The implementing member(s) must be declared as override if they are directly defined in the consumer.

1. M must be accessible from C.

2. An implementation-compatible member ${M}^{\text{'}}$ must exist in C:

1. if M is not a field:

$\mu \left(M\right)\ne \text{Field}\phantom{\rule{3.0mm}{0ex}}\to$
$\phantom{\rule{3.0mm}{0ex}}\exists {M}^{\text{'}}\in C.members:$
$\phantom{\rule{3.0mm}{0ex}}\phantom{\rule{3.0em}{0ex}}overrideCompatible\left({M}^{\text{'}},M\right)$
$\phantom{\rule{3.0mm}{0ex}}\phantom{\rule{3.0em}{0ex}}\wedge \left({M}^{\text{'}}\in C.ownedMembers\to {M}^{\text{'}}.override\right)$

2. if M is a field, then either an implementation-compatible field ${F}^{\text{'}}$ or accessor pair ${G}^{\text{'}},{S}^{\text{'}}$ must exist:

$\mu \left(M\right)=\text{Field}\phantom{\rule{3.0mm}{0ex}}\to$
$\phantom{\rule{3.0mm}{0ex}}\exists {F}^{\text{'}}\in C.fields:$
$\phantom{\rule{3.0mm}{0ex}}\phantom{\rule{3.0em}{0ex}}overrideCompatible\left({F}^{\text{'}},M\right)$
$\phantom{\rule{3.0mm}{0ex}}\phantom{\rule{3.0em}{0ex}}\wedge \left({F}^{\text{'}}\in C.ownedMembers\to {F}^{\text{'}}.override\right)$
$\phantom{\rule{3.0mm}{0ex}}\vee$
$\phantom{\rule{3.0mm}{0ex}}\exists {G}^{\text{'}}\in C.getters,{S}^{\text{'}}\in C.setters:$
$\phantom{\rule{3.0mm}{0ex}}\phantom{\rule{3.0em}{0ex}}overrideCompatible\left({G}^{\text{'}},M\right)$
$\phantom{\rule{3.0mm}{0ex}}\phantom{\rule{3.0em}{0ex}}\wedge overrideCompatible\left({S}^{\text{'}},M\right)$
$\phantom{\rule{3.0mm}{0ex}}\phantom{\rule{3.0em}{0ex}}\wedge \left({G}^{\text{'}}\in C.ownedMembers\to {G}^{\text{'}}.override\right)$
$\phantom{\rule{3.0mm}{0ex}}\phantom{\rule{3.0em}{0ex}}\wedge \left({S}^{\text{'}}\in C.ownedMembers\to {S}^{\text{'}}.override\right)$

Methods defined in interfaces are automatically declared abstract if they do not provide a default implementation. This can also be expressed explicitly via adding the abstract modifier. If a class implementing an abstract interface does not implement a method declared in the interface, the class needs to be declared abstract (cf. Abstract Classes).

Consequences for method implementation:

1. It may be require the implementor to explicitly define a method in order to solve type conflicts produced by methods of different interfaces with same name but different signatures.

2. Methods in an implementor cannot decrease the accessibility of methods from implemented interfaces, that is

$\phantom{\rule{3.0mm}{0ex}}\forall M\in C.methods,{M}^{\text{'}}\in {I}_{i}.methods\left(i=1.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}n\right):$
$\phantom{\rule{3.0mm}{0ex}}\phantom{\rule{2.0em}{0ex}}M.name={M}^{\text{'}}.name\to M.acc\ne private⇒M.acc\ge {M}^{\text{'}}.acc$

3. Methods in the implementor must be a supertype [39] of methods from implemented interfaces. That is to say the implemented methods are override-compatible.

4. There may be several methods ${M}_{1},.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}}.\phantom{\rule{1.0mm}{0ex}},{M}_{n}$ defined in different implemented interfaces and a single owned method ${M}^{\text{'}}$ in ${M}_{C}$. In this case, the above constraints must hold for all methods. In particular, ${M}^{\text{'}}$’s signature must conform to all conflicting methods’ signatures. This is possible by using union types for the arguments and an intersection type as return type. Such a method ${M}^{\text{'}}$ is said to resolve the conflict between the implemented (and also inherited) methods.

5. Since abstracts methods may become part of the implementor methods, the implementor must either define these methods or it must be declared abstract itself. Since interfaces are abstract by default, responsibility for implementing abstract methods is passed on to any implementor of interfaces.

6. If two implemented interfaces provide (non-abstract) members with the same name, they are not automatically consumed by the implementor even if the types would be similar. In these cases, the implementor has to redefine the members in order to be aware of possible semantic differences.

There is currently no separate annotation to indicate that methods are implemented or overridden in order to solve conflicts. We always use the @Override annotation.

Example 48. Method Consumption

Consumption of methods shows simple examples of above rules. Assuming that class C extends super class S and implements interface I1 and I2:

class C extends S implements I1, I2 {...}

The columns describe different scenarios in which a method (with same name) is defined in different classifiers. We assume that the defined methods are always non-abstract (i.e. have default implementations), non-private and have the same signature. The last row shows which method will be actually used in class C. If the method is defined in class C, and if this method is printed bold, then this means that the method is required to be defined in C in order to solve conflicts.

 Interface I1 Interface I2 class S class C $\in C.members$ MI1 MI1 MI1 MI1 MI1 MI1 MI2 MI2 MI2 MS MS MS MC MC MC MI1 MC MC MS MS MC
Consuming Field Initializers

Aside from the fields themselves, an implementor always consumes the field initialization if the field is consumed – this is how the consumption is noticed at runtime.

Example 49. Field and Field Initializer Consumption
/* XPECT  output ~~~
<==
stdout:
s: C , t: D ,u: I1 ,v: I2
stderr:
==>
~~~ */

interface I0 {
v: string = "I0";
}

interface I1 {
s: string = "I1";
t: string = "I1";
u: string = "I1";
}

interface I2 extends I1, I0 {
@Override
t: string = "I2";
@Override
v: string = "I2";
}

class C {
s: string = "C";
}

class D extends C implements I1, I2 {
@Override
t: string = "D";
}

var d = new D();

console.log(
"s:", d.s, ", t:", d.t, ",u:", d.u, ",v:", d.v
)

We expect the following output (for each field):

• d.s = "C" : s: is inherited from C, so it is not consumed from I1 (or I2). Consequently, the initializer of s in C is used.

• d.t = "D": t is defined in D, solving a conflict stemming from the definition of t in I1 and I2. Thus, the initializer of t in D is used.

• d.u = "I1" : u is only defined in I1, thus the initializer defined in I1 is used.

• d.v = "I2" : v is overridden in I2, so is the field initializer. This is why d.v must be assigned to I2 and not I0.

5.3. Structural Typing

In general, N4JS uses nominal typing. This is to say that a duck is a duck only if it is declared to be a duck. In particular when working with external APIs, it is more convenient to use structural or duck typing. That is, a thing that can swim and quacks like a duck, is a duck.

Interfaces or classes can be used for this purpose with a typing strategy modifier. Given a type $T$, the simple ~ (tilde) can be added to its declaration (on definition-site) or in a reference (on use-site) to indicate that the type system should use structural typing rather than nominal typing.[40] This means that some other type must provide the same members as type $T$ to be deemed a structural subtype. However, the operator cannot be used anymore with the type or reference as this operator relies on the declaration information (or at least the closest thing available at runtime). In this case, $T$ is, therefore, always a structural subtype of ~T.

Sometimes it is convenient to refer only to the fields of a classifier, for example when the initial field values are to be provided in a variable passed to the constructor. In that case, the type can be modified with ~~ (two tildes). This is only possible on use-site, i.e. on type references. Furthermore, only on the use-site, it is possible to consider only either readable or writable or fields by using the read-only ~r~ or write-only ~w~ structural field typing. For initialization blocks, it is even possible to use structural initializer field typing via the ~i~ operator.

5.3.1. Syntax

Structural typing is specified using the typing strategy modifier. There are two modifiers defined; one for definition-site and one for use-site structural typing.

Structural Type Operator and References
TypingStrategyUseSiteOperator returns TypingStrategy:
'~' ('~' | STRUCTMODSUFFIX)?;

TypingStrategyDefSiteOperator returns TypingStrategy:
'~';

terminal STRUCTMODSUFFIX:
('r' | 'i' | 'w') '~'
;

ParameterizedTypeRefStructural returns ParameterizedTypeRefStructural:
definedTypingStrategy=TypingStrategyUseSiteOperator
declaredType=[Type|TypeReferenceName]
(=> '<' typeArgs+=TypeArgument (',' typeArgs+=TypeArgument)* '>')?
(=> 'with' '{' astStructuralMembers+=TStructMember* '}')?
;

ThisTypeRefStructural returns ThisTypeRefStructural:
definedTypingStrategy=TypingStrategyUseSiteOperator
'this'
('with' '{' astStructuralMembers+=TStructMember* '}')?
;

5.3.2. Definition Site Structural Typing

An interface or class can be defined to be used with structural typing by adding the structural modifier to its definition (or, in case of external classes, to the declaration). This changes the default type system strategy from nominal to structural typing for that type. That means that all types with the same members as the specified type are subtypes of that type, except for subtypes of N4Object. In the latter case, programmers are enforced to nominal declare the type relation.

If a type $T$ is declared as structural at its definition, $T.defStructural$ is true.

Req. IDE-75: Definition Site Structural Typing (ver. 1)

1. The structurally defined type cannot be used on the right hand side of the instanceof operator:

2. A type $X$ is a subtype of a structurally defined type $T$ either

1. if it is not a subtype of N4Object [41] but it contains all public, non-static members of that type

$X\phantom{\rule{1.0mm}{0ex}}\nless \text{:}\phantom{\rule{1.0mm}{0ex}}\left\{\text{N4Object}\right\}\phantom{\rule{3.0em}{0ex}}T.defStructural\right\}$
$\forall m\in T.members,m.acc=\text{public},¬m.static,m\ne T.ctor:\right\}$
$\phantom{\rule{1.0em}{0ex}}\exists {m}^{\text{'}}\in X.members:\right\}$
$\phantom{\rule{2.0em}{0ex}}{m}^{\text{'}}.acc=\text{public}\wedge ¬{m}^{\text{'}}.static\wedge {m}^{\text{'}}.name=m.name\right\}$
$\phantom{\rule{2.0em}{0ex}}\wedge \Gamma ⊢{m}^{\text{'}}\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}m\right\}$
$\frac{\phantom{\rule{2.0em}{0ex}}\wedge \mu \left(m\right)=\text{Field}⇒\Gamma ⊢m\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}{m}^{\text{'}}}{\phantom{\rule{13.0em}{0ex}}\Gamma ⊢X\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}T\phantom{\rule{13.0em}{0ex}}}$
or

2. if it is a subtype of N4Object which explicitly extends or implements the structurally defined type.

$\frac{X\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}\left\{\text{N4Object}\right\}\phantom{\rule{3.0mm}{0ex}}T.defStructural\phantom{\rule{3.0mm}{0ex}}T\in X.superType{s}^{*}}{\Gamma ⊢X\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}T}$
3. A structurally defined type $T$ is implicitly derived from Object if no other type is specified. In particular, a structurally defined type must not be inherited from

$\frac{T.defStructural}{\Gamma ⊢T\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}\text{Object}}$

$T.defStructural⇒\Gamma ⊢T\phantom{\rule{1.0mm}{0ex}}\nless \text{:}\phantom{\rule{1.0mm}{0ex}}\text{N4Object}\wedge \text{N4Object}\notin T.superType{s}^{*}$

Example 50. Declaration Site Structural Typing

The following snippet demonstrates the effect of definition-site structural types by comparing them to nominal declared types:

Declaration Site Structural Typing
interface ~Tilde { x; y; }
interface Nominal { x; y; }
class C { public x; public y;}
class D extends C implements Tilde { }

function f(p: Tilde) {}
function g(p: Nominal) {}

f(new C());         // error: nominal typing, C does not implement ~Tilde
f(new D());         // ok, D is a nominal subtype (as it implements Tilde)
f({x:10,y:10});     // ok: Tilde is used with structural typing for non-N4-classifiers

Definition site structural typing may lead to unexpected results. For example;

class C{}
class ~E extends C{}

It may be unexpected, but E is not a subtype of C, i.e. $E\phantom{\rule{1.0mm}{0ex}}\nless \text{:}\phantom{\rule{1.0mm}{0ex}}C$! E.g., instanceof won’t work with E, while it works with C!

5.3.3. Use-Site Structural Typing

Use-site structural typing offers several typing strategy modifiers to define the accessability of public properties of classes and interfaces. They can be used e.g. on variable declarations like this: var c : ~C. The table Available Fields of Structural Types shows which properties of structural types can be accessed in the different type strategies. For example, when using the write-only structural strategy (i.e. $~\text{w}~\text{X}$), only the writeable fields, can be accessed for writing. In the table, the term field to both, public datafields and accessors of type $X$. Non-public properties are never accessable in use-site structural types. In any field-structural type, methods of the referenced nominal type $X$ are not available. The initializer structural typing provides readable fields for every writeable field of the references type.

Table 9. Available Fields of Structural Types
Property of $X$ $~\text{X}$ $~~\text{X}$ $~\text{r}~\text{X}$ $~\text{w}~\text{X}$ $~\text{i}~\text{X}$

public method

$\varnothing$

$\varnothing$

$\varnothing$

$\varnothing$

public writable field

$\varnothing$

$\varnothing$

$\varnothing$

writable fields

Multiple structural typing strategies can be nested when there are multiple use sites, like in the example Nested Structural Typing Strategies below at the locations ST1 and ST2. In the example, the datafield a.field has the nested structural type ~r~ ~i~ A and thus the datafield a.field.df is readable. Nested structural types are evaluated on the fly when doing subtype checks.

Example 51. Nested Structural Typing Strategies
class A {
public df : string;
}
interface I<T> {
public field : ~r~T; // ST1
}
var a : ~i~A; // ST2

The following example demonstrates the effect of the structural type modifiers:

Example 52. Effect of structural type modifiers on use-site

Let’s assume the type defined on the left. The following pseudo code snippets explicitly list the type with its members virtually created by a structural modifier. Note that this is pseudo code, as there are no real or virtual types created. Instead, only the subtype relation is defined accordingly:

Effect of structural type modifiers on use-site

Effect of structural type modifiers on use-site

var c:C

class C {
private x;
public y;
public f()
private g()
public get z():Z
public set z(z:Z)
}
interface I {
a;
func();
}
var cstructural:~C

class cstructural {

public y;
public f()

public get z():Z
public set z(z:Z)
}
interface ~I {
a;
func();
}
var cfields:~~C

class cfields {

public y;

public get z():Z
public set z(z:Z)
}
interface ~~I {
a;

}

Type

Structural Type

Structural Field Type

Structural Read-only Field Type Structural Write-only Field Type Structural Initializer Field Type var crofields:~r~C class crofields { public get y():Y public get z():Z } interface ~r~I { public get a():A } var cwofields:~w~C class cwofields { public set y(y:Y) public set z(z:Z) } interface ~w~I { public set a(a:A) } var cinitfields:~i~C class cinitfields { public get y():Y public get z():Z } interface ~i~I { public get a():A }

Note that even if a type is defined without the structural modifier, it is not possible to use instanceof for variables declared as structural, as shown in the next example:

Type Structural Type Structural Field Type class C {..} interface I {..} foo(c: C, i: I) { c instanceof C; // ok c instanceof I; // ok } class C {..} interface I {..} foo(c: ~C, i: ~I) { c instanceof C; // error c instanceof I; // error } class C {..} interface I {..} foo(c: ~~C, i: ~~I) { c instanceof C; // error C instanceof I; // error }

Within this spec, we define the following attributes of a type reference $T$:

• If a type is referenced with the structural type modifier ~ , the property $T.refStructural$ is true.

• If a type is referenced with the structural field type modifier ~~, the property $T.refStructuralField$ is true.

• If a type is referenced with the structural read-only field type modifier ~r~, the property $T.refStructuralReadOnlyField$ is true.

• If a type is referenced with the structural write-only field type modifier ~w~, then the property $T.refStructuralWriteOnlyField$ is true.

• If a type is referenced with the structural initializer field type modifier ~i~, then the property $T.refStructuralInitField$ is true.

Semantics

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

For a given unary expression $u$ with $u.op=\text{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=\text{void}$

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

There are no specific constraints defined for unary expression $u$ with $u.op=\text{typeof}$.

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

For a given unary expression $u$ $u$ with $u.op\in \left\{++,--\right\}$ and $u.expression.type:T$, the following constraints must hold:

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

$\frac{}{\Gamma ⊢\text{UnaryExpression}⊲\text{Expression}:\text{number}}$
• If $u.expression=PropertyAccess\phantom{\rule{0.278em}{0ex}}pa\left(p\right)\wedge pa.isDotAccess$ $\to$ both $get$ p and $set$ p must be defined.

For a given unary expression $u$ $u$ with $u.op\in \left\{+,-,\sim \right\}$ and $u.expression.type:T$, the following constraints must hold:

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

$\frac{}{\Gamma ⊢\text{UnaryExpression}⊲\text{Expression}:\text{number}}$

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

There are no specific constraints defined for with $u.op=\text{!}$.

Type Inference

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

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:

$\frac{}{\Gamma ⊢\text{MultiplicativeExpression}⊲\text{Expression}:\text{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:

$\frac{}{\Gamma ⊢\text{MultiplicativeExpression}:\text{number}}$

Syntax
AdditiveExpression returns Expression: 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:

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:

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

$\begin{array}{c}\frac{e.op{=}^{\text{'}}{+}^{\text{'}}\phantom{\rule{3.0mm}{0ex}}¬toNum\left(e\right)\phantom{\rule{3.0mm}{0ex}}¬mayNum\left(e\right)}{\Gamma ⊢e:\text{string}}\\ \frac{e.op{=}^{\text{'}}{+}^{\text{'}}\phantom{\rule{3.0mm}{0ex}}¬toNum\left(e\right)\phantom{\rule{3.0mm}{0ex}}mayNum\left(e\right)}{\Gamma ⊢e:union\left\{number,string\right\}}\\ \frac{e.op{=}^{\text{'}}{+}^{\text{'}}\phantom{\rule{3.0mm}{0ex}}toNum\left(e\right)}{\Gamma ⊢e:\text{number}}\\ \frac{e.op{\le }^{\text{'}}{+}^{\text{'}}}{\Gamma ⊢e:\text{number}}\end{array}$

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
;

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.

Type Inference

The type returned by a bitwise shift expression is always 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 \left\{<,>,<=,>=\right\}$ 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:

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

For a given relational expression $e$ with $e.op=\text{instanceof}$, the following constraints must hold:

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

In other words,

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

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=\text{in}$, the following constraints must hold:

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

2. In N4JS mode, the left operand is restricted to be of type string or 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;

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\text{(’===’—’!==’)}rhs$, neither $\Gamma ⊢lhs.upper<:rhs.upper$ nor $\Gamma ⊢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 $\text{? extends A}\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}\text{B}$ nor $\text{B}\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}\text{? 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.

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.

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.

$\frac{}{\Gamma ⊢lhs\text{’}&&\text{’—’——’}rhs:union\left\{\Gamma ⊢lhs,\Gamma ⊢rhs\right\}}$

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.expression\in \left\{false,true,null,undefined\right\}$ or $C\in \left\{\text{void},\text{undefined}\right\}$.

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)] ():

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=\text{’=’}$

the following constraints must hold:

1. $\left[\phantom{\rule{-0.167em}{0ex}}\left[assignment.lhs\right]\phantom{\rule{-0.167em}{0ex}}\right]\phantom{\rule{1.0mm}{0ex}}<\text{:}\phantom{\rule{1.0mm}{0ex}}\left[\phantom{\rule{-0.167em}{0ex}}\left[assignment.rhs\right]\phantom{\rule{-0.167em}{0ex}}\right]$

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 , with $op=\text{’@=’}$ but not +=, both, left and right must be subtypes of number.
For operator +=,

• if the left-hand side is a number, then 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\text{’+’}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 $bind\left(assignExpr.left,v\right)$

$\begin{array}{c}v.writeable\vee v.final\wedge \phantom{\rule{3.0mm}{0ex}}\phantom{\rule{2.0em}{0ex}}v.expr=\text{null}\\ \phantom{\rule{3.0mm}{0ex}}\phantom{\rule{2.0em}{0ex}}\wedge assignExpr.containingFunction=v.owner.constructor\\ \phantom{\rule{3.0mm}{0ex}}\phantom{\rule{2.0em}{0ex}}\wedge \mu \left(assignExpr.left\right)=\text{PropertyAccess}\\ \phantom{\rule{3.0mm}{0ex}}\phantom{\rule{4.0em}{0ex}}\wedge assignExpr.left.target=\text{"this"}\end{array}$

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:

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:

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:

$\frac{n=|cexpr.exprs|,\Gamma ⊢cexpr.expr{s}_{n}:T}{\Gamma ⊢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.

$\Gamma ⊢\text{super}:T\wedge T.typingStrategy=\text{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:

$\mu \left(cexpr\right)=\text{CallExpression}\wedge c.target=cexpr$
2. The super constructor call must be the expression of an expression statement $exprStmt$:

$exprStmt=cexpr.container\wedge \mu \left(cexpr.container\right)=\text{ExpressionStatement}$
3. The containing statement $stmtExpr$ must be directly contained in a constructor body:

$\mu \left(exprStmt.containingFunction\right)=\text{Constructor}\right)$ $\phantom{\rule{3.0mm}{0ex}}\wedge exprStmt.container=exprStmt.containingFunction.body$

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