5. Classifiers

5.1. N4JS Specific Classifiers

N4JS provides three new metatypes: class, interface, and enums. In this section we describe classes and interfaces. These metatypes, called classifiers, share some common properties which are described before type specific properties are outlined in the following sections.

All of these metatypes can be marked with type access modifiers:

enum N4JSTypeAccessModifier: project | public;

5.1.1. Properties

Properties defined by syntactic elements:

annotations

Arbitrary annotations, see Annotations for details.

accessModifier

N4JS type access modifier: public, or project; public can be combined with @Internal; if export is true the default is else the default is private.

name

The simple name of a classifier. If the classifier is defined by an anonymous class expression, an artificial but unique name is created. The name needs to be a valid identifier, see Valid Names.

typePars

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

ownedMembers

Collection of owned members, i.e. methods and fields defined directly in the classifier and, if present, the explicitly defined constructor. Depending on the concrete classifier, additional constraints are defined.

typingStrategy

The definition-site typing strategy. By default nominal typing is used. See Structural Typing for details.

The following pseudo properties are defined via annotations:

export

Boolean property set to true if the export modifier is set. If value is true, the classifier may be accessible outside the project.

final

Boolean property which is set to final if annotation @Final is set. Also see Final Modifier

deprecated

Boolean property set to true if annotation @Deprecated is set.

Version 0.4, not implemented in Version 0.3

We additionally define the following pseudo properties:

acc

Type access modifier as described in Accessibility of Types, Top-Level Variables and Function Declarations, it is the aggregated value of the accessModifier and the export property.

owned{Fields|Methods|Getters|Setters|Accessors}

Filters ownedMembers by metatype, short for
xownedMembers,μx=Field etc.

members

Reflexive transitive closure of all members of a classifier and its super classifiers, see Common Semantics of Classifiers on how this is calculated.

fields|methods|getters|setters|accessors

Filters members by metatype, short for
xmembers,μx=Field etc.

superClassifiers

Classes and interface may extend or implement classes or interfaces. Any class or interface extended or interface implemented is called super classifier. We distinguish the directly subtyped classifiers and from the transitive closure of supertypes superClassifiers*

5.1.2. Common Semantics of Classifiers

Req. IDE-42: Subtyping of Classifiers (ver. 1)

For a given type C, and supertypes superClassifiers=S1...Sn directly subtyped C, the following constraints must be true:

  1. The supertypes must be accessible to the subtype:
    S1,...,Sn must be accessible to C.

  2. All type parameters of the direct supertypes have to be bound by type arguments in the subtype and the type arguments have to be substitutable types of the type parameters.

    0<ik:PSi:
    AC.typeArgs:bindAPA.upperBound<:P.upperBound

  3. Wildcards may not be used as type argument when binding a supertype’s type parameters.

  4. A classifier cannot be directly subtyped directly multiple times:
    Si,Sjij1..n:Si=Sji=j

In order to simplify the following constraints, we use the pseudo property members to refer to all members of a classifier. This includes all members directly declared by the classifier itself, i.e. the ownedMember, and all members inherited from its super classifiers. The concrete mechanisms for inheriting a member are different and further constraint (cf. Redefinition of Members). A classifier only inherits its members from its direct supertypes, although the supertypes may contains members inherited from their supertypes.

5.1.3. Classes

5.1.3.1. Definition of Classes

Classes are either declared with a class declaration on top level, or they can be used as anonymous classes in expressions. The latter may have a name, which may be used for error messages and reflection.

At the current stage, class expressions are effectively disabled at least until the semantics of them are finalized in ECMAScript 6.

In N4JS (as in many other languages) multi-inheritance of classes is not supported. Although the diamond problem (of functions being defined in both superclasses) could be solved via union and intersection types, this would lead to problems when calling these super implementations. This is particularly an issue due to JavaScript not supporting multiple prototypes.[27] Interfaces, however, allow for multi-inheritance. Since the former can also define functions with bodies, this is not a hard restriction.

5.1.3.1.1. Syntax
Syntax N4 Class Declaration and Expression
N4ClassDeclaration <Yield>:
    =>(
        {N4ClassDeclaration}
        annotations+=Annotation*
        (declaredModifiers+=N4Modifier)*
        'class' typingStrategy=TypingStrategyDefSiteOperator? name=BindingIdentifier<Yield>?
    )
    TypeVariables?
    ClassExtendsClause<Yield>?
    Members<Yield>
;

N4ClassExpression <Yield>:
    {N4ClassExpression}
    'class' name=BindingIdentifier<Yield>?
    ClassExtendsClause<Yield>?
    Members<Yield>;


fragment ClassExtendsClause <Yield>*:
    'extends' (
          =>superClassRef=ParameterizedTypeRefNominal ('implements' ClassImplementsList)?
        | superClassExpression=LeftHandSideExpression<Yield>
    )
    | 'implements' ClassImplementsList
;

fragment ClassImplementsList*:
    implementedInterfaceRefs+=ParameterizedTypeRefNominal
    (',' implementedInterfaceRefs+=ParameterizedTypeRefNominal)*
;

fragment Members <Yield>*:
    '{'
    ownedMembers+=N4MemberDeclaration<Yield>*
    '}'
;
5.1.3.1.2. Properties

These are the properties of class, which can be specified by the user: Syntax N4 Class Declaration and Expression

abstract

Boolean flag indicating whether class may be instantiable; default is false, see Abstract Classes.

external

Boolean flag indicating whether class is a declaration without implementation or with an external (non-N4JS) implementation; default is false, see Definition Site Structural Typing.

defStructural

Boolean flag indicating whether subtype relation uses nominal or structural typing, see Definition Site Structural Typing for details.

superType/sup

The type referenced by superType is called direct superclass of a class, and vice versa the class is a direct subclass of superType. Instead of superType, we sometimes simply write sup. The derived set sup+ is defined as the transitive closures of all direct and indirect superclasses of a class. If no supertype is explicitly stated, classes are derived from N4Object.

implementedInterfaces/interfaces

Collection of interfaces directly implemented by the class; empty by default. Instead of implementedInterfaces, we simply write interfaces.

ownedCtor

Explicit constructor of a class (if any), see Constructor and Classifier Type.

And we additionally define the following pseudo properties:

ctor

Explicit or implicit constructor of a class, see Constructor and Classifier Type.

fields

Further derived properties for retrieving all methods (property methods), fields (property fields), static members (property staticOwnedMembers), etc. can easily be added by filtering properties members or ownedMembers.

5.1.3.1.3. Type Inference

The type of a class declaration or class expression C (i.e., a class definition in general) is of type constructor{C} if it is not abstract, that is if it can be instantiated. If it is abstract, the type of the definition simply is type{C}:

¬C.abstractΓC:constructorCC.abstractΓC:typeC

Req. IDE-43: Structural and Nominal Supertypes (ver. 1)

The type of supertypes and implemented interfaces is always the nominal type, even if the supertype is declared structurally.

5.1.3.2. Semantics

This section deals with the (more or less) type-independent constraints on classes.

Class expressions are not fully supported at the moment.

The reflexive transitive closure of members of a class is indirectly defined by the override and implementation constraints defined in Redefinition of Members.

Note that since overloading is forbidden, the following constraint is true [28]:

m1,m2members:m1.name=m2.namem1=m2accessorPairm1m2

Remarks: Class and method definition is quite similar to the proposed ECMAScript version 6 draft [ECMA15a(p.S13.5)], except that an N4 class and members may contain

  • annotations, abstract and access modifiers

  • fields

  • types

  • implemented interfaces

Note that even static is used in ECMAScript 6.

Mixing in members (i.e. interface’s methods with default implementation or fields) is similar to mixing in members from roles as defined in [Dart13a(p.S9.1)]. It is also similar to default implementations in Java 8 [Gosling15a]. In Java, however, more constraints exist, (for example, methods of interfaces must be public).

Example 30. Simple Class

This first example shows a very simple class with a field, a constructor and a method.

class C {
    data: any;

    constructor(data: any) {
        this.data = data;
    }

    foo(): void { }
}
Example 31. Extend and implement

The following example demonstrate how a class can extend a superclass and implement an interface.

interface I {
    foo(): void
}
class C{}
class X extends C implements I {
    @Override
    foo(): void {}
}

A class C is a subtype of another classifier S (which can be a class or interface) if the other classifier S is (transitively) contained in the supertypes (superclasses or implemented interfaces) of the class:

left=rightΓTClass left<:TClass rightshortcut
Γleft.superType.declaredType<:rightΓTClass left<:TClass right

Req. IDE-44: Implicit Supertype of Classes (ver. 1)

  1. The implicit supertype of all classes is N4Object. All classes with no explicit supertype are inherited from N4Object.

  2. If the supertype is explicitly set to Object, then the class is not derived from N4Object. Meta-information is created similar to an N4Object-derived class. Usually, there is no reason to explicitly derive a class from Object.

  3. External classes are implicitly derived from , unless they are annotated with @N4JS(cf.External Declarations).

5.1.3.3. Final Modifier

Extensibility refers to whether a given classifier can be subtyped. Accessibility is a prerequisite for extensibility. If a type cannot be seen, it cannot be subclassed. The only modifier influencing the extensibility directly is the annotation @Final, which prevents all subtyping. The following table shows how to prevent other projects or vendors from subtyping by also restricting the accessibility of the constructor:

Table 6. Extensibility of Types
Type C Settings Subclassed in

Project

Vendor

World

C.final

no

no

no

C.ctor.accessModifier=\lenum{project}

yes

no

no

C.ctor.accessModifier=\lenum{public@Internal}

yes

yes

no

Since interfaces are always to be implemented, they must not be declared final.

5.1.3.4. Abstract Classes

A class with modifier abstract is called an abstract class and has its abstract property set to true. Other classes are called concrete classes.

Req. IDE-45: Abstract Class (ver. 1)

  1. A class C must be declared abstract if it owns or inherits one or more abstract members and neither C nor any interfaces implemented by C implements these members. A concrete class has to, therefore, implement all abstract members of its superclasses’ implemented interfaces. Note that a class may implement fields with field accessors and vice versa.

  2. An abstract class may not be instantiated.

  3. An abstract class cannot be set to final (with annotation @Final).

Req. IDE-46: Abstract Member (ver. 1)

  1. A member declared as abstract must not have a method body (in contrary a method not declared as abstract have to have a method body).

  2. Only methods, getters and setters can be declared as abstract (fields cannot be abstract).

  3. It is not possible to inherit from an abstract class which contains abstract members which are not visible in the subclass.

  4. An abstract member must not be set to final (with annotation @Final).

  5. Static members must not be declared abstract.

Remarks:

  • We decided to disallow abstract static members, since we cannot guarantee that a static members is not accessed in all cases

  • Only static members can override static members and only instance members can override other instance members of course.

  • An abstract member must not be declared in a final class (i.e. a class annotated with @Final). This is not explicitly defined as constraint in [Req-IDE-46] since abstract classes must not defined final anyway. We also do not produce error message for abstract members in final classes since these errors would be consequential errors.

Abstract members might be declared private, as they can be accessed from within the module. This is to be changed in order to be aligned with TypeScript, cf. #1221. However we also want to add class expressions — and then the abstract members may be accessed (and overridden) in nested classes created by means of class expressions.
5.1.3.5. Non-Instantiable Classes

To make a class non-instantiable outside a defining compilation unit, i.e. disallow creation of instances for this class, simply declare the constructor as private. This can be used for singletons.

5.1.3.6. Superclass

Req. IDE-47: Superclass (ver. 1)

For a class C with a supertype S=C.sup, the following constraints must hold;

  • C.sup must reference a class declaration S

  • S must be be extendable in the project of C

  • CC.sup+

  • All abstract members in S must be accessible from C:

    MS.members:M.abstract
    M is accessible from C.
    Note that M need not be an owned member of S and that this constraint applies even if C is abstract).

All members of superclasses become members of a class. This is true even if the owning classes are not directly accessible to a class. The member-specific access control is not changed.

5.1.4. Interfaces

5.1.4.1. Definition of Interfaces
5.1.4.1.1. Syntax
Syntax N4 Interface Declaration
N4InterfaceDeclaration <Yield>:
    => (
        {N4InterfaceDeclaration}
        annotations+=Annotation*
        (declaredModifiers+=N4Modifier)*
        'interface' typingStrategy=TypingStrategyDefSiteOperator? name=BindingIdentifier<Yield>?
    )
    TypeVariables?
    InterfaceImplementsList?
    Members<Yield>
;

fragment InterfaceImplementsList*:
    'implements' superInterfaceRefs+=ParameterizedTypeRefNominal
        (',' superInterfaceRefs+=ParameterizedTypeRefNominal)*
;
5.1.4.1.2. Properties

These are the additional properties of interfaces, which can be specified by the user:

superInterfaces

Collection of interfaces extended by this interface; empty by default. Instead of superInterfaces, we simply write interfaces.

5.1.4.1.3. Type Inference

The type of an interface declaration I is of type type{I}:

ΓI:typeI
5.1.4.1.4. Semantics

Interfaces are used to describe the public API of a classifier. The main requirement is that the instance of an interface, which must be an instance of a class since interfaces cannot have instances, provides all members declared in the interface. Thus, a (concrete) class implementing an interface must provide implementations for all the fields, methods, getters and setters of the interface (otherwise it the class must be declared abstract). The implementations have to be provided either directly in the class itself, through a superclass, or by the interface if the member has a default implementation.

A field declaration in an interface denotes that all implementing classes can either provide a field of the same name and the same(!) type or corresponding field accessors. If no such members are defined in the class or a (transitive) superclass, the field is mixed in from the interface automatically. This is also true for the initializer of the field.

All instance methods, getters and setters declared in an interface are implicitly abstract if they do not provide a default implementation. The modifier abstract is not required, therefore, in the source code. The following constraints apply:

Req. IDE-48: Interfaces (ver. 1)

For any interface I, the following must hold:

  1. Interfaces may not be instantiated.

  2. Interfaces cannot be set to final (with annotation @Final): ¬I.final.

  3. Members of an interface must not be declared private. The default access modifier in interfaces is the the type’s visibility or project, if the type’s visibility is private.

  4. Members of an interface, except methods, must not be declared @Final:

    mI.member:m.finalmI.methods
    not allowing field accessors to be declared final was a deliberate decision, because it would complicate the internal handling of member redefinition; might be reconsidered at a later time
  5. The literal may not be used in the initializer expression of a field of an interface.
    This restriction is required, because the order of implementation of these fields in an implementing class cannot be guaranteed. This applies to both instance and static fields in interfaces, but in case of static fields, this is also disallowed due to Static Members of Interfaces.

It is possible to declare members in interfaces with a smaller visibility as the interface itself. In that case, clients of the interface may be able to use the interface but not to implement it.

In order to simplify modeling of runtime types, such as elements, interfaces do not only support the notation of static methods but constant data fields as well. Since IDL [OMG14a] is used to describe these elements in specifications (and mapped to JavaScript via rules described in [W3C12a]) constant data fields are an often-used technique there and they can be modeled in N4JS 1:1.

As specified in [Req-IDE-56], interfaces cannot contain a constructor i.e.
mI.ownedMethods:m.name'constructor'.

Example 32. Simple Interfaces

The following example shows the syntax for defining interfaces. The second interface extends the first one. Note that methods are implicitly defined abstract in interfaces.

interface I {
    foo(): void
}
interface I2 extends I {
    someText: string;
    bar(): void
}

If a classifier C implements an interface I, we say I is implemented by C. If C redefines members declared in I, we say that these members are implemented by C. Members not redefined by C but with a default implementations are mixed in or consumed by C. We all cases we call C the implementor.

Besides the general constraints described in Common Semantics of Classifiers, the following constraints must hold for extending or implementing interfaces:

Req. IDE-49: Extending Interfaces (ver. 1)

For a given type I, and I1...In directly extended by I, the following constraints must be true:

  1. Only interfaces can extend interfaces: I,I1,...,In must be interfaces.

  2. An interface may not directly extend the same interface more than once:
    Ii=Iji=j for any i,j1...n.

  3. An interface may (indirectly) extend the same interface J more than once only if

    1. J is not parameterized, or

    2. in all cases J is extended with the same type arguments for all invariant type parameters.
      Note that for type parameters of J that are declared covariant or contravariant on definition site, different type arguments may be used.

  4. All abstract members in Ii, i1...n, must be accessible from I:
    i1...n:MIi.membersM.abstract M is accessible from I.
    Note that M need not be an owned member of Ii.

Req. IDE-50: Implementing Interfaces (ver. 1)

For a given type C, and I1...In directly implemented by C, the following constraints must be true:

  1. Only classes can implement interfaces: C must be a Class.

  2. A class can only implement interfaces: I1,...,In must be interfaces.

  3. A class may not directly implement the same interface more than once:
    Ii=Iji=j for any i,j1...n.

  4. A class may (indirectly) implement the same interface J more than once only if

    1. J is not parameterized, or

    2. in all cases J is implemented with the same type arguments for all invariant type parameters.
      Note that for type parameters of J that are declared covariant or contravariant on definition site, different type arguments may be used.

  5. All abstract members in Ii, i1...n, must be accessible from C:
    i1...n:MIi.membersM.abstract M is accessible from C.
    Note that M need not be an owned member of Ii.

For default methods in interfaces, see Default Methods in Interfaces.

5.1.5. Generic Classifiers

Classifiers can be declared generic by defining a type parameter via type-param.

Definition: Generic Classifiers

A generic classifier is a classifier with at least one type parameter. That is, a given classifier C is generic if and only if |C.typePars|1.

If a classifier does not define any type parameters, it is not generic, even if its superclass or any implemented interface is generic.

The format of the type parameter expression is described in Parameterized Types. The type variable defined by the type parameter’s type expression can be used just like a normal type inside the class definition.

If using a generic classifier as type of a variable, it may be parameterized. This is usually done via a type expression (cf. Parameterized Types) or via typearg in case of supertypes. If a generic classifier defines multiple type variables, these variables are bound in the order of their definition. In any case, all type variables have to be bound. That means in particular that raw types are not allowed. (cf Parameterized Types for details).

If a generic classifier is used as super classifier, the type arguments can be type variables. Note that the type variable of the super classifier is not lifted, that is to say that all type variables are to be explicitly bound in the type references used in the extend, with, or implements section using typearg. If a type variable is used in typearg to bound a type variable of a type parameter, it has to fulfil possible type constraints (upper/lower bound) specified in the type parameter.

Example 33. Generic Type Definition and Usage as Type of Variable

This example demonstrates how to define a generic type and how to refer to it in a variable definition.

export class Container<T> {
    private item: T;

    getItem(): T {
        return this.item;
    }

    setItem(item: T): void {
        this.item = item;
    }
}

This type can now be used as a type of a variable as follows

import Container from "p/Container"

var stringContainer: Container<string> = new Container<string>();
stringContainer.setItem("Hello");
var s: string = stringContainer.getItem();

In line 3, the type variable T of the generic class Container is bound to string.

Example 34. Binding of type variables with multiple types

For a given generic class G

class A{}
class B{}
class C extends A{}

class G<S, T extends A, U extends B> {
}

the variable definition

var x: G<Number,C,B>;

would bind the type variables as follows:

S

Number

Bound by first type argument, no bound constraints defined for S.

T

C

Bound by second type argument, C must be a subtype of in order to fulfill the type constraint.

U

B

Bound by third type argument, extends is reflexive, that is B fulfills the type constraint.

For a given generic superclass SuperClass

class SuperClass<S, T extends A, U extends B> {};

and a generic subclass SubClass

class SubClass<X extends A> extends SuperClass<Number, X, B> {..};

the variable definition

var s: SubClass<C>;

would bind the type variables as follows:

TypeVariable Bound to Explanation

SuperClass.S

Number

Type variable s of supertype SuperClass is bound to Number.

SuperClass.T

SubClass.X=C

Type variable T of supertype SuperClass is bound to type variable X of SubClass. It gets then indirectly bound to C as specified by the type argument of the variable definition.

SuperClass.U

B

Type variable U of supertype SuperClass is auto-bound to C as no explicit binding for the third type variable is specified.

SubClass.X

C

Bound by first type argument specified in variable definition.

5.1.6. Definition-Site Variance

In addition to use-site declaration of variance in the form of Java-like wildcards, N4JS provides support for definition-site declaration of variance as known from languages such as C# and Scala.

The variance of a parameterized type states how its subtyping relates to its type arguments’ subtyping. For example, given a parameterized type G<T> and plain types A and B, we know

  • if G is covariant w.r.t. its parameter T, then

    B<:AG<B><:G<A>

  • if G is contravariant w.r.t. its parameter T, then

    B<:AG<A><:G<B>

  • if G is invariant w.r.t. its parameter T, then

    B<:AG<A><:G<B>
    B<:AG<A><:G<B>

Note that variance is declared per type parameter, so a single parameterized type with more than one type parameter may be, for example, covariant w.r.t. one type parameter and contravariant w.r.t. another.

Strictly speaking, a type parameter/variable itself is not co- or contravariant;
however, for the sake of simplicity we say T is covariant as a short form for G is covariant with respect to its type parameter T (for contravariant and invariant accordingly).

To declare the variance of a parameterized classifier on definition site, simply add keyword in or out before the corresponding type parameter:

class ReadOnlyList<out T> { // covariance
    // ...
}

interface Consumer<in T> { // contravariance
    // ...
}

In such cases, the following constraints apply.

Req. IDE-174: Definition-Site Variance (ver. 1)

Given a parameterized type with a type parameter , the following must hold:

  1. T may only appear in variance-compatible positions:

    1. if T is declared on definition site to be covariant, then it may only appear in covariant positions within the type’s non-private member declarations.

    2. if T is declared on definition site to be contravariant, then it may only appear in contravariant positions within the type’s non-private member declarations.

    3. if T is invariant, i.e. neither declared covariant nor declared contravariant on definition site, then it may appear in any position (where type variables are allowed).

      Thus, no restrictions apply within the declaration of private members and within the body of field accessors and methods.

  2. definition-site variance may not be combined with incompatible use-site variance:

    1. if T is declared on definition site to be covariant, then no wildcard with a lower bound may be provided as type argument for T.

    2. if T is declared on definition site to be contravariant, then no wildcard with an upper bound may be provided as type argument for T.

    3. if T is invariant, i.e. neither declared covariant nor declared contravariant on definition site, then any kind of wildcard may be provided as type argument.

      Unbounded wildcards are allowed in all cases.

Example 35. Use-site declaration of variance

For illustration purposes, let’s compare use-site and definition-site declaration of variance. Since use-site variance is more familiar to the Java developer, we start with this flavor.

class Person {
    name: string;
}
class Employee extends Person {}

interface List<T> {
    add(elem: T)
    read(idx: int): T
}

function getNameOfFirstPerson(list: List<? extends Person>): string {
    return list.read(0).name;
}

Function getNameOfFirstPerson below takes a list and returns the name of the first person in the list. Since it never adds new elements to the given list, it could accept Lists of any subtype of Person, for example a List<Employee>. To allow this, its formal parameter has a type of List<? extends Person> instead of List<Person>. Such use-site variance is useful whenever an invariant type, like List above, is being used in a way such that it can be treated as if it were co- or contravariant.

Sometimes, however, we are dealing with types that are inherently covariant or contravariant, for example an ImmutableList from which we can only read elements would be covariant. In such a case, use-site declaration of variance is tedious and error-prone: we would have to declare the variance wherever the type is being used and would have to make sure not to forget the declaration or otherwise limit the flexibility and reusability of the code (for example, in the above code we could not call getNameOfFirstPerson with a List<Employee>).

The solution is to declare the variance on declaration site, as in the following code sample:

interface ImmutableList<out T> {
//  add(elem: T)  // error: such a method would now be disallowed
    read(idx: int): T
}

function getNameOfFirstPerson2(list: ImmutableList<Person>): string {
    return list.read(0).name;
}

Now we can invoke getNameOfFirstPerson2 with a List<Employee> even though the implementor of getNameOfFirstPerson2 did not add a use-site declaration of covariance, because the type ImmutableList is declared to be covariant with respect to its parameter T, and this applies globally throughout the program.

5.2. Members

A member is either a method (which may be a special constructor function), a data field, or a getter or a setter. The latter two implicitly define an accessor field. Similar to object literals, there must be no data field with the same name as a getter or setter.

(overriding, implementation and consumption) is described in Redefinition of Members.

5.2.1. Syntax

Syntax N4JS member access modifier
enum N4JSMemberAccessModifier: private | project | protected | public;

N4MemberDeclaration: N4MethodDeclaration | N4FieldDeclaration | N4GetterDeclaration | N4SetterDeclaration;
5.2.1.1. Properties

Members share the following properties:

annotations

Arbitrary annotations, see Annotations for details.

accessModifier

N4JS member access modifier: private, project, potected, or public; the latter two can be combined with @Internal; default is project for classes and private interfaces. For a non-private interface defaults to the interface’s visibility.

name

The simple name of the member, that is an identifier name (cf. Valid Names).

static

Boolean property to distinguish instance from classifier members, see Static Members.

The following pseudo properties are defined via annotations:

deprecated

Boolean property set to true if annotation @Deprecated is set. [29]

And we additionally define the following pseudo properties:

acc

Member access modifier as described in Accessibility of Members, it is the aggregated value of the accessModifier and the export property.

owner

Owner classifier of the member.

typeRef

Type of the member—this is the type of a field or the type of the method which is a function type (and not the return type).

assignability

Enumeration, may be one of the following values:

set

Member may only be set, i.e. it could only be used on the left hand side of an assignment.

get

Member may only be retrieved, i.e. it could only be used on the right hand side of an assignment. This is the default setting for methods.

any

Member may be set or retrieved, i.e. it could only be used on the left or right hand side of an assignment. This is the default setting for fields.

assignability is related but not equal to writable modifiers used for fields. We define a partial order on this enumeration as follows:
<lr::=setanygetany
abstract

All members have a flag abstract, which is user-defined for methods, getters and setter, but which is always false for fields.

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

configurable

Boolean flag reflecting the property descriptor configurable, this is always set to false for members.

5.2.2. Semantics

The members of a given classifier C must be named such that the following constraints are met:

Req. IDE-52: Member Names (ver. 1)

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

  2. No two members may have the same name, except one is static and the other is non-static:

    m1,m2C.ownedMembers,m1m2:m1.namem2.namem1.staticm2.static
  3. The member name must be a valid identifier name, see Identifier Grammar.

Thus, overloading of methods is not supported [30] and no field may have the same name as a method. However, overriding of methods, getters, and setters are possible, see Redefinition of Members. Static members may also have the same name as non-static members.[31]

The dollar character $ is not allowed for user-defined member identifiers as the dollar sign is used for rewriting private members.

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*
        (
            variadic?='...'? name=BindingIdentifier<Yield> ColonSepTypeRef?
        )
    )
    ('=' 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:
 tpmm.typePars,tpCC.typePars:tpm.nametpC.name

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.ownedCtorC.ownedMembers. Note that C.ctorC.ownedMethods in all cases.

The return type of the constructor of a class C is C. If C has type parameters T1,...,Tn, then the return type is C<T1,...,Tn>. 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:
    aC.attr:a.requireder.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 MS.members: M.acc=publicM.name=SM.name

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

    MC.members:M.acc=publicM.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:

    sctor.fpar.structuralMembers,ctor.fpar.spec:
    fctor.owner.ownedFieldsΓs<: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-public field has to be set in the constructor via the with to the parameter otherwise properties are not used to set non-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' and C' is annotated with @CovariantConstructor or C' 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:

      overrideCompatiblectorS.constructor

      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

      overrideCompatiblectorI.constructor

      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 mT 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:

      overrideCompatiblectorCctorSup

      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 true.[32]

declaredWriteable

Boolean flag reflecting the property descriptor writeable, set via annotation @Writeable(true|false). The default value is 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.requireda.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:
     mf.owner.members:mfm.namef.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:

Γf:Γd

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

d.declaredTypenullT=d.declaredTypeΓd:T
d.declaredType=nulld.expressionnullΓd:T
E=Γd.expressionEnull, undefinedT=E}
elseΓd:any

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

Γtfield.typeRef:TΓTField tfield:T
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.exprnull

  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:
    subf.expr*:sub="this"

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

  4. Constant data fields are not writeable (cf. [Req-IDE-68]):
    f.const¬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¬f.const¬f.declaredStatic

  2. A final data field is not writeable:
    f.final¬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.

    f.exprnull(assignExp:assignExpr.containingFunction=f.owner.constructorassignExpr.left.target="this"bindassignExpr.left.propertyf)(f.publicfparf.owner.constructor.fpars:fpar.specsmstructuralMembers:sm.name=f.name)
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)*
    GetterHeader<Yield>)
    (body=Block<Yield>)? ';'?
;

fragment GetterHeader <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.names.static=g.static is inherited by C from one of its super classes, then C must define a setter s' with s'.name=g.names'.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.

Table 7. Optional Fields

Δ

Case

Comment

~

~~

~w~

~r~

~i~

may have setter

never has setter

let x: ΔC = {};

1

nothing mandatory

let x: ΔC = new D0();

2

setters mandatory

let x: ΔC = new DG();

2

setters mandatory

let x: ΔC = new DS();

2

setters mandatory

let x: ΔC = fooD0();

none

D0 not final

let x: ΔC = fooSF0();

none

fooSF0() not nominal

let x: ΔC = fooF0();

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.staticassignExpr.left=T.fT=f.owner.

  • For every writing unary expression u with u.op++--f.staticu.expression=T.fT=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.nameM.static=S.static

  2. The metatypes are compatible:

    μS=MethodμM=Method
    μS=FieldμMField, Getter, Setter
    μS=GetterμMField, Getter
    μS=SetterμMField, 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.constM.const

  5. It is not possible to override a non-final / non-const field or a setter with a final / const field:
    μS=Field¬S.finalS.constμS=Setter¬μM=FieldM.finalM.const

  6. It is not possible to override a non-abstract member with an abstract one:
    ¬M.abstractS.abstract

  7. The types are compatible:

    μMMethod, Getter, FieldμSSetterΓM<:S
    μMSetter, FieldμSGetter¬S.constΓS<:M4

  8. The access modifier is compatible:
    M.accS.acc

We define a relation overrideCompatibleMS 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 Ii) does not override or implement another member, then it must not be declared as override. That is the following constraint must hold:

¬M.override

M'C.super.membersi=1nIi.members:
M'.name=M.nameM'.static=M.static
M'.acc>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.ctorC.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:
    overrideCompatibleMS

  3. If S is a field and M is an accessor, then an additional accessor M' must exists so that M,M' are an accessor pair for S:

    μS=FieldμM=Accessor
    M'C.member:
    overrideCompatibleM'SμMμM'=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 MC and MI as follows:

Given a C, and interface I1,...,In, implemented by C, with

MC=C.ownedMembers{mC.superType.members|m.acc>private}
MI=i=1nIi.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, MC.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 I1,...,In implemented (or extended) by C, and sets MC and MI as defined in [interface_and_class_member_sets]. A non-static member M defined in any interface Ii is merged into the consumer (C), if for all other (possible) members M' of C

M'MCMIM:M.name=M'.name¬M'.static

the following constraints hold:

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

    μM=MethodμM'=Method
    μMMethodμM'Field, FieldAccessor

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

    μM=μM'μM=Field
    M'.abstractM'C.ownedMembers

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

    μM=μM'μM=Field
    M.accM'.acc

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

    μMMethod, Getter, FieldμM'SetterΓM<:M'
    μMSetter, FieldμM'GetterΓM'<: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' must exist in C:

    1. if M is not a field:

      μMField
      M'C.members:
      overrideCompatibleM'M
      M'C.ownedMembersM'.override

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

      μM=Field
      F'C.fields:
      overrideCompatibleF'M
      F'C.ownedMembersF'.override

      G'C.getters,S'C.setters:
      overrideCompatibleG'M
      overrideCompatibleS'M
      G'C.ownedMembersG'.override
      S'C.ownedMembersS'.override

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

    MC.methods,M'Ii.methodsi=1...n:
    M.name=M'.nameM.accprivateM.accM'.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 M1,...,Mn defined in different implemented interfaces and a single owned method M' in MC. In this case, the above constraints must hold for all methods. In particular, M'’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' 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.

Table 8. Consumption of methods

Interface I1

MI1

MI1

MI1

MI1

MI1

MI1

Interface I2

MI2

MI2

MI2

class S

MS

MS

MS

class C

MC

MC

MC

C.members

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: x instanceof T¬T.defStructural

  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:N4ObjectT.defStructural}
      mT.members,m.acc=public,¬m.static,mT.ctor:}
      m'X.members:}
      m'.acc=public¬m'.staticm'.name=m.name}
      Γm'<:m}
      μm=FieldΓm<:m'ΓX<:T
      or

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

      X<:N4ObjectT.defStructuralTX.superTypes*ΓX<: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

      T.defStructuralΓT<:Object

      T.defStructuralΓT:N4ObjectN4ObjectT.superTypes*

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: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. ~w~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 ~X ~~X ~r~X ~w~X ~i~X

public method

public writable field

public readable field

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

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

}

Structural Read-only Field Type

Structural Write-only Field Type

Structural Initializer Field Type

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:

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
}

Type

Structural Type

Structural Field Type

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.

  • We use T.isStructural to simply refer any structural typing, i.e.+ T.isStructural=T.refStructuralT.refStructuralFieldT.refStructuralReadOnlyField \lor T.refStructuralWriteOnlyField || T.refStructuralInitField || T.defStructural$

  • We use T.isNominal as the opposite of T.isStructural, i.e.
    T.isNominal=¬T.isStructural

We call the following:

  • T the (nominal) type T,

  • ~T the structural version of T,

  • ~~T the structural field version of T,

  • ~r~T the structural read-only field,

  • ~w~T the structural write-only field and

  • ~i~T the structural initializer field version of T.

Req. IDE-76: Use-Site Structural Typing (ver. 1)

  1. The structural version of a type is a supertype of the nominal type:
    T<::~T

  2. The structural field version of a type is a supertype of the structural type:
    ~T<::~~T

  3. The structural read-only field version of a type is a supertype of the structural field type:
    ~~T<::~r~T

  4. The structural write-only field version of a type is a supertype of the structural field type:
    ~~T<::~w~T

  5. The structural (field) version of a type cannot be used on the right hand side of the instanceof operator:

    x instanceof EΓE:T
    ¬(T.refStructural
    T.refStructuralField
    T.refStructuralReadOnlyField
    T.refStructuralWriteOnlyField
    T.refStructuralInitField)

    That is, the following code will always issue an error: x instanceof ~T [42].

  6. A type X is a subtype of a structural version of a type ~T, if it contains all public, non-static members of the type T: [43]

    mT.members,m.ownerN4Object,m.acc=public,¬m.static,mT.ctor:
    m'X.members:
    m'.acc=public¬m'.staticm'.name=m.name
    Γm'<:ΓmΓX<:Γ~T

  7. A type X is a subtype of a structural field version of a type ~~T, if it contains all public, non-static fields of the type T. Special cases regarding optional fields are described in Optional Fields.

    mT.fields,m.ownerN4Object,m.acc=public,¬m.static
     m'X.fields:
    m'.acc=public¬m'.staticm'.name=m.name
    Γm':TmΓm:Tm'Tm=Tm'}
    m'.assignabilitym.assignabilityΓX<:~~T

  8. A type X is a subtype of a structural read-only field version of a type ~r~T, if it contains all public and non-static readable fields of the type T. Special cases regarding optional fields are described in Optional Fields.

    mT.fieldsT.getters,m.ownerN4Object,m.acc=public,¬m.static
     m'X.fieldsX.getters:
    m'.acc=public¬m'.staticm'.name=m.name
    Γm':TmΓm:Tm'Tm=Tm'
    m'.assignabilitym.assignabilityΓX<:~r~T

  9. A type X is a subtype of a structural write-only field version of a type ~w~T, if it contains all public and non-static writable fields of the type T. Special cases regarding optional fields are described in Optional Fields.

    mT.fieldsT.setters,m.ownerN4Object,m.acc=public,¬m.static,¬m.final
     m'X.fieldsX.setters:
    m'.acc=public¬m'.staticm'.name=m.name
    Γm':TmΓm:Tm'Tm=Tm'
    m'.assignabilitym.assignabilityΓX<:~w~T

  10. A type X is a subtype of a structural field version of a type ~~this, if it contains all public, non-static fields, either defined via data fields or field get accessors, of the inferred type of this. Special cases regarding optional fields are described in Optional Fields.

    Γthis:T
    mT.fieldsT.setters,m.ownerN4Object,m.acc=public,¬m.static
    m.exprnull
     m'X.fieldsX.getters:
    m'.acc=public¬m'.staticm'.name=m.name
    Γm'<:mm'.assignabilitym.assignabilityΓX<:~~this

  11. A structural field type ~~T is a subtype of a structural type ~X, if ~X only contains fields (except methods inherited from Object) and if ~~T<:~~X.

    X.methodsObject.methods=Γ~~T<:~~XΓ~~T<:~X
  12. Use-site structural typing cannot be used for declaring supertypes of classes or interfaces. That is to say that structural types cannot be used after extends, implements or with in type declarations [44].

Note that all members of N4Object are excluded. This implies that extended reflective features (cf. Reflection meta-information ) are not available in the context of structural typing. The instanceof operator is still working as described in Relational Expression, in that it can be used to check the type of an instance.

If a type X is a (nominal) subtype of T, it is, of course, also a subtype of ~T:

ΓX<:ΓTΓX<:Γ~T

This is only a shortcut for the type inference defined above.

Req. IDE-77: Definition and Use-Site Precedence (ver. 1)

If a type is structurally typed on both definition and use-site, the rules for use-site structural typing ([Req-IDE-76]) are applied.

Example 53. Use-Site Structural Typing

The following example demonstrates the effect of the structural (field) modifier, used in this case for function parameters.

interface I { public x: number; public foo()};
class C { public x: number; public foo() {}};

function n(p: I) {}
function f(p: ~I) {}
function g(p: ~~I) {}

n(new C());     // error: nominal typing, C does not implement I
f(new C());     // ok: C is a (structural) subtype of ~I
f({x:10});      // error, the object literal does not provide function foo()
g({x:10});      // ok: object literal provides all fields of I
Example 54. Structural types variable and instanceof operator

It is possible to use a variable typed with a structural version of a type on the left hand side of the instanceof operator, as demonstrated in this example:

class C {
    public x;
    public betterX() { return this.x||1;}
}

function f(p: ~~C) {
    if (p instanceof C) {
        console.log((p as C).betterX);
    } else {
        console.log(p.x||1);
    }
}

The following table describes the member availability of X in various typing scenarios. Such as ~~X, ~r~X, ~w~X, ~i~X.

Table 10. Member Availability in various Typing Scenarios
Member of type X ~~X ~r~X ~w~X ~i~X

private m0;

 — 

 — 

 — 

 — 

public set m1(m) { }

write

 — 

write

read

public get m2() {…​}

read

read

 — 

public m3;

read/write

read

write

read

public m4 = 'init.m4';

read/write

read

write

read ?

public m5: any?;

read?/write

read?

write

read?

@Final public m6 = 'init.m6';

read

read

@Final public m7;

read

read

read

public get m8() {…​}

read/write

read

write

read

public set m8(m) { }

5.3.4. Structural Read-only, Write-only and Initializer Field Typing

Structural read-only, write-only and initializer field typings are extensions of structural field typing. Everything that is defined for the field structural typing must comply with these extension field typings. For the read-only type, readable fields (mutable and @Final ones) and setters are considered, for the write-only type; only the setters and mutable fields are considered. Due to the hybrid nature of the initializer type it can act two different ways. To be more precise, a type X (structural initializer field type) is a supertype of Y (structural initializer field type) if for each public, non-static, non-optional writable field (mutable data field of setter) of X, there is a corresponding, public, non-static readable field of Y. All public member fields with @Final annotation are considered to be mandatory in the initializer field typing @Spec constructors. The already-initialized @Final fields can be either omitted from, or can be re-initialized via, an initializer field typing @Spec style constructor.

Example 55. Subtype relationship between structural field typing
class A1 {
    public s: string;
}

class A2 {
    public set s(s: string) { }
    public get s(): string { return null; }
}

class A3 {
    @Final public s: string = null;
}

class A4 {
    public get s(): string { return null; }
}

class A5 {
    public set s(s: string) { }
}

A1

~A1

~~A1

~r~A1

~r~A2

~r~A3

~r~A4

~r~A5

~w~A1

~w~A2

~w~A3

~w~A4

~w~A5

~i~A1

~i~A2

~i~A3

~r~A4

~r~A5

A1

~A1

~~A1

~r~A1

~r~A2

~r~A3

~r~A4

~r~A5

~w~A1

~w~A2

~w~A3

~w~A4

~w~A5

~i~A1

~i~A2

~i~A3

~r~A4

~r~A5

5.3.5. Public Setter Annotated With ProvidesInitializer

Public setters with ProvidesInitializer annotation can declare optional fields implemented by means of field accessors instead of data fields. Data fields with an initializer are treated as optional in the initializer field type.

It is important to note that it is valid to use the ProvidesInitializer annotation for setters in n4js files and not only definition files.

Example 56. Setters with @ProvidesInitializer treated as optional
class C {
    private _y: int = 1;

    public get y() { return this._y; }
    @ProvidesInitializer
    public set y(v: int) { this._y = v; }

    public constructor(@Spec spec: ~i~this) { }
}

console.log(new C({}).y); // 1
console.log(new C({y: 42}).y); //24

5.3.6. Structural Types With Optional Fields

Public optional fields become a member of the structural (field) type as well. To ensure the overall type safety, the semantics of optionality (e.g. on or off) depends on the context, in which the optional fields are currently being used (See Optional Fields).

5.3.7. Structural Types With Access Modifier

The access modifier of the subtype have to provide equal or higher visibility.

Example 57. Access modifier in structural typing
class C {
    public s: number;
}
class D {
    project s: number;
}
function f(c: ~C) {}
f(new D()); // error: D is no (structural) subtype of ~C, as visibility of s in D is lower
function g(d: ~D) {}
g(new C()); // ok: C is a (structural) subtype of ~D, as visibility of s in C is greater-than-or-equal to s in D

5.3.8. Structural Types With Additional Members

It is possible to add additional members when structurally referencing a declared type.

5.3.8.1. Syntax
TStructMember:
    TStructGetter | TStructGetterES4 | TStructSetter | TStructMethod | TStructMethodES4 | TStructField;

TStructMethod:
    =>
    ({TStructMethod} ('<' typeVars+=TypeVariable (',' typeVars+=TypeVariable)* '>')?
    returnTypeRef=TypeRef name=TypesIdentifier '(')
        (fpars+=TAnonymousFormalParameter (',' fpars+=TAnonymousFormalParameter)*)? ')'
    ';'?;

TStructMethodES4 returns TStructMethod:
    =>
    ({TStructMethod} ('<' typeVars+=TypeVariable (',' typeVars+=TypeVariable)* '>')?
        name=TypesIdentifier '(')
        (fpars+=TAnonymousFormalParameter (',' fpars+=TAnonymousFormalParameter)*)? ')'
        (':' returnTypeRef=TypeRef)?
    ';'?;

TStructField:
    (
        typeRef=TypeRef name=TypesIdentifier
        | name=TypesIdentifier (':' typeRef=TypeRef)?
    )
    ';';

TStructGetter:
    => ({TStructGetter}
    declaredTypeRef=TypeRef
    'get'
    name=TypesIdentifier)
    '(' ')' ';'?;

TStructGetterES4 returns TStructGetter:
    => ({TStructGetter}
    'get'
    name=TypesIdentifier)
    '(' ')' (':' declaredTypeRef=TypeRef)? ';'?;

TStructSetter:
    => ({TStructSetter}
    'set'
    name=TypesIdentifier)
    '(' fpar=TAnonymousFormalParameter ')' ';'?;

TAnonymousFormalParameter:
    typeRef=TypeRef variadic?='...'? name=TIdentifier?
    | variadic?='...'? (=> name=TIdentifier ':') typeRef=TypeRef;
5.3.8.1.1. Semantics

Req. IDE-78: Additional structural members (ver. 1)

It is only possible to add additional members to a type if use-site structural typing is used.

The following constraints must hold:

  1. For all additional members defined in a structural type reference, the constraints for member overriding ([Req-IDE-72]) apply as well.

  2. All additional members have the access modifier set to public.

  3. Type variables must not be augmented with additional structural members.

Additional fields may be declared optional in the same way as fields in classes. The rules given in Structural Types With Optional Fields apply accordingly. Consider the following example:

Example 58. Additional optional members in structural typing
class C {
    public f1: number;
}

var c1: ~C with { f3: string; } c1;
var c2: ~C with { f3: string?; } c2;

c1 = { f1:42 };  // error: "~Object with { number f1; } is not a subtype of ~C with { string f3; }."
c2 = { f1:42 };  // ok!!

Augmenting a type variable T with additional structural members can cause collisions with another member of a type argument for T. Hence, type variables must not be augmented with additional structural members like in the following example.

Example 59. Forbidden additional structural members on type variables
interface I<T> {
    public field : ~T with {prop : int} // error "No additional structural members allowed on type variables."
}

Using an additional structural member on a type variable T could be seen as a constraint to T. However, constraints like this should be rather stated using an explicit interface like in the example below.

Example 60. Use explicitly defined Interfaces
interface ~J {
    prop : int;
}
interface II<T extends J> {
}

Quick Links