8. Expressions
For all expressions, we define the following pseudo properties:
containingExpression
-
The parent expression, in which an expression is contained, may be null.
containingStatement
-
The statement in which the expression is (indirectly) contained.
containingFunctionOrAccessor
-
The function, method, getter or setter in which the expression is (indirectly) contained, may be null.
containingClass
-
The class in which the expression is (indirectly) contained, may be null.
probableThisTarget
-
The potential target of a this keyword binding, this is not necessarily the containing class or object literal. In case of instance methods of a class
T
, this usually is the classifierT
; in case of static methods, it is the classifier typetype{type}
. container
-
The direct owner of the expression. z
The expressions and statements are ordered, describing first the constructs available in the 5th edition of ECMA-262, referred to as [ECMA11a] in the following. It is worth noting that the grammar snippets already use newer constructs in some cases.
8.1. ECMAScript 5 Expressions
N4JS supports the same expressions as ECMAScript. The semantics are described in [ECMA11a(p.S11)]. In N4JS, some expressions are extended for supporting the declaration of types, annotations, or parameterized usages. These extensions and type-related aspects as well as specific N4JS constraints are described in this section.
Some operators come in different ’flavors’, that is as binary operator, unary pre- or postfix operators, or assignment operators.
For these operators, type constraints are only defined for the binary operator version and the other variants are deduced to that binary version.
E.g., ++
and +=
are deduced to +
(and simple assignment).
8.1.1. The this Literal
This section describes the this
literal and the semantics of the @This
annotation, the type this
is described in This Type.
Semantics
Semantics are similar to the original ECMAScript this keyword, see [ECMA11a(p.11.1.1, p.p.63)]) Also see [West06a] and MozillaJSRef
Regarding the location where this
may be used, the following restrictions apply:
Req. IDE-173: Valid location for this literal (ver. 1)
The literal may not be used in
-
the initializer expression of static data fields in classes.
-
the initializer expression of data fields in interfaces (applies to both static and non-static).
-
static methods of interfaces and static field accessors of interfaces.
See also [Req-IDE-69].
Note: [Req-IDE-173] also applies for this literals inside arrow expressions in initializers.
The use of this
is illustrated with some examples as it can often be confusing.
Type inference heuristics and explanations are provided in the next section.
In unrestricted mode, this
is bound to the receiver.
If there is no receiver it is bound to the global object, however, we often do not know exactly what the global object would be.
var name = "global a"; // assume the top level is similar to the global object
this.name; // <-- "global a"
function f() {
return this.name; // <-- depends on call, usually "global a"
}
var ol1 = {
name: "John",
greeting: "Hello " + this.name, // "Hello global a" -- we do not greet John!
}
var ol2 = {
name: "John",
f: function() {
this.name; // usually "John", as we assume f is called like ol2.f()
var g = function() {
return this.name; // "global a"
}
return g(); // no receiver, this in nested function g will be global scope
}
}
In strict mode, this
is bound to the receiver.
If there is no receiver, it is bound to undefined
.
Thus, we will probably get a lot of errors:
"use strict"
var name = "global a"; // assume the top level is similar to the global object
this.name; // <-- error, this is undefined, there is no receiver
function f() {
return this.name; // <-- depends on call, usually this produces an error as this is undefined
}
var ol1 = {
name: "John",
greeting: "Hello " + this.name, // will produce an error, as this is undefined
}
var ol2 = {
name: "John",
f: function() {
this.name; // usually "John", as we assume f is called like ol2.f()
var g = function() {
this.name; // an error, see call below:
}
return g(); // no receiver, this in nested function g is undefined
}
}
As in strict mode, this
is bound to the receiver and if there is no receiver, it is bound to undefined
. So the example above is also true for N4JS mode.
Classes behave slightly differently:
class A {
name = "John";
greeting = "Hello " + this.name; // works, in N4JS classes, greeting is "Hello John"
f() {
return this.name; // this usually is instance object, similar to object literals.
}
g() {
var h = function() {
return this.name; // as in object literals: no receiver, no this.
}
return h();
}
}
In N4JS classes, this is always bound to the instance when used in field initialization.
|
Type Inference
The type is inferred from the this
type is bound to. The inference,
therefore, has to consider the original semantics as described in [ECMA11a(p.10.4., p.10.4.3, p.p.58)].
In ECMAScript the type of this is unfortunately determined by the function call and not by the function definition:
-
By default,
this
is bound to the global object [ECMA11a(p.10.4.1.1)]. Unfortunately it is often unknown what the global object will be at run time (e.g., node.js differs from browsers). -
If a function is called without a receiver,
this
is bound to-
the global object or
-
to
undefined
in strict mode.
-
-
If a function is called with a receiver,
this
is bound to the receiver object.
Actually, this
is bound to the newly created object if a function is called with the new
operator.
If a function is known to be invoked with an explicit (apply()
etc.), the @This
annotation can be used to explicitly set the this type.
This annotation has precedence over otherwise inferred bindings.
Req. IDE-90: Type Inference Heuristic for This-Keyword (ver. 1)
In general, the actual this target can not be inferred from the context of the this keyword. A heuristic is defined, however, to compute the probable this type:
-
If the this keyword is used in some function annotated with an annotation
@This
, the type specified in the annotation is used. The inferred type is always nominal.
-
If the this keyword is used in some instance method of a classifier or in an instance field initializer,
this
is bound to theT
itself. If the this keyword is used in some static method of a classifierT
or in a static field initializer, the prototype type (or constructor) of the classifier is used, that istype[T]
. In both cases, the target is determined by using the expressions’s pseudo propertyprobableThisTarget
. If the this keyword is used in a function expression assigned to an property of an object literal, the type of the object literal is used. Note that usually this is thethis
type in instance methods, and thethis
type in static methods. -
In all other cases: Non-strict mode:
Strict mode and N4JS mode:
If the actual this type is defined as a structural type, the structural type information is moved to the this type itself. This is transparent to the user in general but maybe visible in case of error messages. That is to say that the actual this type is always a nominal type. This is indicated by the nominal modifier (cf. [Req-IDE-90] constraints 1 and 2.).
Req. IDE-91: Valid Target and Argument for @This Annotation (ver. 1)
-
The
@This
annotation is only allowed on declared functions, function expressions (including arrow functions), methods, and field accessors, i.e. getters and setters, except static members of interfaces. -
The type declared by way of
@This(..)
an annotation of a method or field accessor must be a subtype of the member’s containing classifier.
Req. IDE-92: Single @This Annotation (ver. 1)
It is not allowed to use more then one @This(..)
annotation on an element.
Given the following declaration
@This(~Object with {a: string;}) f() {}
Since the this type is always nominal, ~ Object
becomes Object
.
In case of method call, however, the returned value becomes structural again.
In case of error messages the type of the return type is then
~this[Object] with {a: string;}
For the sake of simplicity, additional structural members are usually omitted in error messages, leading to
~this[Object]
instead of
this[~Object]
This example demonstrates the usage of functions annotated with @This
.
By using the argument union{A,B}
it is possible to have two completely unrelated classes as the receiver type of the function logger
.
To pass an actual object the apply()
method of the function is used.
class A {
log: string() { return "A was logged"; }
}
class B {
log: string() { return "B was logged"; }
}
@This(union{A,B})
function logger() { console.log("~ "+this.log()+" ~"); }
var a: A = new A();
logger.apply(a,[]); // prints "~ A was logged ~"
logger.apply( new B(),[]) // prints "~ B was logged ~"
In this example a function is created via a function expression.
The function is then assigned to member field of class B.
Via annotating the expression with @This(B)
, access to the receiver of type B is enabled.
class B {
log(): string { return "B was logged"; } // method
logMe : {@This(B) function():void}; // reference to a function
}
var b: B = new B();
b.logMe = @This(B) function() { console.log("*>"+this.log()+"<*"); }
b.logMe(); // prints "*>B was logged<*"
Note that if a function is called as a constructor function with new, the
type of this
can be declared via annotation @This(..)
, as shown in the following
snippet:
@This(
~Object with {
w: number; h: number;
area: {function():number};
})
function Box(w: number w, h: number) {
this.w = w;
this.h = h;
this.area = @This(
~Object with {
w: number; h: number;
area: {function():number};
}) function() { return this.w * this.h }
}
var bError = Box(1,2)
var bOK = new Box(1,2)
Inside the constructor function Box
, this
is bound to the structural type definition due to the annotation.
Inside the nested function area
, this
is bound to the receiver object (if the function is called like bOk.area()
).
Again, this depends on the way the nested function is called, which can usually not be determined at the declaration location.
The nested function must then be annotated accordingly.
When calling this function, the type of this is checked against the declared this type, which would cause an error in the first case.
The use of the @This
annotation is not allowed on methods.
Using constructor functions is not recommended and an error or warning will be created. This is only useful for adapting third-party library code. Even in the latter case, it would probably make more sense to declare a (library) class Rectangle rather then defining the constructor function. |
8.1.2. Identifier
Syntax
Identifiers as expressions are identifier references. They are defined as follows:
IdentifierRef <Yield>:
id=[types::IdentifiableElement|BindingIdentifier<Yield>]
;
BindingIdentifier <Yield>:
IDENTIFIER
| <!Yield> 'yield'
| N4Keyword
;
Semantics
The type of an identifier is resolved depending on its binding and scope respectively (cf. [ECMA11a(p.10.2.2.1GetIdentifierReference, p.p.56)]. The following scopes (aka Lexical Environments) are defined:
-
function local; local variables, parameters
-
zero or more function closure in case of nested functions
-
module
-
global
These scope are nested as illustrated in Scopes.
Note that classes definitions and object literal do not define a scope: members of a class or properties of an object literal are to be accessed via this
.
Identifier references always reference declared elements, that is to say either variable, function, or class declarations.
Properties of object literals or members of a class are referenced via (see Property Accessors).
An identifier may be bound to a variable (global or local variable, parameter, variable defined in a function’s closure), or to a property of an object. The latter case is known as property access as further described in Property Accessors.
Req. IDE-93: Read Access to Identifier (ver. 1)
If an identifier is accessed, the bound declared element must be readable if it is not used on the left-hand side of an assignment expression.
Type Inference
An identifier reference is bound to an identifiable element , which is expressed with the function . The type of the reference is then inferred as follows:
8.1.3. Literals
Type Inference
The type of a literal can directly be derived from the grammar. The following axioms are defined for literals:
Note that there are no literals specific for pathSelector
or i18nkey
.
8.1.3.1. Integer Literals
Numeric literals representing integers in the range of JavaScript’s int32 are inferred to the built-in primitive type int
instead of number
.
The following rules apply:
Req. IDE-94: Numeric literals (ver. 1)
-
Numeric literals with a fraction or using scientific notation, e.g.
2.0
and2e0
, respectively, are always inferred tonumber
, even if they represent integers in the range of int32. -
Numeric literals that represent integers in the range of JavaScript’s int32, i.e. from to , are inferred to
int
. -
Hexadecimal and octal literals are always interpreted as positive numbers, so all values above
0x7fffffff
and017777777777
lie outside the range of int32 and will thus be inferred tonumber
; this is an important difference to Java. See below for further elaboration.
There are differences to numeric literals in Java:
Java | JavaScript N4JS | |||
---|---|---|---|---|
Literal |
Value |
Type |
Value |
Type |
|
-2147483648 |
|
-2147483648 |
|
|
2147483647 |
|
2147483647 |
|
|
2147483647 |
|
2147483647 |
|
|
-2147483648 |
|
+2147483648 |
|
|
-1 |
|
4294967295 |
|
|
n/a |
4294967296 |
|
|
|
2147483647 |
|
2147483647 |
|
|
-2147483648 |
|
+2147483648 |
|
|
-1 |
|
4294967295 |
|
|
0 |
|
4294967296 |
|
|
n/a |
8589934592 |
|
The literals 0x100000000
and 0100000000000
produce a syntax error in Java.
Until IDE-1881 is complete, all built-in operations always return a number
even if all operands are of type int
.
For the time being, we therefore interpret -1
as a negative integer literal (inferred to int
), but -(1)
as the negation of a positive integer literal (inferred to number
).
8.1.4. Array Literal
Syntax
ArrayLiteral <Yield> returns ArrayLiteral:
{ArrayLiteral} '['
elements+=ArrayPadding* (
elements+=ArrayElement<Yield>
(',' elements+=ArrayPadding* elements+=ArrayElement<Yield>)*
(trailingComma?=',' elements+=ArrayPadding*)?
)?
']'
;
/**
* This array element is used to pad the remaining elements, e.g. to get the
* length and index right
*/
ArrayPadding returns ArrayElement: {ArrayPadding} ',';
ArrayElement <Yield> returns ArrayElement: {ArrayElement} spread?='...'? expression=AssignmentExpression<In=true,Yield>;
Type Inference
In general, an array literal is inferred as Array<T>
(similar to the type of new Array()
).
The interesting question is the binding of the type variable .
The type of an array padding p is inferred as follows:
The element type of an array literal is simply inferred as the (simplified) union of the type elements of the array. Thus, the type of an array literal is inferred as follows:
In other languages not supporting union types, the element type is often inferred as the join (LCST) of the element types. Using a union type here preserves more information (as the actual types are still known). For many use cases the behavior is similar though, as the members of a union type are the members of the join of the elements of the union.
Note that typeof [1,2,3]
does not return Array<number>
(as ECMAScript is not aware of the generic array type), but Object
.
The type for all variables declared in this example is inferred to Array<string>
:
var names1 = ["Walter", "Werner"];
var names2 = new Array("Wim", "Wendelin");
var names3 = new Array<string>(3); // length is 3
var names4: Array<string>;
Empty array literals are inferred to any
, by default.
We are not using Array<?>
here because then a typical JavaScript pattern would no longer be supported:
var a = [];
a.push('hello'); (1)
1 | This would fail if a and thus [] were inferred to Array<?> |
An important exception; if a type expectation exists for the empty array literal and the expected type is Array<T> , this will be used as the type of the array literal.
|
Req. IDE-95: Empty array literal (ver. 1)
An empty array literal will be inferred as follows:
-
If there is a type expectation for the empty array literal and the expected type is
Array<T>
, for any typeT
, then the type of the empty array literal will be inferred toArray<T>
. -
Otherwise, the type of the empty array literal will be inferred to
Array<any>
.
8.1.5. Object Literal
In addition to ordinary Javascript object literals, N4JS supports the spread operator within object literals as introduced in [ECMA18a].
Syntax
Cf. [ECMA11a(p.S11.1.5, p.p.65ff)] The syntax of an object literal is given by:
ObjectLiteral <Yield>: {ObjectLiteral}
'{'
( propertyAssignments+=PropertyAssignment<Yield>
(',' propertyAssignments+=PropertyAssignment<Yield>)* ','?
)?
'}'
;
PropertyAssignment <Yield>:
PropertyNameValuePair<Yield>
| PropertyGetterDeclaration<Yield>
| PropertySetterDeclaration<Yield>
| PropertyMethodDeclaration<Yield>
| PropertyNameValuePairSingleName<Yield>
| PropertySpread<Yield>
;
PropertyMethodDeclaration <Yield>:
=> ({PropertyMethodDeclaration}
annotations+=Annotation*
TypeVariables? returnTypeRef=TypeRef?
(
generator?='*' LiteralOrComputedPropertyName<Yield> ->MethodParamsAndBody<Generator=true>
| LiteralOrComputedPropertyName<Yield> ->MethodParamsAndBody <Generator=false>
)
)
';'?
;
PropertyNameValuePair <Yield>:
=> (
{PropertyNameValuePair}
annotations+=Annotation*
declaredTypeRef=TypeRef? LiteralOrComputedPropertyName<Yield> ':'
)
expression=AssignmentExpression<In=true,Yield>
;
/*
* Support for single name syntax in ObjectLiteral (but disallowed in actual object literals by ASTStructureValidator
* except in assignment destructuring patterns)
*/
PropertyNameValuePairSingleName <Yield>:
declaredTypeRef=TypeRef?
identifierRef=IdentifierRef<Yield>
('=' expression=AssignmentExpression<In=true,Yield>)?
;
PropertyGetterDeclaration <Yield>:
=>(
{PropertyGetterDeclaration}
annotations+=Annotation*
GetterHeader<Yield>
)
body=Block<Yield=false>
;
PropertySetterDeclaration <Yield>:
=>(
{PropertySetterDeclaration}
annotations+=Annotation*
'set'
->LiteralOrComputedPropertyName <Yield>
)
'(' fpar=FormalParameter<Yield> ')' body=Block<Yield=false>
;
PropertySpread <Yield>:
'...' expression=AssignmentExpression<In=true,Yield>
;
import Address from "my/Address";
var simple = {name: "Walter", age: 72, address: new Address()};
8.1.5.1. Properties
PropertyAssignments have common properties of PropertyNameValuePair, PropertyGetterDeclaration, and PropertySetterDeclaration:
annotations
-
The annotations of the property assignment.
name
-
The name of the property. This may be an identifier, a string or a numeric literal. When comparing names, we implicitly assume the name to be converted to an identifier, even if this identifier is not a valid ECMAScript identifier.
declaredType
-
The declared type of the property which may be null. This property is a pseudo property for PropertySetterDeclaration, in this case it is derived from the declared type of the setter’s formal parameter.
Additionally, we introduce the following pseudo properties to simplify constraints:
isAccessor
-
The read-only boolean property. This is true if the property assignment is a setter or getter declaration. This is comparable to ECMAScript’s spec function
IsAccessoprDescriptor
. For a given property assignment p this is semantically equivalent to . isData
-
The read-only boolean property. This is true if the property assignment is a name value pair. For a given property assignment p this is semantically equivalent to . It is comparable to ECMAScript’s spec function
isDataDescriptor
. The equation is always true.
Semantics
Req. IDE-96: Object literal (ver. 1)
For a given object literal the following constraints must hold (cf. [ECMA11a(p.p.66)]:
-
the name of each property is given as an identifier, a string literal, a numeric literal, or as a computed property name with a compile-time expression (see Compile-Time Expressions). In particular, string literals, e.g.
['myProp']
, built-in symbols, e.g.[Symbol.iterator]
, and literals of@StringBased
enums are all valid computed property names. -
Object literal may not have two PropertyNameValuePairs with the same name in strict mode (cf. 4.a [ECMA11a(p.p.66)]):
-
Object literal may not have PropertyNameValuePair and
PropertyGetterDeclaration
/PropertySetterDeclaration
with the same name (cf. 4.b/c [ECMA11a(p.p.66)]):
-
Object literal may not have multiple
PropertyGetterDeclaration
orPropertySetterDeclaration
with the same name (cf. 4.d [ECMA11a(p.p.66)]):
It is a SyntaxError if the Identifiereval
or the Identifierarguments
occurs as the Identifier in aPropertySetParameterList
of aPropertyAssignment
that is contained in strict code or if itsFunctionBody
is strict code. [ECMA11a(p.p.66)]
-
If two or more property assignments have the same name (and the previous conditions hold), then the types of these assignments must conform. That is to say that the inferred (but not declared) type of all assignments must be type of probably declared types and if the types are explicitly declared, they must be equal.
-
In N4JS mode, the name of a property must be a valid N4JSIdentifier:
Req. IDE-22501: Superfluous properties of an object literal (ver. 1)
Let be the expected type of an object literal as defined by the context in which is used.
If is not type Object
and not dynamic, then the compiler creates a warning contains properties not found in .
This is true in particular for object literals passed in as arguments of a spec-constructor.
8.1.5.2. Scoping and linking
var p = {
f: function() {
console.log("p´s f");
},
b: function() {
this.f();
},
o: {
nested: "Hello"
}
};
p.b();
p.o.nested;
-
Other properties within an object literal property can be accessed using this. In the expression of property name value pairs, however,
this
is not be bound to the containing object literal, but usually to undefined or global. -
The properties of an object literal are accessible from outside.
-
Nested properties of an object literal are also accessible from outside.
Type Inference
An object literal implicitly extends ~Object
, therefore, object literal types use structural typing.
For details see Structural Typing.
From a type systems point of view, the two variables ol
and st
below have the same type.
var ol = {
s: "hello",
n: 42
}
var st: ~Object with { s: string; n: number;};
8.1.6. Parenthesized Expression and Grouping Operator
The grouping operator is defined here as a parenthesized expression.
Syntax
ParenExpression <Yield>: '(' expression=Expression<In=true,Yield> ')';
Type Inference
The type of the grouping operator simply is the type of its nested expression. The type if a parenthesized expression is inferred as follows:
In the following listing, the type of the plain expressions is equivalent to the parenthesized versions:
class A{} class B extends A{}
var f: boolean; var a: A a; var b: B;
/* simple <-> parenthesized */
10; (10);
"hello"; ("hello");
true; (true);
a; (a);
10-5; (10-5);
f?a:b (f?a:b);
8.1.7. Property Accessors
Syntax
Property accessors in N4JS are based on [ECMA11a(p.S11.2.1, p.p.67ff)]. They cannot only be used for accessing properties of an object, but also for accessing members of a class instance. In order to support parameterized calls, the syntax is extended to optionally allow type arguments.
ParameterizedPropertyAccessExpression:
target=PrimaryExpression<Yield> ParameterizedPropertyAccessExpressionTail<Yield>
;
IndexedAccessExpression:
target=PrimaryExpression<Yield> IndexedAccessExpressionTail<Yield>
;
fragment IndexedAccessExpressionTail <Yield>*:
'[' index=Expression<In=true,Yield> ']'
;
fragment ParameterizedPropertyAccessExpressionTail <Yield>*:
'.' TypeArguments? property=[types::IdentifiableElement|IdentifierName]
;
Note that in [ECMA11a], the index access
is called bracket notation
.
Direct Property Access
We define a special case of property access as follows:
Definition: Direct Property Access
A property access expression is called direct, iff
-
its target is an identifier reference to a class, interface, enum, or the built-in object
Symbol
, and -
its property name denotes an owned member of the target classifier (not an inherited, consumed, or polyfilled member) or a literal if the target is an enum.
As a consequence, a direct property access can only refer to static members.
The first requirement of the above definition rules out property access expressions that do not directly point to their target classifier or enum, as shown in the following example:
class C {
const field = 'hello';
}
C.field; // direct property access to 'field'
let ctor = C;
ctor.field; // *not* a direct property access to 'field'
Direct property access is the only form of property access allowed in compile-time expressions, cf. Compile-Time Expressions.
8.1.7.1. Properties
We define the following properties:
target
-
The receiver of the property access.
index
-
The index expression in case of an IndexedAccessExpression (returns otherwise).
property
-
The name of the property in case of non-indexed-access expressions (returns otherwise, although the index may be interpreted as property name).
We define the following pseudo properties:
isDotAccess
-
Read-only boolean property, returns true for non-index access expression (similar to ).
isIndexAccess
-
Read-only boolean property, returns true for index access expression (similar to .
The equation is always true. name
-
Returns the name of the property. This is either the converted to a simple name or the index converted to a name (where possible) if it is an indexed-accessed expression.
Semantics
The parameterization is part of the property access in case of generic methods. For generic functions, a parameterized function call is introduced (cf. Function Calls). The constraints are basically similar.
Req. IDE-97: Property Access and Dot Notation (ver. 1)
-
If dot notation is used in N4JS mode, the referenced property must exist unless receiver is a dynamic type:
-
If dot notation is used and the referenced property exists, then the property must be accessible:
-
If dot notation is used and the referenced property exists and this property is a member with a declared
@This
type (only possible for methods or field accessors), then the receiver must be a subtype of the declared@This
type.
Req. IDE-98: Index Access (ver. 1)
An index access expression is valid iff one of the following cases applies:
-
the receiver is of a dynamic type. In this case, the index may be any expression (need not be a compile-time expression).
-
the receiver is an immediate instance of
Object
, i.e. it is a subtype ofObject
and its super types but not of any other type including~Object
and~~Object
. -
the receiver is of type Array, ArgumentType, string, or String (including their subtypes) and the index is an expression of type
number
. -
the index expression is a compile-time expression
-
and the receiver type defines a member with a name equal to the string representation of the index expression’s compile-time value
-
and the receiver is not an enum.
-
Although index access is very limited, it is still possible to use immediate instances of Object
in terms of a map (but this applies only to index access, not the dot notation):
var map: Object = new Object();
map["Kant"] = "Imperative";
map["Hegel"] = "Dialectic";
map.spinoza = "Am I?"; // error: Couldn't resolve reference to IdentifiableElement 'spinoza'.
Req. IDE-99: Parameterized Property Access (ver. 1)
For a parameterized property access expression , the following constraints must hold:
-
The receiver or target must be a function or method:
-
The number of type arguments must match the number of type parameters of the generic function or method:
-
The type arguments of a parameterized property access expression must be subtypes of the boundaries of the parameters of the called generic method.
Also see constraints on read ([Req-IDE-93]) and write ([Req-IDE-121]) access.
Type Inference
We define the following type inferencing rules for property accessors:
-
The type of an indexed-access expression p is inferred as follows:
-
The type of a property access expression is inferred as follows:
-
The type of a parameterized access expression p is inferred as follows:
8.1.8. New Expression
Syntax
NewExpression: 'new' callee=MemberExpression<Yield> (-> TypeArguments)?
(=> withArgs?='(' Arguments<Yield>? ')' )?
import Address from "my/Address";
var a = new Address();
// a.type := my/Address
class C<T> {
constructor(param: T) {}
}
var c = new C<string>("hello");
Semantics
Req. IDE-100: New expression (ver. 1)
Let be a new expression, with . The following constraints must hold:
-
The callee must be a constructor type: or a constructable type.
-
Let be the type argument of , that is . In that case,
-
must not be an interface or enum:
-
must not contain any wildcards.
-
must not be a type variable.
-
-
If is not a constructor type, it must be a constructable type, that is one of the following:
In particular, it must not refer to a primitive type or a defined functions (i.e., subtypes of
Function
) cannot be used in new-expressions in N4JS.
Remarks:
to 1 The type of an abstract class A
is .
Or in other words: Only instantiable classes have an inferred type of .
to 2 Even though it is possible to use the constructor type of an abstract class – concrete subclasses with override compatible constructor signature will be subclasses of this constructor.
to 3 It is not possible to refer to union or intersection at that location. So this is not explicitly denied here since it is not possible anyway.
The following examples demonstrates the usage of abstract classes and constructor types, to make the first two constraints more clearer:
/* XPECT_SETUP org.eclipse.n4js.spec.tests.N4JSSpecTest END_SETUP */
abstract class A {}
class B extends A {}
// XPECT errors --> "Cannot instantiate abstract class A." at "A"
var x = new A();
// XPECT noerrors -->
var y = new B();
function foo(ctor : constructor{A}) {
// XPECT noerrors -->
return new ctor();
}
// XPECT errors --> "type{A} is not a subtype of constructor{A}." at "A"
foo(A);
// XPECT noerrors -->
foo(B);
Type Inference
The type of a new expression is inferred as follows:
For classes, constructors are described in Constructor and Classifier Type.
In N4JS it is not allowed to call new on a plain function. For example:
function foo() {}
var x = new foo();
will issue an error.
8.1.9. Function Expression
See Functions for details.
8.1.10. Function Calls
In N4JS, a function call [ECMA11a(p.S11.2.3)] is similar to a method call. Additionally to the ECMAScript’s CallExpression, a ParameterizedCallExpression is introduced to allow type arguments passed to plain functions.
Syntax
[[function-calls-syntax]]
Similar to [ECMA11a(p.S11.2.3, p.p.68ff)], a function call is defined as follows:
CallExpression <Yield>:
target=IdentifierRef<Yield>
ArgumentsWithParentheses<Yield>
;
ParameterizedCallExpression <Yield>:
TypeArguments
target=IdentifierRef<Yield>
ArgumentsWithParentheses<Yield>
;
fragment ArgumentsWithParentheses <Yield>*:
'(' Arguments<Yield>? ')'
;
fragment Arguments <Yield>*:
arguments+=AssignmentExpression<In=true,Yield> (',' arguments+=AssignmentExpression<In=true,Yield>)* (',' spread?='...' arguments+=AssignmentExpression<In=true,Yield>)?
| spread?='...' arguments+=AssignmentExpression<In=true,Yield>
;
Semantics
Req. IDE-101: Function Call Constraints (ver. 1)
For a given call expression bound to a method or function declaration , the following constraints must hold:
-
If less arguments are provided than formal parameters were declared, the missing formal parameters must have been declared optional:
-
If more arguments are provided than formal parameters were declared, the last formal parameter must have been declared variadic:
-
Types of provided arguments must be subtypes of the formal parameter types:
-
If more arguments are provided than formal parameters were declared, the type of the exceeding arguments must be a subtype of the last (variadic) formal parameter type:
Req. IDE-102: Parameterized Function Call Constraints (ver. 1)
-
The number of type arguments in a parameterized call expression must be equal to the number of type parameters of the generic function / method and the type arguments must be subtypes of the corresponding declared upper boundaries of the type parameters of the called generic function.
Note that (for a limited time), constraints [Req-IDE-101] and [Req-IDE-102] are not applied if the the type of is Function
.
See Function-Object-Type.
Type Inference
A call expression is bound to a method (Methods) or function declaration (which may be part of a function definition
(Function Declaration or specified via a function type Function Type) (via evaluation of MemberExpression
.
The type of the call is inferred from the function declaration or type as follows:
A generic method invocation may be parameterized as well. This is rarely required as the function argument types are usually inferred from the given arguments. In some cases, for instance with pathSelectors, this is useful. In that case, the type variable defined in the generic method declaration is explicitly bound to types by using type arguments. See Property Accessors for semantics and type inference.
This examples demonstrate how to explicitly define the type argument in a method call in case it cannot be inferred automatically.
class C {
static <T> foo(p: pathSelector<T>): void {..}
};
C.<my.Address>foo("street.number");
Note that in many cases, the type inferencer should be able to infer the type automatically. For example, for a method
function <T> bar(c: T, p: pathSelector<T>): void {..};
and a function call
bar(context, "some.path.selector");
[source,n4js]
the type variable T
can be automatically bound to the type of variable context
.
8.1.11. Postfix Expression
Syntax
PostfixExpression returns Expression: LeftHandSideExpression
(=>({PostfixExpression.expression=current} /* no line terminator here */ op=PostfixOperator))?
;
enum PostfixOperator: inc='++' | dec='--';
Semantics and Type Inference
The type inference and constraints for postfix operators ++
and --
, cf. [ECMA11a(p.S11.3.1, p.p.70)], [ECMA11a(p.S11.3.1, p.p.70)],
are defined similarly to their prefix variants (unary expressions), see Unary Expression.
Req. IDE-103: Postfix Expression Constraints (ver. 1)
For a given postfix expression with and , the following constraints must hold:
-
In N4JS mode, the type of the expression must be a number.
-
If both p and p must be defined.
8.1.12. Unary Expression
Syntax
We define the following unary operators and expression, similar to [ECMA11a(p.p.70ff)]:
UnaryExpression returns Expression:
PostfixExpression
| ({UnaryExpression} op=UnaryOperator expression=UnaryExpression);
enum UnaryOperator: delete | void | typeof | inc='++' | dec='--' | pos='+' | neg='-' | inv='$\sim$' | not='!';
Semantics
For semantics of the delete operator, see also [MozillaJSRef]
Req. IDE-104: Delete Operator Constraints (ver. 1)
For a given unary expression with , the following constraints must hold:
-
In strict mode, must be a reference to a property of an object literal, a member of a class type, or to a property of the global type (i.e., the reference must be bound, and the bound target must not be a variable).
-
In N4JS mode, the referenced property or member must not be declared in the containing type and the containing type reference must be declared dynamic.
Req. IDE-105: Void Operator Constraints (ver. 1)
There are no specific constraints defined for with
Req. IDE-106: Typeof Operator Constraints (ver. 1)
There are no specific constraints defined for unary expression with .
Req. IDE-107: Increment/Decrement Constraints (ver. 1)
For a given unary expression with and , the following constraints must hold:
-
If mode is N4JS, the type of the expression must be a number
-
If both p and p must be defined.
Req. IDE-108: Unary Plus/Minus/Bitwise Not Operator Constraints (ver. 1)
For a given unary expression with and , the following constraints must hold:
-
In N4JS mode, the type T of the expression must be a number:
Req. IDE-109: Logical Not Operator Constraints (ver. 1)
There are no specific constraints defined for with .
Type Inference
The following operators have fixed types independent of their operand types:
8.1.13. Multiplicative Expression
Syntax
Cf. [ECMA11a(p.p.73ff)]
MultiplicativeExpression returns Expression: UnaryExpression
(=>({MultiplicativeExpression.lhs=current} op=MultiplicativeOperator) rhs=UnaryExpression)*;
enum MultiplicativeOperator: times='*' | div='/' | mod='%';
Semantics
Req. IDE-110: Multiplicative Expression Constraints (ver. 1)
For a given multiplicative expression the following constraints must hold in N4JS mode :
-
The types of the operands may be any type:
If a non-numeric operand is used, the result may be NaN
which actually is a number as well.
Type Inference
[[type-inference-9]]
The inferred type of a multiplicative expression always is number:
8.1.14. Additive Expression
Syntax
Cf. [ECMA11a(p.p.75ff)]
AdditiveExpression returns Expression: MultiplicativeExpression
(=>({AdditiveExpression.lhs=current} op=AdditiveOperator) rhs=MultiplicativeExpression)*;
enum AdditiveOperator: add='+' | sub='-';
Semantics
Req. IDE-111: Additive Expression Constraints (ver. 1)
For a given additive expression the following constraints must hold in N4JS mode:
-
The type of the operand can be any type:
In JavaScript it is possible to subtract two non-numerics, leading to NaN
. Also undefined
or null
may be used. The real difference is what type is to be returned (string or number, see below).
8.1.14.1. Type Inference
The type of an additive expression is usually inferred to number
, except for addition which may lead to string as well.
The result for the addition operator is only be a number if both operands are numbers, booleans, null, or undefined.
Using undefined
in an additive expression leads to NaN
which actually is a number from the type system’s point of view. Additional analysis may create errors in the latter case though.
We first define two helper rules to simplify the addition operator condition:
The type of an additive expression is inferred as follows:
That is, if both operands are number, int, boolean, null, or even undefined, then the 'plus' is interpreted as mathematical addition and the result is a number. In other cases the 'plus' is interpreted as string concatenation and the result is a string. In case of union types, the result may be a union of number and string.
Adding two integers (int) leads to a number, since the result may not be represented as an (JavaScript) int anymore.
1+2; // number 3
"1"+"2"; // string "12"
"1"+2; // string "12"
1+true; // number 2
false+1; // number 1
"1"+true; // string "1true"
"1"+null; // string "1null"
1+null; // number 1
1+undefined; // number NaN
"1"+undefined; // string "1undefined"
Support new Symbol.toPrimitive
.
8.1.15. Bitwise Shift Expression
Syntax
Cf. +[+<<ECMA11a,ECMA11a(p.p.76f)>>+]+
ShiftExpression returns Expression: AdditiveExpression
(=>({ShiftExpression.lhs=current} op=ShiftOperator rhs=AdditiveExpression))*
;
ShiftOperator returns ShiftOperator:
'>' '>' '>'? // SHR, USHR
| '<' '<' // SHL
;
Semantics
Req. IDE-112: Bitwise Shift Expression Constraints (ver. 1)
For a given bitwise shift expression the following constraints must hold in N4JS mode: * The types of the operands can be any.
Type Inference
The type returned by a bitwise shift expression is always number
:
A non-numeric operand is interpreted as 0, except for true
which is interpreted as 1
; or objects implementing the symbol toPrimitive
.
8.1.16. Relational Expression
Syntax
Cf. [ECMA11a(p.p.77ff)]
RelationalExpression returns Expression: ShiftExpression
(=>({RelationalExpression.lhs=current} op=RelationalOperator) rhs=ShiftExpression)*;
RelationalExpressionNoIn returns Expression: ShiftExpression
(=>({RelationalExpression.lhs=current} op=RelationalOperatorNoIn) rhs=ShiftExpression)*;
enum RelationalOperator:
lt='<' | gt='>' | lte='<=' | gte='>=' | instanceof | in;
RelationalOperatorNoIn returns RelationalOperator:
'<' | '>' | '<=' | '>=' | 'instanceof';
Semantics
Req. IDE-113: Greater/Less (Equals) Operator Constraints (ver. 1)
For a given relational expression with in N4JS mode, the following constraints must hold:
-
The operands must have the same type and the type must be either a number, string, or boolean:
Req. IDE-114: Instanceof Operator Constraints (ver. 1)
For a given relational expression with , the following constraints must hold:
-
The right operand of the instanceof operator must be a
Function
[49]
In other words,
is contained in the the first type rule, an object type reference [50] or an enum type reference.
The type of a definition site structural classifier is not of type C
.
Thus, the instanceof
operator cannot be used for structural types.
Use-site structural typing is also not possible since ~
would be interpreted (by the parser) as a binary operator.
Req. IDE-115: Operator Constraints (ver. 1)
For a given relational expression with , the following constraints must hold:
-
The right operand of the in operator must be an
Object
: -
In N4JS mode, the left operand is restricted to be of type
string
ornumber
:
A special feature of N4JS is support for interface type references in combination with the instance of
operator.
The compiler rewrites the code to make this work.
instanceof
with InterfaceThe following example demonstrates the use of the operator with an interface. This is, of course, not working in pure ECMAScript.
interface I {}
class A implements I {}
class B extends A {}
class C {}
function f(name: string, p: any) {
if (p instanceof I) {
console.log(name + " is instance of I");
}
}
f("A", new A())
f("B", new B())
f("C", new C())
This will print out
A is instance of I
B is instance of I
Type Inference
The type of a relational expression always is boolean
;
8.1.17. Equality Expression
Syntax
Cf. [ECMA11a(p.p.80ff)]
EqualityExpression returns Expression: RelationalExpression
(=>({EqualityExpression.lhs=current} op=EqualityOperator) rhs=RelationalExpression)*;
EqualityExpressionNoIn returns Expression: RelationalExpressionNoIn
(=>({EqualityExpression.lhs=current} op=EqualityOperator) rhs=RelationalExpressionNoIn)*;
enum EqualityOperator: same='===' | nsame='!==' | eq='==' | neq='!=';
Semantics
There are no hard constraints defined for equality expressions.
In N4JSmode, a warning is created if for a given expression , neither nor and no interface or composed type is involved as the result is constant in these cases.
Note that a warning is only created if the upper bounds do not match the described constraints. This is necessary for wildcards. For example in
// with
class A{} class B extends A{}
function isFirst(ar: Array<? extends A>, b: B): boolean {
return b === ar[0]
}
the type of array elements is ? extends A
.
Neither nor is true.
This is why the upper bounds are to be used.
Type Inference
The inferred type of an equality expression always is boolean
.
8.1.18. Binary Bitwise Expression
Syntax
Cf. [ECMA11a(p.p.82ff)]
BitwiseANDExpression returns Expression: EqualityExpression
(=> ({BitwiseANDExpression.lhs=current} '&') rhs=EqualityExpression)*;
BitwiseANDExpressionNoIn returns Expression: EqualityExpressionNoIn
(=> ({BitwiseANDExpression.lhs=current} '&') rhs=EqualityExpressionNoIn)*;
BitwiseXORExpression returns Expression: BitwiseANDExpression
(=> ({BitwiseXORExpression.lhs=current} '^') rhs=BitwiseANDExpression)*;
BitwiseXORExpressionNoIn returns Expression: BitwiseANDExpressionNoIn
(=> ({BitwiseXORExpression.lhs=current} '^') rhs=BitwiseANDExpressionNoIn)*;
BitwiseORExpression returns Expression: BitwiseXORExpression
(=> ({BitwiseORExpression.lhs=current} '|') rhs=BitwiseXORExpression)*;
BitwiseORExpressionNoIn returns Expression: BitwiseXORExpressionNoIn
(=> ({BitwiseORExpression.lhs=current} '|') rhs=BitwiseXORExpressionNoIn)*;
Semantics
Req. IDE-116: Bitwise Bitwise Expression Constraints (ver. 1)
For a given bitwise bitwise expression the following constraints must hold in N4JS mode:
-
The types of the operands must be both number.
Type Inference
The type returned by a binary bitwise expression is always :
8.1.19. Binary Logical Expression
Syntax
LogicalANDExpression returns Expression: BitwiseORExpression
(=> ({LogicalANDExpression.lhs=current} '&&') rhs=BitwiseORExpression)*;
LogicalANDExpressionNoIn returns Expression: BitwiseORExpressionNoIn
(=> ({LogicalANDExpression.lhs=current} '&&') rhs=BitwiseORExpressionNoIn)*;
LogicalORExpression returns Expression: LogicalANDExpression
(=> ({LogicalORExpression.lhs=current} '||') rhs=LogicalANDExpression)*;
LogicalORExpressionNoIn returns Expression: LogicalANDExpressionNoIn
(=> ({LogicalORExpression.lhs=current} '||') rhs=LogicalANDExpressionNoIn)*;
Semantics
Req. IDE-117: Binary Logical Expression Constraints (ver. 1)
For a given binary logical expression with and the following constraints must hold:
-
In N4JS mode must not be
undefined
ornull
.
Type Inference
The evaluation relies on ECMAScript’s abstract operation ToBoolean
[ECMA11a(p.p.43)].
A short-circuit evaluation strategy is used so that depending on the types of the operands, different result types may be inferred.
In particular, the inferred type usually is not boolean
((cf. [ECMA11a(p.S11.11., p.p.83ff)] ).
The type inference does not take this short-circuit evaluation strategy into account, as it will affect the result in case one of the types is null
either or undefined
, which is not allowed in N4JS mode.
8.1.20. Conditional Expression
Syntax
ConditionalExpression returns Expression: LogicalORExpression
(=> ({ConditionalExpression.expression=current} '?') trueExpression=AssignmentExpression ':' falseExpression=AssignmentExpression)?;
ConditionalExpressionNoIn returns Expression: LogicalORExpressionNoIn
(=> ({ConditionalExpression.expression=current} '?') trueExpression=AssignmentExpression ':' falseExpression=AssignmentExpressionNoIn)?;
Semantics
Req. IDE-118: Conditional Expression Constraints (ver. 1)
For a given conditional expression with
the following constraints must hold:
-
A warning will be issued in N4JSmode if evaluates to a constant value. That is to say
or .
There are no specific constraints defined for the condition.
The ECMAScript operation ToBoolean
[ECMA11a(p.S9.2, p.p.43)] is used to convert any type to boolean.
Type Inference
The inferred type of a conditional expression is the union of the true and false expression (cf. [ECMA11a(p.S11.12, p.p.84)] ():
Given the following declarations:
class A{} class B extends A{}
class C{} class D extends A{}
class G<T> { field: T; }
var ga: G<A>, gb: G<B>;
a: A, b: B, c: C, d: D;
var boolean cond;
Then the type of the following conditional expression is inferred as noted in the comments:
cond ? a : a; // A
cond ? a : b; // union{A,B}
cond ? a : c; // union{A,C}
cond ? b : d; // union{B,D}
cond ? (cond ? a : b) : (cond ? c : d); // union{A,B,C,D}
cond ? (cond ? a : b) : (cond ? b : d); // union{A,B,D}
cond ? ga : gb; // union{G<A>,G<B>}
8.1.21. Assignment Expression
Syntax
AssignmentExpression <In, Yield>:
lhs=Expression op=AssignmentOperator rhs=AssignmentExpression<In,Yield>
;
AssignmentOperator:
'='
| '*=' | '/=' | '%=' | '+=' | '-='
| '<<=' | '>>=' | '>>>='
| '&=' | '^=' | '|='
;
Semantics
Req. IDE-119: Simple Assignment (ver. 1)
For a given assignment with
the following constraints must hold:
-
In the following inference rule and the constraint, ’@’ is to be replaced with the right part of one of the assignment operators listed above, that is,
Req. IDE-120: Compound Assignment (ver. 1)
For a given assignment , with but not +=
, both, left and right must be subtypes of number
.
For operator +=
,
-
if the left-hand side is a
number
, then must return a number as well. The right-hand side must, in fact, be anumber
(and not aboolean
) here in order to avoid unexpected results. -
if the left-hand side is a
string
, then must return a string as well. That means that the right-hand side can be ofany
type.
The expected type for the left-hand side is union{number,string}
.
The basic idea behind these constraints is that the type of the left-hand side is not to be changed by the compound assignment.
Req. IDE-121: Write Acccess (ver. 1)
For a given assignment expression , the left-hand side must be writeable or a final data field and the assignment must be in the constructor. Let be the bound variable (or field) with
The value of writeable is true for setters and usually for variables and data fields.
Assignability of variables and data fields can be restricted via const
or the @Final
annotation.
See Assignment Modifiers(data fields) and Const (const variables) for details.
Also see [Req-IDE-93] for read access constraint.
The left-hand side of an assignment expression may be an array or object literal and the assignment expression is then treated as a destructuring assignment. See Array and Object Destructuring for details.
Type Inference
Similarly to [ECMA11a(p.S11.1, p.p.84ff)], we define type inference for simple assignment (=
) and compound assignment (op=
) individually.
The type of the assignment is simply the type of the right-hand side:
Compound assignments are reduced to the former by splitting an operator @=
, in which @
is a simple operator,
into a simple operator expression with operator @
and a simple assignment =
.
Since the type of the latter is the right-hand side, we can define:
8.1.22. Comma Expression
Syntax
CommaExpression <In, Yield>:
exprs+=AssignmentExpression<In,Yield> ',' exprs+=AssignmentExpression<In,Yield>
(',' exprs+=AssignmentExpression<In,Yield>)*
;
Semantics
All expressions will be evaluated even though only the value of the last expression will be the result.
Assignment expressions preceed comma expressions:
var b: boolean;
b = (12, 34, true); // ok, b=true
b = 12, 34, true ; // error, b=12 is invalid
Type Inference
The type of a comma expression is inferred to the last expression:
8.2. ECMAScript 6 Expressions
8.2.1. The super Keyword
SuperLiteral: {SuperLiteral} 'super';
Apart from the use of keyword super
in wildcards of type expressions (cf. Type Expressions),
there are two use cases for keyword super
: super member access and super constructor calls.
Two use cases for keyword super:
class B extends A {
constructor() {
// super call
super();
}
@Override
m();: void {
// super member access
super.m();
}
}
Semantics
super
can be used to access the supertype’s constructor, methods, getters and setters.
The supertype is defined lexically, which is different from how this
works.[51]
Note that in [ECMA15a] Chapter 12.3.5 The Super Keyword
, super
is defined as a keyword but the syntax and semantics are defined in conjunction of member access.
Req. IDE-122: Type of Super is Always Nominal (ver. 1)
The type referenced with the super literal is always nominal. This is a consequence of references to types in extend clauses to be nominal.
Req. IDE-123: Access Super Constructor with Super Literal (ver. 1)
If the super literal is used to access the super constructor of a class, all of the following constraints must hold:
-
The super constructor access must be a call expression:
-
The super constructor call must be the expression of an expression statement :
-
The containing statement must be directly contained in a constructor body:
-
There must be no access to and not return statement before the containing statement .
Let be the index of in the constructor body:
Then, the following constraint must hold: [52]
Further constraints with regard to super constructor calls are described in Constructor and Classifier Type.
Req. IDE-124: Access Super Member with Super Literal (ver. 1)
If the super literal is used to access a member of the super class, all of the following constraints must hold, with
-
The super literal must be the receiver of a method call (cf. remarks below):
-
The super literal is used in a method or field accessor of a class:
3. The super literal must not be used in a nested function expression:
+ 4. If the return type of the method access via super is this, the actually bound this type will be the type of the calling class (and not of the class defining the method).
+
Req. IDE-125: Super Literal Usage (ver. 1)
For super literals, either [Req-IDE-123] or [Req-IDE-124] must hold, no other usage is allowed.
Consequences:
-
Since fields cannot be overridden (except for changing the access modifier), it is not possible nor allowed to access a field via
super
. -
Super literals must not be used with index access (e.g.,
super["foo"]
) -
It is not possible to chain super keywords. That is, it is not possible to call
super.super.m()
. -
It is not allowed to use the super literal in interfaces or non-methods/accessors.
-
Super cannot be used to call an overridden method of an implemented method from the overriding method in the implementing class.
-
In order to be able to access a super method of a method of a class , exactly one non-abstract super method in a super class of must exist. This is assured by the standard rules for binding identifiers.
If super is used to access a super member, the receiver type is not changed. This is important in particular for static methods as demonstrated in the following example:
class A {
static foo(): void { console.log("A") }
static bar(): void {
this.foo();
}
}
class B extends A {
@Override
static foo(): void { console.log("B") }
@Override
static bar(): void {
A.bar(); (1)
super.bar(); (2)
}
}
B.bar();
1 | The receiver (which is similar to the this-binding in ECMAScript) is changed to A . |
2 | Using super, the receiver is preserved, i.e. B . |
8.3. ECMAScript 7 Expressions
8.3.1. Await Expression
In N4JS, await
is implemented as a unary operator with the same precedence as yield
in ECMAScript 6.
Constraints governing the use of await
are given together with those for async
in Asynchronous Functions.
8.4. N4JS Specific Expressions
8.4.1. Class Expression
A class expression in N4JS is similar to a class expression in ECMAScript 6 [ECMA15a(p.14.5)].
Class expressions are not part of version 0.3 |
Syntax
See Classes.
Semantics and Type Inference
The inferred type of a class expression simply is the class type as described in Constructor and Classifier Type.
8.4.2. Cast (As) Expression
Syntax
CastExpression <Yield> returns Expression: expression=Expression 'as' targetTypeRef=TypeRefForCast;
TypeRefForCast returns StaticBaseTypeRef:
ParameterizedTypeRef
| ThisTypeRef
| ConstructorTypeRef
| ClassifierTypeRef
| FunctionTypeExpression
| UnionTypeExpression
| IntersectionTypeExpression
8.4.2.1. Semantics and Type Inference
The inferred type of the type cast expression is the target type:
The type cast returns the expression without further modifications. Type casts are simply removed during compilation so there will be no exceptions thrown at the cast until later when accessing properties which may not be present in case of a failed cast.
An error is issued if the cast is either unnecessary or cannot succeed. See further details in Type Cast.
8.4.3. Import Calls
Import calls as specified by the corresponding ECMA TC39 proposal are available in N4JS. Such an import call has the form
import(moduleSpecifier)
and may appear in the source code wherever an expression may appear. It’s argument need not be a string literal, as is the case with module specifiers of ordinary imports; instead, any expression that evaluates to a string at runtime is accepted. Hence, it can be used to import from a target module that is not yet known at compile time.
A note on terminology: import calls covered in this section are sometimes referred to as "dynamic import". In N4JS
that term is already used for imports of the form import * as N+ from "…"
, i.e. compile-time imports that do not
require type information of the module imported from, see Dynamic Imports, and stems from the term "dynamic type"
(see Dynamic). To avoid confusion, we will usually avoid referring to import calls as a "dynamic
import".
8.5. Compile-Time Expressions
A compile-time expression is an expression that can be fully evaluated at compile time. Not all expressions introduced
in the previous sections qualify as compile-time expressions. Some forms of expressions always qualify (e.g. a string
literal is always a compile-time expression), some never (e.g. call expressions), and for some expressions the operands
must be of a certain value. The latter applies, for example, to divison: 5 / 0
is a valid ECMAScript expression (evaluating
to NaN
) but is not a compile-time expression. So it’s the actual compile-time value of the divisor that makes the difference,
here. In any case, if an expression has operands, it is a compile-time expression only if all operands are compile-time expressions.
The value a compile-time expression evaluates to at compile-time is called compile-time value. So, an expression has a compile-time value if and only if it is a compile-time expression.
Definition: Compile-Time Expression
The following expressions are called compile-time expressions:
-
undefined
(but notNaN
orInfinity
). -
the
null
literal. -
all boolean, numeric, and string literals.
-
template string literals, iff all embedded expressions are compile-time expressions.
-
a parenthesis expression, iff its nested expression is a compile-time expression.
-
unary expressions in case of the following operators:
-
!
iff the operand is a compile-time expression and evaluates to a boolean value. -
+
iff the operand is a compile-time expression and evaluates to a numeric value. -
-
iff the operand is a compile-time expression and evaluates to a numeric value. -
void
.
-
-
binary expressions in case of the following operators:
-
+
iff both operands are compile-time expressions and-
both evaluate to numeric values, or
-
at least one evaluates to a string value.
-
-
-
,*
iff both operands are compile-time expressions and evaluate to numeric values. -
/
,%
iff both operands are compile-time expressions and evaluate to numeric values and the right-hand operand is non-zero (i.e. division by zero is disallowed in compile-time expression, becauseNaN
is not a supported compile-time value). -
&&
,||
iff both operands are compile-time expressions and evaluate to boolean values.
-
-
a tertiary conditional expression, iff the first operand is a compile-time expression evaluating to a boolean value B and
-
in case B is true, the second operand is a compile-time expression.
-
in case B is false, the third operand is a compile-time expression.
-
-
an identifier reference to a const variable, iff its initializer expression is a compile-time expression.
-
a property access expression, iff it is direct (see Direct Property Access) and refers to
-
a built-in symbol, e.g.
Symbol.iterator
, -
a literal of a
@StringBased
enum, or -
a const field with a compile-time initializer expression.
-
In all other cases, the expression is not a compile-time expression.
Every expression in the code may be a compile-time expression, but in most places this has no particular effect and is simply ignored. They are of significance only in computed property names, in index access expressions, as initializers of const variables and fields (as stated above) and when nested as an operand inside an expression at these locations.