7. Expressions¶
This chapter describes the meanings of expressions and the rules for the evaluation of expressions, except for the expressions related to coroutines (see Create and Launch a Coroutine for launch expressions, and Awaiting a Coroutine for await expressions).
expression:
literal
| qualifiedName
| arrayLiteral
| objectLiteral
| spreadExpression
| parenthesizedExpression
| thisExpression
| methodCallExpression
| fieldAccessExpression
| indexingExpression
| functionCallExpression
| newExpression
| castExpression
| instanceOfExpression
| typeOfExpression
| ensureNotNullishExpression
| nullishCoalescingExpression
| unaryExpression
| multiplicativeExpression
| additiveExpression
| shiftExpression
| relationalExpression
| equalityExpression
| bitwiseAndLogicalExpression
| conditionalAndExpression
| conditionalOrExpression
| assignmentExpression
| conditionalExpression
| stringInterpolation
| lambdaExpression
| dynamicImportExpression
| launchExpression
| awaitExpression
;
The grammar rules below introduce the productions to be used by other forms of expression productions:
objectReference:
typeReference
| super
| expression
;
arguments:
'(' argumentSequence? ')'
;
argumentSequence:
restArgument
| expression (',' expression)* (',' restArgument)? ','?
;
restArgument:
'...'? expression
;
The objectReference refers to class or interface in the first two cases, and thus allows handling static members. The last case refers to an instance variable of class or interface type unless the expression within potentiallyNullishExpression is evaluated to undefined.
The arguments refers to the list of arguments of a call. The last argument
can be prefixed by the spread operator ‘...
’.
7.1. Evaluation of Expressions¶
The result of a program expression evaluation denotes the following:
A variable (the term variable is used here in the general, non-terminological sense to denote a modifiable lvalue in the left-hand side of an assignment); or
A value (results found in all other places).
A variable or a value are equally considered the value of the expression if such a value is required for further evaluation.
The type of an expression is inferred at compile time (see Contexts and Conversions).
Expressions can contain assignments, increment operators, decrement operators, method calls, and function calls. The evaluation of an expression can produce side effects as a result.
Constant expressions (see Constant Expressions) are the expressions with values that can be determined at compile time.
7.1.1. Normal and Abrupt Completion of Expression Evaluation¶
Every expression in a normal mode of evaluation requires certain computational steps. The normal modes of evaluation for each kind of expression are described in the following sections.
An expression evaluation completes normally if all computational steps are performed without throwing an exception or error.
On the contrary, an expression completes abruptly if the expression evaluation throws an exception or an error.
The information about the causes of an abrupt completion can be available in the value attached to the exception or error object.
The predefined operators throw runtime errors as follows:
If an array reference expression has the null value, then an array access expression throws NullPointerError.
If an array reference expression has the null value, then an indexing expression (see Indexing Expression) throws NullPointerError.
If an array index expression has a value that is negative, greater than, or equal to the length of the array, then an indexing expression (see Indexing Expression) throws ArrayIndexOutOfBoundsError.
If a cast cannot be performed at runtime, then cast expressions (see Cast Expressions) throw ClassCastError.
If the right-hand operand expression has the zero value, then integer division (see Division), or integer remainder (see Remainder) operators throw ArithmeticError.
If the boxing conversion (see Boxing Conversions) occurs, then an assignment to an array element of a reference type (see Array Literal), method call expression (see Method Call Expression), or prefix/postfix increment/decrement (see Unary Expressions) operators throw OutOfMemoryError.
If the type of an array element is incompatible with the value that is being assigned, then an assignment to an array element of a reference type (see Array Literal) throws ArrayStoreError.
Possible hard-to-predict and hard-to-handle linkage and virtual machine errors can cause errors in the course of an expression evaluation.
An abrupt completion of a subexpression evaluation results in the following:
Immediate abrupt completion of the expression that contains such a subexpression (if the evaluation of the entire expression requires the evaluation of the contained subexpression); and
Cancellation of all subsequent steps of the normal mode of evaluation.
The terms ‘complete normally’ and ‘complete abruptly’ can also denote normal and abrupt completion of the execution of statements (see Normal and Abrupt Statement Execution). A statement can complete abruptly for a variety of reasons in addition to an exception or an error being thrown.
7.1.2. Order of Expression Evaluation¶
The operands of an operator are evaluated from left to right in accordance with the following rules:
Any right-hand operand is evaluated only after the full evaluation of the left-hand operand of a binary operator.
If using a compound-assignment operator (see Simple Assignment Operator), the evaluation of the left-hand operand includes the following:
Remembering the variable denoted by the left-hand operand;
Fetching the value of that variable for the subsequent evaluation of the right-hand operand; and
Saving such value.
If the evaluation of the left-hand operand completes abruptly, then no part of the right-hand operand is evaluated.
Any part of the operation can be executed only after the full evaluation of every operand of an operator (except conditional operators ‘&&’, ‘||’, and ‘?:’).
The execution of a binary operator that is an integer division ‘/’ (see Division), or integer remainder ‘%’ (see Remainder) can throw ArithmeticError only after the evaluations of both operands complete normally.
The ArkTS programming language follows the order of evaluation as indicated explicitly by parentheses, and implicitly by the precedence of operators. This rule particularly applies for infinity and NaN values of floating-point calculations. ArkTS considers integer addition and multiplication as provably associative; however, floating-point calculations must not be naively reordered because they are unlikely to be computationally associative (even though they appear to be mathematically associative).
7.1.3. Operator Precedence¶
The table below summarizes all information on the precedence and associativity of operators. Each section on a particular operator also contains detailed information.
Operator |
Precedence |
Associativity |
---|---|---|
postfix increment and decrement |
\(++ --\) |
left to right |
prefix increment and decrement, unary, typeof |
\(++\) \(--\) \(+\) \(-\) ! ~ typeof |
right to left |
multiplicative |
* / % |
left to right |
additive |
\(+\) \(-\) |
left to right |
cast |
as |
left to right |
shift |
<< >> >>> |
left to right |
relational |
< > <= >= instanceof |
left to right |
equality |
== != |
left to right |
bitwise AND |
& |
left to right |
bitwise exclusive OR |
^ |
left to right |
bitwise inclusive OR |
| |
left to right |
logical AND |
&& |
left to right |
logical OR |
|| |
left to right |
null-coalescing |
?? |
left to right |
ternary |
?: |
right to left |
assignment |
= += \(-=\) %=
\(*=\) \(/=\)
|
right to left |
7.1.4. Evaluation of Arguments¶
An evaluation of arguments always progresses from left to right up to the first error, or through the end of the expression; i.e., any argument expression is evaluated after the evaluation of each argument expression to its left completes normally (including comma-separated argument expressions that appear within parentheses in method calls, constructor calls, class instance creation expressions, or function call expressions).
If the left-hand argument expression completes abruptly, then no part of the right-hand argument expression is evaluated.
7.1.5. Evaluation of Other Expressions¶
These general rules cannot cover the order of evaluation of certain expressions when they from time to time cause exceptional conditions. The order of evaluation of the following expressions requires specific explanation:
Class instance creation expressions (see New Expressions);
Array creation expressions (see Array Creation Expressions);
Indexing expressions (see Indexing Expression);
Method call expressions (see Method Call Expression);
Assignments involving indexing (see Assignment);
Lambda expressions (see Lambda Expressions).
7.2. Literal¶
Literals (see Literals) denote fixed and unchanging value. Types of literals are determined as follows:
Literal |
Type of Literal Expression |
---|---|
Integer |
int if the value can be represented by the 32-bit type, otherwise long |
Floating-point |
double, float |
Boolean (true, false) |
boolean |
Char |
char |
String |
string |
Null (null) |
null |
Undefined (undefined) |
undefined |
7.3. Qualified Name¶
A qualifiedName (see Names) is an expression that consists of dot-separated names. A qualifiedName that consists of a single identifier is called a simple name.
A simple name refers to the following:
Global entity of the current compilation unit;
Local variable; or
Parameter of the surrounding function or method.
A qualifiedName that is not a simple name refers to the following:
Entity imported from some compilation unit, or
Member of some class or interface.
1 import * as compilationUnitName from "someFile"
2
3 class Type {}
4
5 function foo (parameter: Type) {
6 let local: Type = parameter /* here 'parameter' is the
7 expression in the form of simple name */
8 local = new Type () /* here 'local' is the expression in the
9 form of simple name */
10 local = compilationUnitName.someGlobalVariable /* here qualifiedName
11 refers to a global variable imported from some compilation unit */
12 }
7.4. Array Literal¶
An array literal is an expression that can be used to create an array, and to provide some initial values:
arrayLiteral:
'[' expressionSequence? ']'
;
expressionSequence:
expression (',' expression)* ','?
;
An array literal is a comma-separated list of initializer expressions enclosed between ‘[’ and ‘]’. A trailing comma after the last expression in an array literal is ignored:
1 let x = [1, 2, 3] // ok
2 let y = [1, 2, 3,] // ok, trailing comma is ignored
The number of initializer expressions enclosed in braces of the array initializer determines the length of the array to be constructed.
If sufficient space is allocated for a new array, then a one-dimensional array of the specified length is created. All elements of the array are initialized to the values specified by initializer expressions.
On the contrary, the evaluation of the array initializer completes abruptly if:
The space allocated for the new array is insufficient, and OutOfMemoryError is thrown; or
Some initialization expression completes abruptly.
Initializer expressions are executed from left to right. The n’th expression specifies the value of the n-1’th element of the array.
Array literals can be nested (i.e., the initializer expression that specifies an array element can be an array literal if that element is of an array type).
The type of an array literal is inferred by the following rules:
If the type can be inferred from the context, then the type of an array literal is the inferred type T[].
Otherwise, the type is inferred from the types of its elements.
7.4.1. Array Type Inference from Context¶
The type of an array literal can be inferred from the context, including explicit type annotation of a variable declaration, left-hand part type of an assignment, call parameter type, or type of a cast expression:
1 let a: number[] = [1, 2, 3] // ok, variable type is used
2 a = [4, 5] // ok, variable type is used
3
4 function min(x: number[]): number {
5 let m = x[0]
6 for (let v of x)
7 if (v < m)
8 m = v
9 return m
10 }
11 min([1., 3.14, 0.99]); // ok, parameter type is used
12
13 // ...
14 type Matrix = number[][]
15 let m: Matrix = [[1, 2], [3, 4], [5, 6]]
All valid conversions are applied to the initializer expression, i.e., each initializer expression type must be compatible (see Type Compatibility) with the array element type. Otherwise, a compile-time error occurs.
1 let value: number = 2
2 let list: Object[] = [1, value, "hello", new Error()] // ok
In the example above, the first literal and ‘value’ are implicitly boxed to Number, and the types of a string literal and the instance of type Error are compatible (see Type Compatibility) with Object because the corresponding classes are inherited from Object.
If the type used in the context is a tuple type (see Tuple Types), and types of all literal expressions are compatible with tuple type elements at respective positions, then the type of the array literal is a tuple type.
1 let tuple: [number, string] = [1, "hello"] // ok
2
3 let incorrect: [number, string] = ["hello", 1] // compile-time error
7.4.2. Array Type Inference from Types of Elements¶
If the type of an array literal [
expr1, … , exprN ]
cannot be inferred from the context, then the following algorithm is to be
used to infer it from the initialization expressions:
If there is no expression (N == 0), then the type is Object[].
If the type of the expression cannot be determined, then the type of the array literal cannot be inferred, and a compile-time error occurs.
If each initialization expression is of some numeric type, then the type is number[].
If all initialization expressions are of the same type T, then the type is T[].
Otherwise, the type is constructed as the union type T1 | … | TN, where Ti is the type of expri. Union type normalization (see Union Types Normalization) is applied to this union type.
1 let a = [] // type is Object[]
2 let b = ["a"] // type is string[]
3 let c = [1, 2, 3] // type is number[]
4 let d = ["a", 1, 3.14] // type is (string | number)[]
5 let e = [(): void => {}, new A()] // type is (() => void | A)[]
7.5. Object Literal¶
An object literal is an expression that can be used to create a class instance, and to provide some initial values. In some cases it is more convenient to use an object literal in place of a class instance creation expression (see New Expressions):
objectLiteral:
'{' valueSequence? '}'
;
valueSequence:
nameValue (',' nameValue)* ','?
;
nameValue:
identifier ':' expression
;
An object literal is written as a comma-separated list of name-value pairs enclosed in curly braces ‘{’ and ‘}’. A trailing comma after the last pair is ignored. Each name-value pair consists of an identifier and an expression:
1 class Person {
2 name: string = ""
3 age: number = 0
4 }
5 let b : Person = {name: "Bob", age: 25}
6 let a : Person = {name: "Alice", age: 18, } //ok, trailing comma is ignored
The type of an object literal is always some class C that is inferred from the context. A type inferred from the context can be either a named class (see Object Literal of Class Type), or an anonymous class created for the inferred interface type (see Object Literal of Interface Type).
A compile-time error occurs if:
The type of an object literal cannot be inferred from the context; or
The inferred type is not a class or an interface type.
1 let p = {name: "Bob", age: 25} /* compile-time error, type is
2 not inferred */
7.5.1. Object Literal of Class Type¶
If the class type C is inferred from the context, then the type of object literal is C:
1 class Person {
2 name: string = ""
3 age: number = 0
4 }
5 function foo(p: Person) { /*some code*/ }
6 // ...
7 let p: Person = {name: "Bob", age: 25} /* ok, variable type is
8 used */
9 foo({name: "Alice", age: 18}) // ok, parameter type is used
An identifier in each name-value pair must name a field of the class C, or a field of any superclass of class C.
A compile-time error occurs if the identifier does not name an accessible member field (Scopes) in the type C:
1 class Friend {
2 name: string = ""
3 private nick: string = ""
4 age: number = 0
5 }
6 // compile-time error, nick is private:
7 let f: Friend = {name: "aa", age: 55, nick: "bb"}
A compile-time error occurs if the type of an expression in a name-value pair is not compatible (see Type Compatibility) with the field type:
1 let f: Friend = {name: 123 /* compile-time error - type of right hand-side
2 is not compatible to the type of the left hand-side */
If class C is to be used in an object literal, then it must have a parameterless constructor (explicit or default) that is accessible in the class composite context.
A compile-time error occurs if:
C does not contain a parameterless constructor; or
No constructor is accessible.
This is presented in the examples below:
1 class C {
2 constructor (x: number) {}
3 }
4 // ...
5 let c: C = {} /* compile-time error - no parameterless
6 constructor */
1 class C {
2 private constructor () {}
3 }
4 // ...
5 let c: C = {} /* compile-time error - constructor is not
6 accessible */
7.5.2. Object Literal of Interface Type¶
If the interface type I is inferred from the context, then the type of the object literal is an anonymous class implicitly created for interface I:
1 interface Person {
2 name: string
3 age: number
4 }
5 let b : Person = {name: "Bob", age: 25}
In this example, the type of b is an anonymous class that contains the same fields as the interface I.
The interface type I must contain fields only. A compile-time error occurs if the interface type I contains a method:
1 interface I {
2 name: string = ""
3 foo(): void
4 }
5 let i : I = {name: "Bob"} // compile-time error, interface has methods
7.5.3. Object Literal of Record Type¶
Generic type Record<Key, Value> (see Record Utility Type) is used to map the properties of a type (Key type) to another type (Value type). A special form of an object literal is used to initialize the value of such type:
recordLiteral:
'{' keyValueSequence? '}'
;
keyValueSequence:
keyValue (',' keyValue)* ','?
;
keyValue:
expression ':' expression
;
The first expression in keyValue denotes a key, and must be of type Key; the second expression denotes a value, and must be of type Value:
let map: Record<string, number> = {
"John": 25,
"Mary": 21,
}
console.log(map["John"]) // prints 25
interface PersonInfo {
age: number
salary: number
}
let map: Record<string, PersonInfo> = {
"John": { age: 25, salary: 10},
"Mary": { age: 21, salary: 20}
}
If a key is a union type consisting of literals, then all variants must be listed in the object literal. Otherwise, a compile-time error occurs:
let map: Record<"aa" | "bb", number> = {
"aa": 1,
} // compile-time error: "bb" key is missing
7.5.4. Object Literal Evaluation¶
The evaluation of an object literal of type C (where C is either a named class type or an anonymous class type created for the interface) is to be performed by the following steps:
A parameterless constructor is executed to produce an instance x of the class C. The execution of the object literal completes abruptly if so does the execution of the constructor.
Name-value pairs of the object literal are then executed from left to right in the textual order they occur in the source code. The execution of a name-value pair includes the following:
Evaluation of the expression; and
Assigning the value of the expression to the corresponding field of x.
The execution of the object literal completes abruptly if so does the execution of a name-value pair.
The object literal completes normally with the value of the newly initialized class instance if so do all name-value pairs.
7.6. Spread Expression¶
spreadExpression:
'...' expression
;
A spread expression can be used only within the array literal (see Array Literal) or argument passing. The expression must be of array type (see Array Types). Otherwise, a compile-time error occurs.
A spread expression for arrays can be evaluated as follows:
At compilation time by the compiler if expression is constant (see Constant Expressions);
During program execution otherwise.
An array referred by the expression is broken by the evaluation into a sequence of values. This sequence is used where a spread expression is used. It can be an assignment, a call of a function, or a method.
1 let array1 = [1, 2, 3]
2 let array2 = [4, 5]
3 let array3 = [...array1, ...array2] // spread array1 and array2 elements
4 // while building new array literal during compile-time
5 console.log(array3) // prints [1, 2, 3, 4, 5]
6
7 foo (...array2) // spread array2 elements into arguments of the foo() call
8 function foo (...array: number[]) {
9 console.log (array)
10 }
11
12 run_time_spread_application (array1, array2) // prints [1, 2, 3, 666, 4, 5]
13 function run_time_spread_application (a1: number[], a2: number[]) {
14 console.log ([...a1, 666, ...a2])
15 // array literal will be built at runtime
16 }
Note: If an array is spread while calling a function, an appropriate parameter must be of spread array kind. A compile-time error occurs if an array is spread into a sequence of ordinary parameters:
1 let an_array = [1, 2]
2 bar (...an_array) // compile-time error
3 function bar (n1: number, n2: number) { ... }
7.7. Parenthesized Expression¶
parenthesizedExpression:
'(' expression ')'
;
The type and the value of a parenthesized expression are the same as those of the contained expression.
7.8. this
Expression¶
thisExpression:
'this'
;
The keyword this
can be used as an expression only in the body of an
instance method of a class, enum, or interface.
It can be used in a lambda expression only if it is allowed in the context the lambda expression appears in.
The keyword this
in a direct call expression this(…) can only
be used in the explicit constructor call statement.
A compile-time error occurs if the keyword this
appears elsewhere.
The keyword this
used as a primary expression denotes a value that is a
reference to the following:
Object for which the instance method is called; or
Object being constructed.
The value denoted by this
in a lambda body and in the surrounding context
is the same.
The class of the actual object referred to at runtime can be T if T is a class type, or a class that is a subtype of T.
7.9. Field Access Expressions¶
A field access expression can access a field of an object that is referred to by the value of the object reference. The object reference value can have different forms described in detail in Accessing Current Object Fields and Accessing Superclass Fields.
fieldAccessExpression:
objectReference ('.' | '?.') identifier
;
This object reference cannot denote a package, class type, or interface type.
Otherwise, the meaning of that expression is determined by the same rules as the meanings of qualified names.
A field access that contains ‘?.’ (see Chaining Operator) is called safe field access because it handles nullish values safely.
If object reference evaluation completes abruptly, then so does the entire field access expression.
7.9.1. Accessing Current Object Fields¶
An object reference used for Field Access must be a non-nullish reference type T. Otherwise, a compile-time error occurs.
Field access expression is valid if the identifier refers to a single accessible member field in type T.
A compile-time error occurs if:
The identifier names several accessible member fields (see Scopes) in type T.
The identifier does not name an accessible member field in type T.
The result of the field access expression is computed at runtime as follows:
For a static field:
The result of an object reference expression evaluation is discarded.
The result of the field access expression is value or variable of the static field in the class or interface that is the type of the object reference expression:
If the field is not readonly, then the result is variable, and its value can be changed.
If the field is readonly, then the result is value (except where the field access occurs in a class initializer, see Class Initializer).
For a non-static field:
The object reference expression is evaluated.
The result of the field access expression is value or variable of the instance field in the class or interface that is the type of the object reference expression:
If the field is not readonly, then the result is variable, and its value can be changed.
If the field is readonly, then the result is value (except where the field access occurs in a constructor, see Constructor Declaration).
Only the object reference type (not the class type of an actual object referred at runtime) is used to determine the field to be accessed.
7.9.2. Accessing Superclass Fields¶
A field access expression cannot denote a package, class type, or interface type. Otherwise, the meaning of that expression is determined by the same rules as the meaning of a qualified name.
The form super.identifier refers to the field named identifier of the current object. That current object is viewed as an instance of the superclass of the current class.
The forms that use the keyword super
are valid only in:
Instance methods;
Instance initializers;
Constructors of a class; or
Initializers of an instance variable of a class.
A compile-time error occurs if forms with the keyword super
:
Occur elsewhere;
Occur in the declaration of class Object (since Object has no superclass).
The field access expression super.f is handled in the same way as the expression this.f in the body of class S. Assuming that super.f appears within class C, f is accessible in S from class C (see Scopes) while:
The direct superclass of C is class S;
The direct superclass of the class denoted by T is a class with S as its fully qualified name.
A compile-time error occurs otherwise (particularly if the current class is not T).
7.10. Method Call Expression¶
A method call expression calls a static or instance method of a class or an interface.
methodCallExpression:
objectReference ('.' | '?.) identifier typeArguments? arguments block?
;
The syntax form that has a block associated with the method call is a special form called ‘trailing lambda call’ (see Trailing Lambda for details.
A compile-time error occurs if typeArguments is present, and any of type arguments is a wildcard (see Type Arguments).
A method call with ‘?.’ (see Chaining Operator) is called a ‘safe method call’ because it handles nullish values safely.
Resolving a method at compile time is more complicated than resolving a field because method overloading (see Class Method Overloading) can occur.
There are several steps that determine and check the method to be called at compile time (see Step 1: Selection of Type to Use, Step 2: Selection of Method, and Step 3: Semantic Correctness Check).
7.10.1. Step 1: Selection of Type to Use¶
The object reference and the method identifier are used to determine the type in which to search the method. The following options must be considered:
Form of object reference |
Type to use |
---|---|
typeReference.identifier |
Type denoted by typeReference. |
expression.identifier, where expression is of type T |
T if T is a class or interface, T’s constraint (Type Parameter Constraint) if T is a type parameter. A compile-time error occurs otherwise. |
super.identifier |
The superclass of the class that contains the method call. |
7.10.2. Step 2: Selection of Method¶
After the type to use is known, the call method must be determined. The goal is to select one from all potentially applicable methods.
As there is more than one applicable method, the most specific method must be selected. The method selection process results with the set of applicable methods and is described in Function or method selection.
A compile-time error occurs if:
The set of applicable methods is empty; or
The set of applicable methods has more than one candidate.
7.10.3. Step 3: Semantic Correctness Check¶
At this step, the single method to call (the most specific method) is known, and the following set of semantic checks must be performed:
If the method call has the form typeReference.identifier, then the method must be declared
static
. Otherwise, a compile-time error occurs.If the method call has the form expression.identifier, then the method must not be declared
static
. Otherwise, a compile-time error occurs.If the method call has the form super.identifier, then the method must not be declared
abstract
. Otherwise, a compile-time error occurs.If the last argument of a method call has the spread operator ‘
...
’, then objectReference that follows that argument must refer to an array whose type is compatible (see Type Compatibility) with the type specified in the last parameter of the method declaration.
7.10.4. Function or method selection¶
The function selection is the process of choosing the functions or methods that are applicable for the given function or method call. The choosing algorithm is described below:
An empty list of applicable candidates is created.
2. The argument types are taken from the call and compose the list TA = ( ta1 , ta2 , … tan ) where tai is the type of the i’th argument, and n is the number of the function or method call arguments.
3. Suppose there is a set of M candidates (functions or methods with the same name) that are accessible at the point of call. The following actions are performed for every candidate:
3.1 If the number of parameters if the j’th candidate is not equal to n then the candidate is excluded from the M set.
3.2 For each candidate from the M set, the following check is performed. Each type tai from the list TA is compared with the type of the i’th candidate parameter. The comparison is performed using the rules of type compatibility (see Type Compatibility) but without consideration for possible boxing conversions (see Boxing Conversions) and unboxing conversions (see Unboxing Conversions). Also, no considerations to the rest parameter (see Rest Parameter) and optional parameters (see Optional Parameters) parameters are performed.
If the candidate satisfies the check, it is added to the list of applicable candidates.
3.3 After all candidates are considered, and if the list of applicable candidates is empty then the step 3.2 is performed again. On this step each type tai is compared with the type of the i’th candidate parameter, and type compatibility rules do consider possible boxing and unboxing conversion.
If the candidate satisfies the check, it is added to the list of applicable candidates.
If the candidate satisfies the check, it is added to the list of applicable candidates.
3.4 After all candidates are considered, and if the list of applicable candidates is empty then the step 3.2 is performed again. On this step each type tai is compared with the type of the i’th candidate parameter, and type compatibility rules do consider possible boxing and unboxing conversion as well as rest and optional parameters.
The list of applicable candidates is ready.
Examples:
1class Base {}
2class Derived extends Base {}
3
4foo (p: Base)
5foo (p: Derived)
6foo (new Derived) // two applicable candidates for this call
7
8foo (p: A | B)
9foo (p: A | C)
10foo(new A) // two applicable candidates for this call
11
12foo (p1: Base)
13foo (p2: Base|SomeOtherType)
14foo (...p3: Base[])
15foo (new Base) // three applicable candidates for this call
7.11. Function Call Expression¶
A function call expression is used to call a function (see Function Types) or a lambda expression (see Lambda Expressions):
functionCallExpression:
expression ('?.' | typeArguments)? arguments block?
;
A special syntactic form that contains a block associated with the function call is called ‘trailing lambda call’ (see Trailing Lambda for details).
A compile-time error occurs if:
The typeArguments clause is present, and any of the type arguments is a wildcard (see Type Arguments).
The expression type is different than the function type.
The expression type is nullish but no ‘?.’ (see Chaining Operator) is present.
If the operator ‘?.’ (see Chaining Operator) is present, and the expression evaluates to a nullish value, then:
The arguments are not evaluated;
The call is not performed; and
The result of the functionCallExpression is undefined.
The function call is safe because it handles nullish values properly.
Step 1: Selection of Function and Step 2: Semantic Correctness Check below specify the steps to follow to determine what function is being called.
7.11.1. Step 1: Selection of Function¶
One function must be selected from all potentially applicable functions as a function can be overloaded.
The most specific function must be selected where there are more than one applicable functions.
The function selection process results with the set of applicable functions and is described in Function or method selection.
A compile-time error occurs if:
The set of applicable function is empty; or
The set of applicable functions has more than one candidate.
7.11.2. Step 2: Semantic Correctness Check¶
The single function to call is known at this step. The following semantic check must be performed:
If the last argument of the function call has the spread operator ‘...
’,
then objectReference that follows the argument must refer to an array
of a type compatible with that specified in the last parameter of the
function declaration (see Type Compatibility).
7.12. Indexing Expression¶
An indexing expression is used to access elements of arrays (see Array Types) and Record instances (see Record Utility Type). It can also be applied to instances of indexable types (see Indexable Types):
indexingExpression:
expression ('?.')? '[' expression ']'
;
An indexing expression contains two subexpressions as follows:
Object reference expression before the left bracket; and
Index expression inside the brackets.
If ‘?.’ (see Chaining Operator) is present in an indexing expression, then:
The type of the object reference expression must be a nullish type based on an array type or on the Record type. Otherwise, a compile-time error occurs.
The object reference expression must be checked to evaluate to nullish value. If it does, then the entire indexingExpression equals undefined.
If no ‘?.’ is present in an indexing expression, then object reference expression must be an array type or the Record type. Otherwise, a compile-time error occurs.
7.12.1. Array Indexing Expression¶
For array indexing, the index expression must be of a numeric type.
If the type of index expression is number or other floating-point type, and its fractional part is different from 0, then errors occur as follows:
Runtime error, if the situation is identified during program execution; and
Compile-time error, if the situation is detected during compilation.
A numeric types conversion (see Primitive Types Conversions) is performed on index expression to ensure that the resultant type is int. Otherwise, a compile-time error occurs.
If the type of object reference expression after applying of the chaining operator ‘?.’ (see Chaining Operator) is an array type T[], then the type of the indexing expression is T.
The result of an indexing expression is a variable of type T (i.e., a variable within the array selected by the value of that index expression).
It is essential that, if type T is a reference type, then the fields of array elements can be modified by changing the resultant variable fields:
1 let names: string[] = ["Alice", "Bob", "Carol"]
2 console.log(name[1]) // prints Bob
3 string[1] = "Martin"
4 console.log(name[1]) // prints Martin
5
6 class RefType {
7 field: number = 666
8 }
9 const objects: RefType[] = [new RefType(), new RefType()]
10 const object = objects [1]
11 object.field = 777 // change the field in the array element
12 console.log(objects[0].filed) // prints 666
13 console.log(objects[1].filed) // prints 777
14
15 let an_array = [1, 2, 3]
16 let element = an_array [3.5] // Compile-time error
17 function foo (index: number) {
18 let element = an_array [index]
19 // Runtime-time error if index is not integer
20 }
An array indexing expression evaluated at runtime behaves as follows:
First, the object reference expression is evaluated.
If the evaluation completes abruptly, then so does the indexing expression, and the index expression is not evaluated.
If the evaluation completes normally, then the index expression is evaluated. The resultant value of the object reference expression refers to an array.
If the index expression value of an array is less than zero, greater than, or equal to the array’s length, then ArrayIndexOutOfBoundsError is thrown.
Otherwise, the result of the array access is a type T variable within the array selected by the value of the index expression.
1 function setElement(names: string[], i: number, name: string) {
2 names[i] = name // run-time error, if 'i' is out of bounds
3 }
7.12.2. Record Indexing Expression¶
For a Record<Key, Value> indexing (see Record Utility Type), the index expression must be of type Key.
The following two cases are to be considered separately:
Type Key is a union that contains literal types only;
Other cases.
Case 1. If type Key is a union that contains literal types only, then the index expression can only be one of the literals listed in the type. The result of an indexing expression is of type Value.
1 type Keys = 'key1' | 'key2' | 'key3'
2
3 let x: Record<Keys, number> = {
4 'key1': 1,
5 'key2': 2,
6 'key3': 4,
7 }
8 let y = x['key2'] // y value is 2
A compile-time error occurs if an index expression is not a valid literal:
1 console.log(x['key4']) // compile-time error
2 x['another key'] = 5 // compile-time error
For this type Key, the compiler guarantees that an object of Record<Key, Value> contains values for all Key keys.
Case 2. There is no restriction on an index expression. The result of an indexing expression is of type Value | undefined.
1 let x: Record<number, string> = {
2 1: "hello",
3 2: "buy",
4 }
5
6 function foo(n: number): string | undefined {
7 return x[n]
8 }
9
10 function bar(n: number): string {
11 let s = x[n]
12 if (s == undefined) { return "no" }
13 return s!
14 }
15
16 foo(3) // prints "undefined"
17 bar(3) // prints "no"
18
19 let y = x[3]
In the code above, the type of y is string | undefined, and the value of y is undefined.
An indexing expression evaluated at runtime behaves as follows:
First, the object reference expression is evaluated.
If the evaluation completes abruptly, then so does the indexing expression, and the index expression is not evaluated.
If the evaluation completes normally, then the index expression is evaluated. The resultant value of the object reference expression refers to a record instance.
If the record instance contains a key defined by the index expression, then the result is the value mapped to the key.
Otherwise, the result is the literal undefined.
7.13. Chaining Operator¶
The chaining operator ?.
is used to effectively access values of
nullish types. It can be used in the following contexts:
If the value of the expression to the left of ?.
is undefined or null,
then the evaluation of the entire surrounding expression is omitted. The
result of the entire expression is then undefined.
1 class Person {
2 name: string
3 spouse?: Person = undefined
4 constructor(name: string) {
5 this.name = name
6 }
7 }
8
9 let bob = new Person("Bob")
10 console.log(bob.spouse?.name) // prints "undefined"
11
12 bob.spouse = new Person("Alice")
13 console.log(bob.spouse?.name) // prints "Alice"
A compile-time error occurs if an expression is not of a nullish type.
A compile-time error occurs if a chaining operator is placed in the context where a variable is expected, e.g., in the left-hand-side expression of an assignment (see Assignment) or expression (see Postfix Increment, Postfix Decrement, Prefix Increment or Prefix Decrement).
7.14. New Expressions¶
The operation new instantiates an object of the class or array type.
newExpression:
newClassInstance
| newArrayInstance
;
A class instance creation expression creates new object that is an instance of the specified class described in full detail below.
The creation of array instances is an experimental feature discussed in Array Creation Expressions.
newClassInstance:
'new' typeArguments? typeReference arguments? classBody?
;
A class instance creation expression specifies a class to be instantiated. It optionally lists all actual arguments for the constructor.
A class instance creation expression can throw an error as specified in Error Handling.
A class instance creation expression is standalone if it has no assignment or call context (see Assignment-like Contexts).
The execution of a class instance creation expression is performed as follows:
A new instance of the class is created;
Initial values are given to all new instance fields with initializers, and then to new instance fields with default values;
The constructor of the class is called to fully initialize the created instance.
The validity of the constructor call is similar to the validity of the method call as discussed in Step 3: Semantic Correctness Check, except the cases discussed in the Constructor Body section.
A compile-time error occurs if typeReference is a type parameter.
7.15. Cast Expressions¶
Cast expressions apply cast operator ‘as
’ to some expression
by issuing a value of the specified type.
castExpression:
expression 'as' type
;
1 class X {}
2
3 let x1 : X = new X()
4 let ob : Object = x1 as Object
5 let x2 : X = ob as X
The cast operator converts the value V of one type (as denoted by the expression) at runtime to a value of another type.
The cast expression introduces the target type for the casting context (see Casting Contexts and Conversions). The target type can be either type or typeReference.
A cast expression type is always the target type.
The result of a cast expression is a value, not a variable (even if the operand expression is a variable).
The casting conversion (see Casting Contexts and Conversions) converts the operand value at runtime to the target type specified by the cast operator (if needed).
A compile-time error occurs if the casting conversion cannot convert the compile-time type of the operand to the target type specified by the cast operator.
If the as
cast cannot be performed during program execution, then
ClassCastError is thrown.
7.16. InstanceOf Expression¶
instanceOfExpression:
expression 'instanceof' type
;
Any instanceof expression is of type boolean.
The expression operand of the operator instanceof
must be of a
reference type. Otherwise, a compile-time error occurs.
A compile-time error occurs if type operand of the operator instanceof
is
one of the following:
Type parameter (see Generic Parameters),
Union type that contains type parameter after normalization (see Union Types Normalization),
Generic type (see Generics)—this temporary limitation is expected to be removed in the future.
If the type of expression at compile time is compatible with type (see
Type Compatibility), then the result of the instanceof expression
is true
.
Otherwise, an instanceof expression checks during program execution
whether the type of the value the expression successfully evaluates to is
compatible with type (see Type Compatibility).
If so, then the result of the instanceof expression is true
.
Otherwise, the result is false
.
If the expression evaluation causes exception or error, then execution
control is transferred to a proper catch
section or runtime system,
and the result of the instanceof expression cannot be determined.
7.17. TypeOf Expression¶
typeOfExpression:
'typeof' expression
;
Any typeof expression is of type string. The typeof expression values of the types below are predefined, and the expressions require no evaluation:
Type of Expression |
Resulting String |
Code Example |
---|---|---|
number/Number |
“number” |
let n: number
typeof n
let N: Number
typeof N
|
string/String |
“string” |
let s: string
typeof s
|
boolean/Boolean |
“boolean” |
let b: boolean
typeof b
let B: Boolean
typeof B
|
any class or interface |
“object” |
let a: Object[]
typeof a
|
any function type |
“function” |
let f: () => void =
() => {}
typeof f
|
undefined |
“undefined” |
typeof undefined
|
null |
“object” |
typeof null
|
T | null, when T is a class or interface or array |
“object” |
let x: Object |= null
typeof x
|
enum |
“number” |
enum C {R, G, B}
let c: C
typeof c
|
All high-performance numeric value types and their boxed versions: byte, short, int, long, float, double, Byte, Short, Int, long, Long, Float, and Double |
“byte”, “short”, “int”, “long”, “float”, and “double” |
let x: byte
typeof x
...
|
The typeof expression value of all other types is to be evaluated during program execution. The result of the evaluation is the typeof value.
Type of Expression |
Code Example |
---|---|
union type |
function f(p:A|B) {
typeof p
}
|
type parameter |
class A<T|null|undefined> {
f: T
m() {
typeof this.f
}
constructor(p:T) {
this.f = p
}
}
|
If the expression evaluation causes exception or error, then the control of
the execution is transferred to a proper catch
section of the runtime
system. The result of the typeof expression cannot be determined in that case.
7.18. Ensure-Not-Nullish Expression¶
ensureNotNullishExpression:
expression '!'
;
An ensure-not-nullish expression is a postfix expression with the operator
‘!
’. An ensure-not-nullish expression in the expression e! checks
whether e of nullish type (see Nullish Types) evaluates to the
nullish value.
If the result of the evaluation of e is not equal to null or undefined, then the result of e! is the outcome of the evaluation of e.
If the result of the evaluation of e is equal to null or undefined, then NullPointerError is thrown.
A compile-time error occurs if e is not a nullish type.
The type of ensure-not-nullish expression is the non-nullish variant of the type of e.
7.19. Nullish-Coalescing Expression¶
nullishCoalescingExpression:
expression '??' expression
;
A nullish-coalescing expression is a binary expression that uses the operator
‘??
’, and checks whether the evaluation of the left-hand-side expression
equals the nullish value:
If so, then the right-hand-side expression evaluation is the result of a nullish-coalescing expression.
If not so, then the left-hand-side expression evaluation result is the result of a nullish-coalescing expression, and the right-hand-side expression is not evaluated (the operator ‘
??
’ is thus lazy).
A compile-time error occurs if the left-hand-side expression is not a reference type.
The type of a nullish-coalescing expression is union type (see Union Types) of the non-nullish variant of the types used in the left-hand-side and right-hand-side expressions.
The semantics of a nullish-coalescing expression is represented in the following example:
1 let x = expression1 ?? expression2
2
3 let x = expression1
4 if (x == null) x = expression2
5
6 // Type of x is Type(expression1)|Type(expression2)
A compile-time error occurs if the nullish-coalescing operator is mixed with conditional-and or conditional-or operators without parentheses.
7.20. Unary Expressions¶
unaryExpression:
expression '++'
| expression '––'
| '++' expression
| '––' expression
| '+' expression
| '–' expression
| '~' expression
| '!' expression
;
All expressions with unary operators (except postfix increment and postfix decrement operators) group right-to-left for ‘~+x’ to have the same meaning as ‘~(+x)’.
7.20.1. Postfix Increment¶
A postfix increment expression is an expression followed by the increment operator ‘\(++\)’.
A compile-time error occurs if the type of the variable resultant from the expression is not convertible (see Implicit Conversions) to a numeric type.
The type of a postfix increment expression is the type of the variable. The result of a postfix increment expression is a value, not a variable.
If the evaluation of the operand expression completes normally at runtime, then:
The value 1 is added to the value of the variable by using necessary conversions (see Primitive Types Conversions); and
The sum is stored back into the variable.
Otherwise, the postfix increment expression completes abruptly, and no incrementation occurs.
The value of the postfix increment expression is the value of the variable before the new value is stored.
7.20.2. Postfix Decrement¶
A postfix decrement expression is an expression followed by the decrement operator ‘\(--\)‘.
A compile-time error occurs if the type of the variable resultant from the expression is not convertible (see Implicit Conversions) to a numeric type.
The type of a postfix decrement expression is the type of the variable. The result of a postfix decrement expression is a value, not a variable.
If evaluation of the operand expression completes at runtime, then:
The value 1 is subtracted from the value of the variable by using necessary conversions (see Primitive Types Conversions); and
The sum is stored back into the variable.
Otherwise, the postfix decrement expression completes abruptly, and no decrementation occurs.
The value of the postfix decrement expression is the value of the variable before the new value is stored.
7.20.3. Prefix Increment¶
A prefix increment expression is an expression preceded by the operator ‘\(++\)’.
A compile-time error occurs if the type of the variable resultant from the expression is not convertible (see Implicit Conversions) to a numeric type.
The type of a prefix increment expression is the type of the variable. The result of a prefix increment expression is a value, not a variable.
If evaluation of the operand expression completes normally at runtime, then:
The value 1 is added to the value of the variable by using necessary conversions (see Primitive Types Conversions); and
The sum is stored back into the variable.
Otherwise, the prefix increment expression completes abruptly, and no incrementation occurs.
The value of the prefix increment expression is the value of the variable before the new value is stored.
7.20.4. Prefix Decrement¶
A prefix decrement expression is an expression preceded by the operator ‘\(--\)‘.
A compile-time error occurs if the type of the variable resultant from the expression is not convertible (see Implicit Conversions) to a numeric type.
The type of a prefix decrement expression is the type of the variable. The result of a prefix decrement expression is a value, not a variable.
If evaluation of the operand expression completes normally at runtime, then:
The value 1 is subtracted from the value of the variable by using necessary conversions (see Primitive Types Conversions); and
The sum is stored back into the variable.
Otherwise, the prefix decrement expression completes abruptly, and no decrementation occurs.
The value of the prefix decrement expression is the value of the variable before the new value is stored.
7.20.5. Unary Plus¶
A unary plus expression is an expression preceded by the operator ‘\(+\)’.
The type of the operand expression with the unary operator ‘\(+\)’ must be convertible (see Implicit Conversions) to a numeric type. Otherwise, a compile-time error occurs.
The numeric types conversion (see Primitive Types Conversions) is performed on the operand to ensure that the resultant type is that of the unary plus expression. The result of a unary plus expression is always a value, not a variable (even if the result of the operand expression is a variable).
7.20.6. Unary Minus¶
A unary minus expression is an expression preceded by the operator ‘\(-\)‘.
The type of the operand expression with the unary operator ‘\(-\)’ must be convertible (see Implicit Conversions) to a numeric type. Otherwise, a compile-time error occurs.
The numeric types conversion (see Primitive Types Conversions) is performed on the operand to ensure that the resultant type is that of the unary minus expression. The result of a unary minus expression is a value, not a variable (even if the result of the operand expression is a variable).
A unary numeric promotion performs the value set conversion (see Implicit Conversions).
The unary negation operation is always performed on, and its result is drawn from the same value set as the promoted operand value.
Further value set conversions are then performed on that same result.
The value of a unary minus expression at runtime is the arithmetic negation of the promoted value of the operand.
The negation of integer values is the same as subtraction from zero. The ArkTS programming language uses two’s-complement representation for integers. The range of two’s-complement value is not symmetric. The same maximum negative number results from the negation of the maximum negative int or long. In that case, an overflow occurs but throws no exception or error. For any integer value x, -x is equal to (~x)+1.
The negation of floating-point values is not the same as subtraction from zero (if x is +0.0, then 0.0-x is +0.0, however -x is -0.0).
A unary minus merely inverts the sign of a floating-point number. Special cases to consider are as follows:
The operand NaN results in NaN (NaN has no sign).
The operand infinity results in the infinity of the opposite sign.
The operand zero results in zero of the opposite sign.
7.20.7. Bitwise Complement¶
A bitwise complement expression is an expression preceded by the operator ‘~
’.
The type of the operand expression with the unary operator ‘~’ must be convertible (see Implicit Conversions) to a primitive integer type. Otherwise, a compile-time error occurs.
The numeric types conversion (see Primitive Types Conversions) is performed on the operand to ensure that the resultant type is that of the unary bitwise complement expression.
The result of a unary bitwise complement expression is a value, not a variable (even if the result of the operand expression is a variable).
The value of a unary bitwise complement expression at runtime is the bitwise complement of the promoted value of the operand. In all cases, ~x equals (-x)-1.
7.20.8. Logical Complement¶
A logical complement expression is an expression preceded by the operator ‘\(!\)’.
The type of the operand expression with the unary ‘!
’ operator must be
boolean or Boolean. Otherwise, a compile-time error occurs.
The unary logical complement expression’s type is boolean.
The unboxing conversion (see Unboxing Conversions) is performed on the operand at runtime if needed.
The value of a unary logical complement expression is true if the
(possibly converted) operand value is false
, and false if the operand
value (possibly converted) is true
.
7.21. Multiplicative Expressions¶
Multiplicative expressions use multiplicative operators ‘*’, ‘/’, and ‘%’:
multiplicativeExpression:
expression '*' expression
| expression '/' expression
| expression '%' expression
;
The multiplicative operators group left-to-right.
The type of each operand in a multiplicative operator must be convertible (see Contexts and Conversions) to a numeric type. Otherwise, a compile-time error occurs.
The numeric types conversion (see Primitive Types Conversions) is performed on both operands to ensure that the resultant type is the type of the multiplicative expression.
The result of a unary bitwise complement expression is a value, not a variable (even if the operand expression is a variable).
7.21.1. Multiplication¶
The binary operator ‘*’ performs multiplication, and returns the product of its operands.
Multiplication is a commutative operation if operand expressions have no side effects.
Integer multiplication is associative when all operands are of the same type.
Floating-point multiplication is not associative.
If overflow occurs during integer multiplication, then:
The result is the low-order bits of the mathematical product as represented in some sufficiently large two’s-complement format.
The sign of the result can be other than the sign of the mathematical product of the two operand values.
A floating-point multiplication result is determined in compliance with the IEEE 754 arithmetic:
The result is NaN if:
Either operand is NaN;
Infinity is multiplied by zero.
If the result is not NaN, then the sign of the result is as follows:
Positive if both operands have the same sign; and
Negative if the operands have different signs.
If infinity is multiplied by a finite value, then the multiplication results in a signed infinity (the sign is determined by the rule above).
If neither NaN nor infinity is involved, then the exact mathematical product is computed.
The product is rounded to the nearest value in the chosen value set by using the IEEE 754 ‘round-to-nearest’ mode. The ArkTS programming language requires gradual underflow support as defined by IEEE 754 (see Floating-Point Types and Operations).
If the magnitude of the product is too large to represent, then the operation overflows, and the result is an appropriately signed infinity.
The evaluation of a multiplication operator ‘*’ never throws an error despite possible overflow, underflow, or loss of information.
7.21.2. Division¶
The binary operator ‘/’ performs division and returns the quotient of its left-hand and right-hand operands (dividend and divisor respectively).
Integer division rounds toward 0, i.e., the quotient of integer operands n and d, after a numeric types conversion on both (see Primitive Types Conversions for details), is an integer value q with the largest possible magnitude that satisfies \(|d\cdot{}q|\leq{}|n|\).
Note that q is:
Positive when |n| \(\geq{}\) |d|, while n and d have the same sign; but
Negative when |n| \(\geq{}\) |d|, while n and d have opposite signs.
Only a single special case does not comply with this rule: the integer overflow occurs, and the result equals the dividend if the dividend is a negative integer of the largest possible magnitude for its type, while the divisor is -1.
This case throws no exception or error despite the overflow. However, if in an integer division the divisor value is 0, then ArithmeticError is thrown.
A floating-point division result is determined in compliance with the IEEE 754 arithmetic:
The result is NaN if:
Either operand is NaN;
Both operands are infinity; or
Both operands are zero.
If the result is not NaN, then the sign of the result is:
Positive if both operands have the same sign; or
Negative if the operands have different signs.
The division results in a signed infinity (the sign is determined by the rule above) if:
Infinity is divided by a finite value; and
A nonzero finite value is divided by zero.
The division results in a signed zero (the sign is determined by the rule above) if:
A finite value is divided by infinity; and
Zero is divided by any other finite value.
If neither NaN nor infinity is involved, then the exact mathematical quotient is computed.
If the magnitude of the product is too large to represent, then the operation overflows, and the result is an appropriately signed infinity.
The quotient is rounded to the nearest value in the chosen value set by using the IEEE 754 ‘round-to-nearest’ mode. The ArkTS programming language requires gradual underflow support as defined by IEEE 754 (see Floating-Point Types and Operations).
The evaluation of a floating-point division operator ‘/’ never throws an error despite possible overflow, underflow, division by zero, or loss of information.
7.21.3. Remainder¶
The binary operator ‘%’ yields the remainder of its operands (dividend as left-hand, and divisor as the right-hand operand) from an implied division.
The remainder operator in ArkTS accepts floating-point operands (unlike in C and C++).
The remainder operation on integer operands (for the numeric type conversion on both see Primitive Types Conversions) produces a result value, i.e., \((a/b)*b+(a\%b)\) equals a.
This equality holds even in the special case where the dividend is a negative integer of the largest possible magnitude of its type, and the divisor is -1 (the remainder is then 0).
According to this rule, the result of the remainder operation can only be:
Negative if the dividend is negative; or
Positive if the dividend is positive.
The magnitude of the result is always less than that of the divisor.
If the value of the divisor for an integer remainder operator is 0, then ArithmeticError is thrown.
A floating-point remainder operation result as computed by the operator ‘%’ is different than that produced by the remainder operation defined by IEEE 754. The IEEE 754 remainder operation computes the remainder from a rounding division (not a truncating division), and its behavior is different from that of the usual integer remainder operator. Contrarily, ArkTS presumes that on floating-point operations the operator ‘%’ behaves in the same manner as the integer remainder operator (comparable to the C library function fmod). The standard library (see Standard Library) routine Math.IEEEremainder can compute the IEEE 754 remainder operation.
The result of a floating-point remainder operation is determined in compliance with the IEEE 754 arithmetic:
The result is NaN if:
Either operand is NaN;
The dividend is infinity;
The divisor is zero; or
The dividend is infinity, and the divisor is zero.
If the result is not NaN, then the sign of the result is the same as the sign of the dividend.
The result equals the dividend if:
The dividend is finite, and the divisor is infinity; or
If the dividend is zero, and the divisor is finite.
If infinity, zero, or NaN are not involved, then the floating-point remainder r from the division of the dividend n by the divisor d is determined by the mathematical relation \(r=n-(d\cdot{}q)\), in which q is an integer that is only:
Negative if \(n/d\) is negative, or
Positive if \(n/d\) is positive.
The magnitude of q is the largest possible without exceeding the magnitude of the true mathematical quotient of n and d.
The evaluation of the floating-point remainder operator ‘%’ never throws an error, even if the right-hand operand is zero. Overflow, underflow, or loss of precision cannot occur.
7.22. Additive Expressions¶
Additive expressions use additive operators ‘+’ and ‘-‘:
additiveExpression:
expression '+' expression
| expression '-' expression
;
The additive operators group left-to-right.
If either operand of the operator is ‘+’ of type string, then the operation is a string concatenation (see String Concatenation). In all other cases, the type of each operand of the operator ‘+’ must be convertible (see Implicit Conversions) to a numeric type. Otherwise, a compile-time error occurs.
The type of each operand of the binary operator ‘-’ in all cases must be convertible (see Implicit Conversions) to a numeric type. Otherwise, a compile-time error occurs.
7.22.1. String Concatenation¶
If one operand of an expression is of type string, then the string conversion (see String Operator Contexts) is performed on the other operand at runtime to produce a string.
String concatenation produces a reference to a string object that is a concatenation of two operand strings. The left-hand operand characters precede the right-hand operand characters in a newly created string.
If the expression is not a constant expression (see Constant Expressions), then a new string object is created (see New Expressions).
7.22.2. Additive Operators ‘+’ and ‘-’ for Numeric Types¶
The binary operator ‘+’ applied to two numeric type operands performs addition and produces the sum of such operands.
The binary operator ‘-’ performs subtraction and produces the difference of two numeric operands.
The numeric types conversion (see Primitive Types Conversions) is performed on the operands.
The type of an additive expression on numeric operands is the promoted type of that expression’s operands. If the promoted type is int or long, then integer arithmetic is performed. If the promoted type is float or double, then floating-point arithmetic is performed.
If operand expressions have no side effects, then addition is a commutative operation.
If all operands are of the same type, then integer addition is associative.
Floating-point addition is not associative.
If overflow occurs on an integer addition, then:
The result is the low-order bits of the mathematical sum as represented in a sufficiently large two’s-complement format.
The sign of the result is different than that of the mathematical sum of the operands’ values.
The result of a floating-point addition is determined in compliance with the IEEE 754 arithmetic:
The result is NaN if:
Either operand is NaN; or
The operands are two infinities of opposite sign.
The sum of two infinities of the same sign is the infinity of that sign.
The sum of infinity and a finite value equals the infinite operand.
The sum of two zeros of opposite sign is positive zero.
The sum of two zeros of the same sign is zero of that sign.
The sum of zero and a nonzero finite value is equal to the nonzero operand.
The sum of two nonzero finite values of the same magnitude and opposite sign is positive zero.
If infinity, zero, or NaN are not involved, and the operands have the same sign or different magnitudes, then the exact sum is computed mathematically.
If the magnitude of the sum is too large to represent, then the operation overflows, and the result is an appropriately signed infinity.
Otherwise, the sum is rounded to the nearest value within the chosen value set by using the IEEE 754 ‘round-to-nearest mode’. The ArkTS programming language requires gradual underflow support as defined by IEEE 754 (see Floating-Point Types and Operations).
When applied to two numeric type operands, the binary operator ‘-’ performs subtraction, and returns the difference of such operands (minuend as left-hand, and subtrahend as the right-hand operand).
The result of a-b is always the same as that of a+(-b) in both integer and floating-point subtraction.
The subtraction from zero for integer values is the same as negation. However, the subtraction from zero for floating-point operands and negation is not the same (if x is +0.0, then 0.0-x is +0.0; however -x is -0.0).
The evaluation of a numeric additive operator never throws an error despite possible overflow, underflow, or loss of information.
7.23. Shift Expressions¶
Shift expressions use shift operators ‘<<’ (left shift), ‘>>’ (signed right shift), and ‘>>>’ (unsigned right shift). The value to be shifted is the left-hand operand in a shift operator, and the right-hand operand specifies the shift distance:
shiftExpression:
expression '<<' expression
| expression '>>' expression
| expression '>>>' expression
;
The shift operators group left-to-right.
Numeric types conversion (see Primitive Types Conversions) is performed separately on each operand to ensure that both operands are of primitive integer type.
Note: If the initial type of one or both operands is double
or
float
, then such operand or operands are first truncated to appropriate
integer type. If both operands are of type bigint, then shift operator
is applied to bigint operands.
A compile-time error occurs if either operand in a shift operator (after unary numeric promotion) is not a primitive integer type or bigint.
The shift expression type is the promoted type of the left-hand operand.
If the left-hand operand is of the promoted type int, then only five lowest-order bits of the right-hand operand specify the shift distance (as if using a bitwise logical AND operator ‘&’ with the mask value 0x1f or 0b11111 on the right-hand operand), and thus it is always within the inclusive range of 0 through 31.
If the left-hand operand is of the promoted type long, then only six lowest-order bits of the right-hand operand specify the shift distance (as if using a bitwise logical AND operator ‘&’ with the mask value 0x3f or 0b111111 the right-hand operand). Thus, it is always within the inclusive range of 0 through 63.
Shift operations are performed on the two’s-complement integer representation of the value of the left-hand operand at runtime.
The value of n << s is n left-shifted by s bit positions. It is equivalent to multiplication by two to the power s even in case of an overflow.
The value of n >> s is n right-shifted by s bit positions with sign-extension. The resultant value is \(floor(n / 2s)\). If n is non-negative, then it is equivalent to truncating integer division (as computed by the integer division operator by 2 to the power s).
The value of n >>> s is n right-shifted by s bit positions with zero-extension, where:
If n is positive, then the result is the same as that of n >> s.
If n is negative, and the type of the left-hand operand is int, then the result is equal to that of the expression (n >> s) + (2 << s).
If n is negative, and the type of the left-hand operand is long, then the result is equal to that of the expression (n >> s) + (2L << s).
7.24. Relational Expressions¶
Relational expressions use relational operators ‘<’, ‘>’, ‘<=’, and ‘>=’.
relationalExpression:
expression '<' expression
| expression '>' expression
| expression '<=' expression
| expression '>=' expression
;
The relational operators group left-to-right.
A relational expression is always of type boolean.
Two kinds of relational expressions are described below. The kind of a relational expression depends on the types of operands. It is a compile time error if at leats one type of operands is different from types described below.
7.24.1. Numerical Comparison Operators <, <=, >, and >=¶
The type of each operand in a numerical comparison operator must be convertible (see Implicit Conversions) to a numeric type. Otherwise, a compile-time error occurs.
Numeric types conversions (see Primitive Types Conversions) are performed on each operand as follows:
Signed integer comparison if the converted type of the operand is int or long.
Floating-point comparison if the converted type of the operand is float or double.
The comparison of floating-point values drawn from any value set must be accurate.
A floating-point comparison must be performed in accordance with the IEEE 754 standard specification as follows:
The result of a floating-point comparison is false if either operand is NaN.
All values other than NaN must be ordered with the following:
Negative infinity less than all finite values; and
Positive infinity greater than all finite values.
Positive zero equals negative zero.
Based on the above presumption, the following rules apply to integer operands, or floating-point operands other than NaN:
The value produced by the operator ‘<’ is true if the value of the left-hand operand is less than that of the right-hand operand. Otherwise, the value is false.
The value produced by the operator ‘<=’ is true if the value of the left-hand operand is less than or equal to that of the right-hand operand. Otherwise, the value is false.
The value produced by the operator ‘>’ is true if the value of the left-hand operand is greater than that of the right-hand operand. Otherwise, the value is false.
The value produced by the operator ‘>=’ is true if the value of the left-hand operand is greater than or equal to that of the right-hand operand. Otherwise, the value is false.
7.24.2. String Comparison Operators <, <=, >, and >=¶
Results of all string comparisons are defined as follows:
The operator ‘<’ delivers true if the string value of the left-hand operand is lexicographically less than the string value of the right-hand operand, or false otherwise.
The operator ‘<=’ delivers true if the string value of the left-hand operand is lexicographically less or equal than the string value of the right-hand operand, or false otherwise.
The operator ‘>’ delivers true if the string value of the left-hand operand is lexicographically greater than the string value of the right-hand operand, or false otherwise.
The operator ‘>=’ delivers true if the string value of the left-hand operand is lexicographically greater or equal than the string value of the right-hand operand, or false otherwise.
7.24.3. Boolean Comparison Operators <, <=, >, and >=¶
Results of all boolean comparisons are defined as follows:
The operator ‘<’ delivers true if the left-hand operand is false and the right-hand operand is true, or false otherwise.
The operator ‘<=’ delivers true if the left-hand operand is false and the right-hand operand is true or false, or false otherwise.
The operator ‘>’ delivers true if the left-hand operand is true and the right-hand operand is false, or false otherwise.
The operator ‘>=’ delivers true if the left-hand operand is true and the right-hand operand is false or true, or false otherwise.
7.25. Equality Expressions¶
Equality expressions use equality operators ‘==’, ‘===’, ‘!=’, and ‘!==’:
equalityExpression:
expression ('==' | '===' | '!=' | '!==') expression
;
Any equality expression is of type boolean. The result of operators ==
and ===
is true if operands are equal (see below). Otherwise, the
result is false.
In all cases, a != b
produces the same result as !(a == b)
, and
a !== b
produces the same result as !(a === b)
.
Equality operators group left-to-right. Equality operators are commutative if operand expressions cause no side effects.
Equality operators are similar to relational operators, except for their lower precedence (\(a < b==c < d\) is true if both \(a < b\) and \(c < d\) have the same truth value).
The choice of value equality or reference equality depends on the equality operator and the types of operands used:
value equality is applied to entities of primitive types, their boxed versions, and strings.
reference equality is applied to entities of reference types.
For operators ===
and !==, Reference Equality is used if both
operands are of reference types. Otherwise, a compile-time error occurs.
For operators ==
and !=, the following is used:
Value Equality for Numeric Types if operands are of numeric types or boxed version of numeric types;
Value Equality for Booleans if both operands are of type boolean or Boolean;
Value Equality for Characters if both operands are of type char or Char;
Value Equality for Strings if both operands are of type string;
Equality with null or undefined if one operand is null or undefined;
Reference Equality if both operands are of compatible reference types;
Otherwise, a compile-time error occurs.
1// Entities of value types are not comparable between each other
25 == "5" // compile-time error
35 == true // compile-time error
4"5" == true // compile-time error
7.25.1. Value Equality for Numeric Types¶
The numeric types conversion (see Primitive Types Conversions) is performed on the operands of a value equality operator if each operand is of a numeric type or the boxed version of a numeric type.
If the converted type of the operands is int
or long
, then an
integer equality test is performed.
If the converted type is float or double, then a floating-point equality test is performed.
The floating-point equality test must be performed in accordance with the following IEEE 754 standard rules:
The result of ‘\(==\)’ is false but the result of ‘\(!=\)’ is true if either operand is NaN.
The test \(x != x\) is true only if x is NaN.
Positive zero equals negative zero.
The equality operators consider two distinct floating-point values unequal in any other situation.
For example, if one value represents positive infinity, and the other represents negative infinity, then each compares equal to itself and unequal to all other values.
Based on the above presumptions, the following rules apply to integer operands or floating-point operands other than NaN:
If the value of the left-hand operand is equal to that of the right-hand operand, then the operator ‘\(==\)’ produces the value true. Otherwise, the result is false.
If the value of the left-hand operand is not equal to that of the right-hand operand, then the operator ‘\(!=\)’ produces the value true. Otherwise, the result is false.
The following example illustrates value equality:
15 == 5 // true
25 != 5 // false
3
45 === 5 // compile-time error
5
65 == new Number(5) // true
75 === new Number(5) // compile-time error
8
9new Number(5) == new Number(5) // true
105 = 5.0 // true
7.25.2. Value Equality for Strings¶
Two strings are equal if they represent the same sequence of characters:
1"abc" == "abc" // true
2
3function foo(s: string) {
4 console.log(s == "hello")
5}
6foo("hello") // prints "true"
7.25.3. Value Equality for Booleans¶
The operation is a boolean equality if each operand is of type boolean or Boolean.
Boolean equality operators are associative.
If an operand is of type Boolean, then the unboxing conversion must be performed (see Primitive Types Conversions).
If both operands (after the unboxing conversion is performed if required)
are either true
or false
, then the result of ‘\(==\)’ is true.
Otherwise, the result is false.
If both operands are either true
or false
, then the result of
‘\(!=\)’ is false. Otherwise, the result is true.
7.25.4. Value Equality for Characters¶
The operation is a character equality if each operand is of type char or Char.
Character equality operators are associative.
If an operand is of type Char, then the unboxing conversion must be performed (see Primitive Types Conversions).
If both operands (after the unboxing conversion is performed if required) contain the same character code, then the result of ‘\(==\)’ is true. Otherwise, the result is false.
If both operands contain different character codes, then the result of ‘\(!=\)’ is false. Otherwise, the result is true.
7.25.5. Equality with null or undefined¶
Any entity can be compared to null by using the operators ==
and !=
.
This comparison can return true only for the entities of nullable types
if they actually have the null value during the program execution. It must
be known during the compilation if other kinds of tests return false.
Similarly, a comparison to undefined produces false unless the variable being compared is of type undefined, or of a union type that has undefined as one of its types.
The following comparisons evaluate to false at compile time:
15 == null // false
25 == undefined // false
3((): void => {}) == null // false
4
5class X {}
6new X() == null // false
The following comparison is evaluated at runtime:
1 function foo(x: string | undefined) {
2 return x == undefined
3 }
7.25.6. Reference Equality¶
The reference equality compares two reference type operands.
A compile-time error occurs if:
Any operand is not of a reference type.
There is no implicit conversion (see Implicit Conversions) that can convert the type of either operand to the type of the other.
The result of ‘\(==\)’ or ‘\(===\)’ is true if both operand values:
Are
null
;Are
undefined
; orRefer to the same object, array, or function.
Otherwise, the result is false.
This semantics is illustrated by the following example:
1class X {}
2new X() == new X() // false, two different object of class X
3new X() === new X() // false, the same
4let x1 = new X()
5let x2 = x1
6x1 == x2 // true, as x1 and x2 refer to the same object
7x1 === x2 // true, the same
8
9new Number(5) === new Number(5) // false, different objects
10
11new Number(5) == new Number(5) // true, value equality is used
7.26. Bitwise and Logical Expressions¶
The bitwise operators and logical operators are as follows:
AND operator ‘&’;
Exclusive OR operator ‘^’; and
Inclusive OR operator ‘|’.
bitwiseAndLogicalExpression:
expression '&' expression
| expression '^' expression
| expression '|' expression
;
These operators have different precedence. The operator ‘&’ has the highest, and ‘\(|\)’ has the lowest precedence.
The operators group left-to-right. Each operator is commutative, if the operand expressions have no side effects, and associative.
The bitwise and logical operators can compare two operands of a numeric type, or two operands of the boolean type. Otherwise, a compile-time error occurs.
7.26.1. Integer Bitwise Operators &, ^, and |¶
The numeric types conversion (see Primitive Types Conversions) is first performed on the operands of an operator ‘&’, ‘^’, or ‘|’ if both operands are of a type convertible (see Implicit Conversions) to a primitive integer type.
Note: If the initial type of one or both operands is double
or
float
, then that operand or operands are first truncated to the appropriate
integer type. If both operands are of type bigint, then no conversion is
required.
A bitwise operator expression type is the converted type of its operands.
The resultant value of ‘&’ is the bitwise AND of the operand values.
The resultant value of ‘^’ is the bitwise exclusive OR of the operand values.
The resultant value of ‘|’ is the bitwise inclusive OR of the operand values.
7.26.2. Boolean Logical Operators &, ^, and |¶
The type of the bitwise operator expression is boolean if both operands of a ‘&’, ‘^’, or ‘|’ operator are of type boolean or Boolean. In any case, the unboxing conversion (see Primitive Types Conversions) is performed on the operands if required.
If both operand values are true
, then the resultant value of ‘&’ is true.
Otherwise, the result is false.
If the operand values are different, then the resultant value of ‘^’ is true. Otherwise, the result is false.
If both operand values are false
, then the resultant value of ‘|’ is
false. Otherwise, the result is true.
7.27. Conditional-And Expression¶
The conditional-and operator ‘&&’ is similar to ‘&’ (see Bitwise and Logical Expressions) but evaluates its right-hand operand only if the left-hand operand’s value is true.
The computation results of ‘&&’ and ‘&’ on boolean operands are the same, but the right-hand operand in ‘&&’ can be not evaluated.
conditionalAndExpression:
expression '&&' expression
;
The conditional-and operator groups left-to-right.
The conditional-and operator is fully associative as regards both the result value and side effects (i.e., the evaluations of the expressions ((a) && (b)) && (c) and (a) && ((b) && (c)) produce the same result, and the same side effects occur in the same order for any a, b, and c).
A conditional-and expression is always of type boolean.
Each operand of the conditional-and operator must be of type boolean, or Boolean. Otherwise, a compile-time error occurs.
The left-hand operand expression is first evaluated at runtime. If the result is of type Boolean, then the unboxing conversion (see Primitive Types Conversions) is performed as follows:
If the resultant value is false, then the value of the conditional-and expression is false; the evaluation of the right-hand operand expression is omitted.
If the value of the left-hand operand is
true
, then the right-hand expression is evaluated. If the result of the evaluation is of type Boolean, then the unboxing conversion (see Primitive Types Conversions) is performed. The resultant value is the value of the conditional-and expression.
7.28. Conditional-Or Expression¶
The conditional-or operator ‘\(||\)’ is similar to ‘\(|\)’ (see Integer Bitwise Operators &, ^, and |) but evaluates its right-hand operand only if the value of its left-hand operand is false.
conditionalOrExpression:
expression '||' expression
;
The conditional-or operator groups left-to-right.
The conditional-or operator is fully associative as regards both the result value and side effects (i.e., the evaluations of the expressions ((a) || (b)) || (c) and (a) || ((b) || (c)) produce the same result, and the same side effects occur in the same order for any a, b, and c).
A conditional-or expression is always of type boolean.
Each operand of the conditional-or operator must be of type boolean or Boolean. Otherwise, a compile-time error occurs.
The left-hand operand expression is first evaluated at runtime. If the result is of the Boolean type, then the unboxing conversion () is performed as follows:
If the resultant value is true, then the value of the conditional-or expression is true, and the evaluation of the right-hand operand expression is omitted.
If the resultant value is false, then the right-hand expression is evaluated. If the result of the evaluation is of type Boolean, then the unboxing conversion is performed (see Unboxing Conversions). The resultant value is the value of the conditional-or expression.
The computation results of ‘||’ and ‘|’ on boolean operands are the same, but the right-hand operand in ‘||’ can be not evaluated.
7.29. Assignment¶
All assignment operators group right-to-left (i.e., \(a=b=c\) means \(a=(b=c)\)—and thus the value of c is assigned to b, and then the value of b to a).
assignmentExpression:
expression1 assignmentOperator expression2
;
assignmentOperator
: '='
| '+=' | '-=' | '*=' | '=' | '%='
| '<<=' | '>>=' | '>>>='
| '&=' | '|=' | '^='
;
The result of the first operand in an assignment operator (represented by expression1) must be one of the following:
A named variable, such as a local variable, or a field of the current object or class;
A computed variable resultant from a field access (see Field Access Expressions); or
An array or record component access (see Indexing Expression).
A compile-time error occurs if expression1 contains the chaining
operator ‘?.
’ (see Chaining Operator).
A compile-time error occurs if the result of expression1 is not a variable.
The type of the variable is the type of the assignment expression.
The result of the assignment expression at runtime is not itself a variable but the value of a variable after the assignment.
7.29.1. Simple Assignment Operator¶
A compile-time error occurs if the type of the right-hand operand (expression2) is not compatible (see Type Compatibility) with the type of the variable (see Generic Parameters). Otherwise, the expression is evaluated at runtime in one of the following ways:
If the left-hand operand expression1 is a field access expression e.f (see Field Access Expressions), possibly enclosed in a pair of parentheses, then:
expression1 e is evaluated: if the evaluation of e completes abruptly, then so does the assignment expression.
The right-hand operand expression2 is evaluated: if the evaluation completes abruptly, then so does the assignment expression.
The value of the right-hand operand as computed above is assigned to the variable denoted by e.f.
If the left-hand operand is an array access expression (see Array Indexing Expression), possibly enclosed in a pair of parentheses, then:
The array reference subexpression of the left-hand operand is evaluated. If this evaluation completes abruptly, then so does the assignment expression. In that case, the right-hand operand and the index subexpression are not evaluated, and the assignment does not occur.
If the evaluation completes normally, then the index subexpression of the left-hand operand is evaluated. If this evaluation completes abruptly, then so does the assignment expression. In that case, the right-hand operand is not evaluated, and the assignment does not occur.
If the evaluation completes normally, then the right-hand operand is evaluated. If this evaluation completes abruptly, then so does the assignment expression, and the assignment does not occur.
If the evaluation completes normally, but the value of the index subexpression is less than zero, or greater than, or equal to the length of the array, then ArrayIndexOutOfBoundsError is thrown, and the assignment does not occur.
Otherwise, the value of the index subexpression is used to select an element of the array referred to by the value of the array reference subexpression.
That element is a variable of type SC. If TC is the type of the left-hand operand of the assignment operator determined at compile time, then there are two options:
If TC is a primitive type, then SC can only be the same as TC.
The value of the right-hand operand is converted to the type of the selected array element. The value set conversion (see Implicit Conversions) is performed to convert it to the appropriate standard value set (not an extended-exponent value set). The result of the conversion is stored into the array element.
If TC is a reference type, then SC can be the same as TC or a type that extends or implements TC.
If the ArkTS compiler cannot guarantee at compile time that the array element is exactly of type TC, then a check must be performed at runtime to ensure that class RC—i.e., the class of the object referred to by the value of the right-hand operand at runtime—is compatible with the actual type SC of the array element (see Type Compatibility with Initializer).
If class RC is not assignable to type SC, then ArrayStoreError is thrown, and the assignment does not occur.
Otherwise, the reference value of the right-hand operand is stored in the selected array element.
If the left-hand operand is a record access expression (see Record Indexing Expression), possibly enclosed in a pair of parentheses, then:
The object reference subexpression of the left-hand operand is evaluated. If this evaluation completes abruptly, then so does the assignment expression. In that case, the right-hand operand and the index subexpression are not evaluated, and the assignment does not occur.
If the evaluation completes normally, the index subexpression of the left-hand operand is evaluated. If this evaluation completes abruptly, then so does the assignment expression. In that case, the right-hand operand is not evaluated, and the assignment does not occur.
If the evaluation completes normally, the right-hand operand is evaluated. If this evaluation completes abruptly, then so does the assignment expression. In that case, the assignment does not occur.
Otherwise, the value of the index subexpression is used as the key. In that case, the right-hand operand is used as the value, and the key-value pair is stored in the record instance.
If none of the above is true, then the following three steps are required:
The left-hand operand is evaluated to produce a variable. If the evaluation completes abruptly, then so does the assignment expression. In that case, the right-hand operand is not evaluated, and the assignment does not occur.
If the evaluation completes normally, then the right-hand operand is evaluated. If the evaluation completes abruptly, then so does the assignment expression. In that case, the assignment does not occur.
If that evaluation completes normally, then the value of the right-hand operand is converted to the type of the left-hand variable. In that case, the result of the conversion is stored into the variable. A compile-time error occurs if the type of the left-hand variable is readonly array, while the converted type of the right-hand operand is a non-readonly array.
7.29.2. Compound Assignment Operators¶
A compound assignment expression in the form E1 op= E2 is equivalent to E1 = ((E1) op (E2)) as T, where T is the type of E1, except that E1 is evaluated only once. This expression can be evaluated at runtime in one of the following ways:
If the left-hand operand expression is not an indexing expression:
The left-hand operand is evaluated to produce a variable. If the evaluation completes abruptly, then so does the assignment expression. In that case, the right-hand operand is not evaluated, and the assignment does not occur.
If the evaluation completes normally, then the value of the left-hand operand is saved, and the right-hand operand is evaluated. If the evaluation completes abruptly, then so does the assignment expression. In that case, the assignment does not occur.
If the evaluation completes normally, then the saved value of the left-hand variable, and the value of the right-hand operand are used to perform the binary operation as indicated by the compound assignment operator. If the operation completes abruptly, then so does the assignment expression. In that case, the assignment does not occur.
If the evaluation completes normally, then the result of the binary operation converts to the type of the left-hand variable. The result of such conversion is stored into the variable.
If the left-hand operand expression is an array access expression (see Array Indexing Expression), then:
The array reference subexpression of the left-hand operand is evaluated. If the evaluation completes abruptly, then so does the assignment expression. In that case, the right-hand operand and the index subexpression are not evaluated, and the assignment does not occur.
If the evaluation completes normally, then the index subexpression of the left-hand operand is evaluated. If the evaluation completes abruptly, then so does the assignment expression. In that case, the right-hand operand is not evaluated, and the assignment does not occur.
If the evaluation completes normally, the value of the array reference subexpression refers to an array, and the value of the index subexpression is less than zero, greater than, or equal to the length of the array, then ArrayIndexOutOfBoundsError is thrown. In that case, the assignment does not occur.
If the evaluation completes normally, then the value of the index subexpression is used to select an array element referred to by the value of the array reference subexpression. The value of this element is saved, and then the right-hand operand is evaluated. If the evaluation completes abruptly, then so does the assignment expression. In that case, the assignment does not occur.
If the evaluation completes normally, consideration must be given to the saved value of the array element selected in the previous step. While this element is a variable of type S, and T is the type of the left-hand operand of the assignment operator determined at compile time:
If T is a primitive type, then S is the same as T.
The saved value of the array element, and the value of the right-hand operand are used to perform the binary operation of the compound assignment operator.
If this operation completes abruptly, then so does the assignment expression. In that case, the assignment does not occur.
If this evaluation completes normally, then the result of the binary operation converts to the type of the selected array element. The result of the conversion is stored into the array element.
If T is a reference type, then it must be string.
S must also be a string because the class string is the final class. The saved value of the array element and the value of the right-hand operand are used to perform the binary operation (string concatenation) of the compound assignment operator ‘\(+=\)’. If this operation completes abruptly, then so does the assignment expression. In that case, the assignment does not occur.
If the evaluation completes normally, then the string result of the binary operation is stored into the array element.
If the left-hand operand expression is a record access expression (see Record Indexing Expression):
The object reference subexpression of the left-hand operand is evaluated. If this evaluation completes abruptly, then so does the assignment expression. In that case, the right-hand operand and the index subexpression are not evaluated, and the assignment does not occur.
If this evaluation completes normally, then the index subexpression of the left-hand operand is evaluated. If the evaluation completes abruptly, then so does the assignment expression. In that case, the right-hand operand is not evaluated, and the assignment does not occur.
If this evaluation completes normally, the value of the object reference subexpression and the value of index subexpression are saved, then the right-hand operand is evaluated. If the evaluation completes abruptly, then so does the assignment expression. In that case, the assignment does not occur.
If this evaluation completes normally, the saved values of the object reference subexpression and index subexpression (as the key) are used to get the value that is mapped to the key (see Record Indexing Expression), then this value and the value of the right-hand operand are used to perform the binary operation as indicated by the compound assignment operator. If the operation completes abruptly, then so does the assignment expression. In that case, the assignment does not occur.
If the evaluation completes normally, then the result of the binary operation is stored as the key-value pair in the record instance (as in Simple Assignment Operator).
7.30. Conditional Expressions¶
The conditional expression ‘\(? :\)’ uses the boolean value of the first expression to decide which of the other two expressions to evaluate:
conditionalExpression:
expression '?' expression ':' expression
;
The conditional operator ‘\(? :\)’ groups right-to-left (i.e., \(a?b:c?d:e?f:g\) and \(a?b:(c?d:(e?f:g))\) have the same meaning).
The conditional operator ‘\(? :\)’ consists of three operand expressions with the separators ‘\(?\)’ between the first and the second, and ‘\(:\)’ between the second and the third expression.
A compile-time error occurs if the first expression is not of type boolean or Boolean.
Type of the conditional expression is determined as the union of types of the second and the third expressions further normalized in accordance with the process discussed in Union Types Normalization. If the second and the third expressions are of the same type, then this is the type of the conditional expression.
The following steps are performed as the evaluation of a conditional expression occurs at runtime:
The first operand expression of a conditional expression is evaluated. The unboxing conversion is performed on the result if necessary. If the unboxing conversion fails, then so does the evaluation of the conditional expression.
If the value of the first operand is
true
, then the second operand expression is evaluated. Otherwise, the third operand expression is evaluated. The result of successful evaluation is the result of the conditional expression.
The examples below represent different scenarios with standalone expressions:
1 class A {}
2 class B extends A {}
3
4 condition ? new A() : new B() // A | B => A
5
6 condition ? 5 : 6 // 5 | 6
7
8 condition ? "5" : 6 // "5" | 6
7.31. String Interpolation Expressions¶
A ‘string interpolation expression’ is a template literal (a string literal delimited with backticks, see Template Literals for details) that contains at least one embedded expression:
stringInterpolation:
'`' (BacktickCharacter | embeddedExpression)* '`'
;
embeddedExpression:
'${' expression '}'
;
An ‘embedded expression’ is an expression specified inside curly braces preceded by the dollar sign ‘$’. A string interpolation expression is of type string (see string Type).
When evaluating a string interpolation expression, the result of each embedded expression substitutes that embedded expression. An embedded expression must be of type string. Otherwise, the implicit conversion to string takes place in the same way as with the string concatenation operator (see String Concatenation):
1 let a = 2
2 let b = 2
3 console.log(`The result of ${a} * ${b} is ${a * b}`)
4 // prints: The result of 2 * 2 is 4
The string concatenation operator can be used to rewrite the above example as follows:
1 let a = 2
2 let b = 2
3 console.log("The result of " + a + " * " + b + " is " + a * b)
An embedded expression can contain nested template strings.
7.32. Lambda Expressions¶
‘Lambda expression’ is a short block of code that takes in parameters and can return a value. Lambda expressions are generally similar to functions, but do not need names. Lambda expressions can be implemented immediately within expressions:
lambdaExpression:
'async'? signature '=>' lambdaBody
;
lambdaBody:
expression | block
;
1 (x: number): number => { return Math.sin(x) }
2 (x: number) => Math.sin(x) // expression as lambda body
A lambda expression evaluation:
Produces an instance of a function type (see Function Types).
Does not cause the execution of the expression body. However, the expression body can be executed later if a function call expression is used on the produced instance.
See Throwing Functions for the details of ‘throws
’, and
Rethrowing Functions for the details of ‘rethrows
’ marks.
7.32.1. Lambda Signature¶
Lambda signatures are composed of formal parameters and return types of lambda expressions and function declarations (see Function Declarations). Lambda expressions and function declarations have the same syntax and semantics.
See Scopes for the specification of the scope, and Shadowing Parameters for the shadowing details of formal parameter declarations.
A compile-time error occurs if a lambda expression declares two formal parameters with the same name.
As a lambda expression is evaluated, the values of actual argument expressions initialize the newly created parameter variables of the declared type before the execution of the lambda body.
7.32.2. Lambda Body¶
Lambda body can be a single expression or a block (see Block). Similarly to the body of a method or a function, a lambda body describes the code to be executed when a lambda call occurs (see Runtime Evaluation of Lambda Expressions).
The meanings of names, and of the keywords this
and super
(along with
the accessibility of the referred declarations) are the same as in the
surrounding context. However, lambda parameters introduce new names.
If a single expression, a lambda body is equivalent to the block with one return statement that returns that single expression { return singleExpression }.
If completing normally, a lambda body block is value-compatible. A lambda body completing normally means that the statement of the form return expression is executed.
If any local variable or formal parameter of the surrounding context is used but not declared in a lambda body, then the local variable or formal parameter is captured by the lambda.
If an instance member of the surrounding type is used in the lambda body
defined in a method, then this
is captured by the lambda.
A compile-time error occurs if a local variable is used in a lambda body but is neither declared in nor assigned before it.
7.32.3. Lambda Expression Type¶
‘Lambda expression type’ is a function type (see Function Types) that has the following:
Lambda parameters (if any) as parameters of the function type; and
Lambda return type as the return type of the function type.
Note: Lambda return type can be inferred from the lambda body.
7.32.4. Runtime Evaluation of Lambda Expressions¶
The evaluation of a lambda expression is distinct from the execution of the lambda body.
If completing normally at runtime, the evaluation of a lambda expression produces a reference to an object. In that case, it is similar to the evaluation of a class instance creation expression.
The evaluation of a lambda expression:
Allocates and initializes a new instance of a class with the properties below; or
Refers to an existing instance of a class with the properties below.
If the available space is not sufficient for a new instance to be created, then the evaluation of the lambda expression completes abruptly, and OutOfMemoryError is thrown.
During a lambda expression evaluation, the captured values of the lambda expression are saved to the internal state of the lambda.
Source Fragment |
Output |
---|---|
1 function foo() {
2 let y: int = 1
3 let x = () => { return y+1 }
4 console.log(x())
5 }
|
|
The variable ‘y’ is captured by the lambda.
The captured variable is not a copy of the original variable. If the value of the variable captured by the lambda changes, then the original variable is implied to change:
Source Fragment |
Output |
---|---|
1 function foo() {
2 let y: int = 1
3 let x = () => { y++ }
4 console.log(y)
5 x()
6 console.log(y)
7 }
|
|
In order to cause the lambdas behave as required, the language implementation can act as follows:
Replace primitive type for the corresponding boxed type (x: int to x: Int) if the captured variable is of a primitive type;
Replace the captured variable’s type for a proxy class that contains an original reference (x: T for x: Proxy<T>; x.ref = original-ref) if that captured variable is of a non-primitive type.
If the captured variable is defined as ‘const’, then neither boxing nor proxying is required.
If the captured formal parameter can be neither boxed nor proxied, then the implementation can require the addition of a local variable as follows:
Source Code |
Pseudo Code |
---|---|
1 function foo(y: int) {
2 let x = () => { return y+1 }
3 console.log(x())
4 }
|
1 function foo(y: int) {
2 let y$: Int = y
3 let x = () => { return y$+1 }
4 console.log(x())
5 }
|
7.33. Dynamic Import Expression¶
Dynamic import expression allows loading a compilation unit asynchronously and dynamically.
dynamicImportExpression:
'import' '(' expression ')'
;
The expression must be of type string that denotes the path to the module to be loaded.
The result of this expression is Promise<DynamicObject> (see The Promise<T> Class and DynamicObject Type).
Methods of class Promise can be used to access the loaded unit, or to catch an error.
7.34. Constant Expressions¶
constantExpression:
expression
;
A ‘constant expression’ is an expression that denotes a value of a primitive type, or a string that completes normally while being composed only of the following:
Literals of a primitive type, and literals of type string (see Literals);
Casts to primitive types, and casts to type string (see Cast Expressions);
Unary operators ‘\(+\)’, ‘\(-\)’, ‘~’, and ‘!’, but not ‘\(++\)’ or ‘\(--\)’ (see Unary Plus, Unary Minus, Prefix Increment, and Prefix Decrement);
Multiplicative operators ‘\(*\)’, ‘\(/\)’, and ‘\(\%\)’ (see Multiplicative Expressions);
Additive operators ‘\(+\)’ and ‘\(-\)’ (see Additive Expressions);
Shift operators ‘<<’, ‘>>’, and ‘>>>’ (see Shift Expressions);
Relational operators ‘<’, ‘<=’, ‘>’, and ‘>=’ (see Relational Expressions);
Equality operators ‘
==
’ and ‘!=
’ (see Equality Expressions);Bitwise and logical operators ‘&’, ‘^’, and ‘|’ (see Bitwise and Logical Expressions);
Conditional-and operator ‘&&’ (see Conditional-And Expression), and conditional-or operator ‘||’ (see Conditional-Or Expression);
Ternary conditional operator ‘? :’ (see Conditional Expressions);
Parenthesized expressions (see Parenthesized Expression) that contain constant expressions;
Simple names that refer to constant variables;
Qualified names that have the form typeReference’.’identifier, and refer to constant variables.