3. Types

This chapter introduces the notion of type that is one of the fundamental concepts of ArkTS and other programming languages. Type classification as accepted in ArkTS is discussed below—along with all aspects of using types in programs written in the language.

Conventionally, the type of an entity is defined as the set of values the entity can take, and the set of operators applicable to the entity of a given type.

ArkTS is a statically typed language. It means that the type of every declared entity and every expression is known at compile time. The type of an entity is either set explicitly by a developer, or inferred implicitly by the compiler.

There are two categories of types:

  1. Value Types, and

  2. Reference Types.

The types integral to ArkTS are called predefined types (see Predefined Types).

The types introduced, declared, and defined by a developer are called user-defined types. All user-defined types must always have a complete type definition presented as source code in ArkTS.


3.1. Predefined Types

Predefined types include the following:

  • Basic numeric value type: number

  • High-performance value types:

    • Numeric types: byte, short, int, long, float, and double;

    • Character type: char;

    • Boolean type: boolean;

  • Reference types: object, string, [] (array), bigint, void, never, and undefined;

  • Class types: Object, String, Array<T>, and BigInt.

Each predefined value type has a corresponding predefined class type that wraps the value of the predefined value type: Number, Byte, Short, Int, Long, Float, Double, Char, and Boolean.

The predefined value types are called primitive types. Primitive type names are reserved, i.e., they cannot be used for user-defined type names.

Type double is an alias to number. Type Double is an alias to Number.


3.2. User-Defined Types

User-defined types include the following:


3.3. Types by Category

All ArkTS types are summarized in the following table:

Predefined Types

User-Defined Types

Value Types (Primitive Types)

Reference Types

Value Types

Reference Types

number, byte, short, int, long, float, double, char, boolean

Number, Byte, Short, Int, Long, Float, Double, Char, Boolean, Object, object, void, null, String, string, BigInt, bigint, never

enum types

class types, interface types, array types, function types, tuple types, union types, type parameters


3.4. Using Types

A type can be referred to in source code by the following:

type:
    predefinedType
    | typeReference
    | arrayType
    | tupleType
    | functionType
    | unionType
    | keyofType
    | '(' type ')'
    ;

It is presented by the example below:

1 let b: boolean  // using primitive value type name
2 let n: number   // using primitive value type name
3 let o: Object   // using predefined class type name
4 let a: number[] // using array type

Parentheses in types (where a type is a combination of array, function, or union types) are used to specify the required type structure. Without parentheses, the symbol ‘|’ that constructs a union type has the lowest precedence as presented in the following example:

 1 // a nullable array with elements of type string:
 2 let a: string[] | null
 3 let s: string[] = []
 4 a = s    // ok
 5 a = null // ok, a is nullable
 6
 7 // an array with elements whose types are string or null:
 8 let b: (string | null)[]
 9 b = null // error, b is an array and is not nullable
10 b = ["aa", null] // ok
11
12 // a function type that returns string or null
13 let c: () => string | null
14 c = null // error, c is not nullable
15 c = (): string | null => { return null } // ok
16
17 // (a function type that returns string) or null
18 let d: (() => string) | null
19 d = null // ok, d is nullable
20 d = (): string => { return "hi" } // ok

3.5. Named Types

Classes, interfaces, enumerations, and unions are named types. Respective named types are introduced by the following:

Classes and interfaces with type parameters are generic types (see Generics). Named types without type parameters are non-generic types.

Type references (see Type References) refer to named types by specifying their type names, and (where applicable) by type arguments to be substituted for the type parameters of the named type.


3.6. Type References

A type reference refers to a type by one of the following:

If the referred type is a class or an interface type, then each identifier in a name or an alias can be optionally followed by a type argument (see Type Arguments):

typeReference:
    typeReferencePart ('.' typeReferencePart)*
    |  Identifier '!'
    ;

typeReferencePart:
    Identifier typeArguments?
    ;
1 let map: Map<string, number>

3.7. Value Types

Predefined integer types (see Integer Types and Operations), floating-point types (see Floating-Point Types and Operations), the boolean type (see Boolean Types and Operations), character types (see Character Type and Operations), and user-defined enumeration types (see Enumerations) are value types.

The values of such types do not share state with other values.


3.7.1. Integer Types and Operations

Type

Type’s Set of Values

Corresponding Class Type

byte

All signed 8-bit integers (\(-2^7\) to \(2^7-1\))

Byte

short

All signed 16-bit integers (\(-2^{15}\) to \(2^{15}-1\))

Short

int

All signed 32-bit integers (\(-2^{31}\) to \(2^{31} - 1\))

Int

long

All signed 64-bit integers (\(-2^{63}\) to \(2^{63} - 1\))

Long

bigint

All integers with no limits

BigInt

ArkTS provides a number of operators to act on integer values as discussed below.

The classes Byte, Short, Int, and Long predefine constructors, methods, and constants that are parts of the ArkTS standard library (see Standard Library).

If one operand is not of type long, then the numeric promotion (see Primitive Types Conversions) must be used first to widen it to type long.

If neither operand is of type long, then:

  • The operation implementation uses 32-bit precision.

  • The result of the numerical operator is of type int.

If one operand (or neither operand) is of type int, then the numeric promotion must be used first to widen it to type int.

Any integer type value can be cast to or from any numeric type.

Casts between types integer and boolean are not allowed.

The integer operators cannot indicate an overflow or an underflow.

An integer operator can throw errors (see Error Handling) as follows:

  • An integer division operator ‘/’ (see Division), and an integer remainder operator ‘%’ (see Remainder) throw ArithmeticError if their right-hand operand is zero.

  • An increment operator ‘++’ and a decrement operator ‘–’ (see Additive Expressions) throw OutOfMemoryError if boxing conversion (see Boxing Conversions) is required but the available memory is not sufficient to perform it.


3.7.2. Floating-Point Types and Operations

Type

Type’s Set of Values

Corresponding Class Type

float

The set of all IEEE 754 [3] 32-bit floating-point numbers floating-point numbers

Float

number, double

The set of all IEEE 754 64-bit floating-point numbers

Number Double

ArkTS provides a number of operators to act on floating-point type values as discussed below.

The classes Float and Double predefine constructors, methods, and constants that are parts of the ArkTS standard library (see Standard Library).

An operation is called a floating-point operation if at least one of the operands in a binary operator is of the floating-point type (even if the other operand is integer).

If at least one operand of the numerical operator is of type double, then the operation implementation uses 64-bit floating-point arithmetic. The result of the numerical operator is a value of type double.

If the other operand is not of type double, then the numeric promotion (see Primitive Types Conversions) must be used first to widen it to type double.

If neither operand is of type double, then the operation implementation is to use 32-bit floating-point arithmetic. The result of the numerical operator is a value of type float.

If the other operand is not of type float, then the numeric promotion must be used first to widen it to type float.

Any floating-point type value can be cast to or from any numeric type.

Casts between types floating-point and boolean are not allowed.

Operators on floating-point numbers, except the remainder operator (see Remainder), behave in compliance with the IEEE 754 Standard. For example, ArkTS requires the support of IEEE 754 denormalized floating-point numbers and gradual underflow that make it easier to prove the desirable properties of a particular numerical algorithm. Floating-point operations do not ‘flush to zero’ if the calculated result is a denormalized number.

ArkTS requires floating-point arithmetic to behave as if the floating-point result of every floating-point operator is rounded to the result precision. An inexact result is rounded to the representable value nearest to the infinitely precise result. ArkTS uses the ‘round to nearest’ principle (the default rounding mode in IEEE 754), and prefers the representable value with the least significant bit zero out of any two equally near representable values.

ArkTS uses ‘round toward zero’ to convert a floating-point value to an integer (see Primitive Types Conversions). In this case it acts as if the number is truncated, and the mantissa bits are discarded. The result of rounding toward zero is the value of that format that is closest to and no greater in magnitude than the infinitely precise result.

A floating-point operation with overflow produces a signed infinity.

A floating-point operation with underflow produces a denormalized value or a signed zero.

A floating-point operation with no mathematically definite result produces NaN.

All numeric operations with a NaN operand result in NaN.

A floating-point operator (the increment ‘++’ operator and decrement ‘–’ operator, see Additive Expressions) can throw OutOfMemoryError (see Error Handling) if boxing conversion (see Boxing Conversions) is required but the available memory is not sufficient to perform it.


3.7.3. Numeric Types Hierarchy

Integer and floating-point types are numeric types.

Larger types include smaller types or their values:

  • double > float > long > int > short > byte

A value of a smaller type can be assigned to a variable of a larger type.

Type bigint does not belong to the hierarchy. There is no implicit conversion from a numeric type to bigint. Standard library class BigInt must be used to create bigint values from numeric types.


3.7.4. Boolean Types and Operations

Type boolean represents logical values true and false that correspond to the class type Boolean.

The boolean operators are as follows:

The conversion of an integer or floating-point expression x to a boolean value must follow the C language convention—any nonzero value is converted to true, and the value of zero is converted to false. In other words, the result of expression x conversion to type boolean is always the same as the result of comparison x != 0.


3.8. Reference Types

Reference types can be of the following kinds:


3.8.1. Objects

An object can be a class instance, a function instance, or an array. The pointers to these objects are called references or reference values.

A class instance creation expression (see New Expressions) explicitly creates a class instance.

Referring to a declared function by its name, qualified name, or lambda expression (see Lambda Expressions) explicitly creates a function instance.

An array creation expression explicitly creates an array (see Array Creation Expressions).

A string literal initialization explicitly creates a string.

Other expressions can implicitly create a class instance (see New Expressions), or an array (see Array Creation Expressions).

The operations on references to objects are as follows:

Multiple references to an object are possible.

Most objects have state. The state is stored in the field if an object is an instance of class, or in a variable that is an element of an array object.

If two variables contain references to the same object, and the state of that object is modified in the reference of one variable, then the state so modified can be seen in the reference of the other variable.


3.8.2. Object Class Type

The class Object is a supertype of all other classes, interfaces, string, arrays, unions, function types, and enum types. Thus all of them inherit (see Inheritance) the methods of the class Object. Full description of all methods of class Object is given in the standard library (see Standard Library) description.

The method toString as used in the examples in this document returns a string representation of the object.

Using Object is recommended in all cases (although the name object refers to type Object).


3.8.3. string Type

Type string is a predefined type. It stores sequences of characters as Unicode UTF-16 code units. Type string includes all string literals, e.g., ‘abc’.

The value of a string object cannot be changed after it is created, i.e., a string object is immutable.

The value of a string object can be shared.

Type string has dual semantics:

If the result is not a constant expression (see Constant Expressions), then the string concatenation operator ‘+’ (see String Concatenation) implicitly creates a new string object.

Using string is recommended in all cases (although the name String also refers to type string).


3.8.4. never Type

The type never is a subtype (see Subtyping) of any other type.

Type never type has no instances. It is used to represent values that do not exist (for example a function with this return type never returns a value, but finishes its work throwing an error or exception).


3.8.5. void Type

Type void has no instances (no values). It is typically used as the return type if a function or a method returns no value:

1 function foo (): void {}
2
3 class C {
4     bar(): void {}
5 }

A compile-time occurs if:

  • void is used as type annotation;

  • An expression of the void type is used as a value.

1 let x: void // compile-time error - void used as type annotation
2
3 function foo (): void
4 let y = foo()  // void used as a value

Type void can be used as type argument that instantiates a generic type if a specific value of type argument is irrelevant. In this case, it is synonymous to type undefined (see undefined Type):

1class A<T>
2let a = new A<void>() // ok, type parameter is irrelevant
3let a = new A<undefined>() // ok, the same
4
5function foo<T>(x: T) {}
6
7foo<void>(undefined) // ok
8foo<void>(void) // compile-time error: void is used as value

3.8.6. Array Types

Array type is the built-in type characterized by the following:

  • Any object of array type contains elements indexed by integer position starting from 0;

  • Access to any array element is performed within the same time;

  • If passed to non-ArkTS environment, an array is represented as a contiguous memory location;

  • Types of all array elements are upper-bounded by the element type specified in the array declaration.

Two basic operations with array elements take elements out of, and put elements into an array by using the operator [] and index expression.

The number of elements in an array can be obtained by accessing the field length. Setting a new value of this field allows shrinking the array by reducing the number of its elements.

Attempting to increase the length of the array causes a compile-time error (if the compiler has the information sufficient to determine this), or to a run-time error.

An example of syntax for the built-in array type is presented below:

arrayType:
   type '[' ']'
   ;

The family of array types that are parts of the standard library (see Standard Library), including all available operations, is described in the library documentation. Common to these types is that the operator [] can be applied to variables of all array types and to their derived types. It is noteworthy that type T[] and type Array<T> are as follows:

  • Equivalent if T is a reference type; and

  • Different if T is a value type.

The examples are presented below:

 1 let a : number[] = [0, 0, 0, 0, 0]
 2   /* allocate array with 5 elements of type number */
 3 a[1] = 7 /* put 7 as the 2nd element of the array, index of this element is 1 */
 4 let y = a[4] /* get the last element of array 'a' */
 5 let count = a.length // get the number of array elements
 6 a.length = 3
 7 y = a[2] // OK, 2 is the index of the last element now
 8 y = a[3] // Will lead to runtime error - attempt to access non-existing array element
 9
10 let b: Number[] = new Array<Number>
11    /* That is a valid code as type used in the 'b' declaration is identical
12       to the type used in the new expression */

A type alias can set a name for an array type (see Type Alias Declaration):

1 type Matrix = number[][] /* Two-dimensional array */

An array as an object is assignable to a variable of type Object:

1 let a: number[] = [1, 2, 3]
2 let o: Object = a

3.8.7. Function Types

A function type can be used to express the expected signature of a function. A function type consists of the following:

  • List of parameters (which can be empty);

  • Optional return type;

  • Optional keyword throws.

functionType:
    '(' ftParameterList? ')' ftReturnType 'throws'?
    ;

ftParameterList:
    ftParameter (',' ftParameter)\* (',' restParameter)?
    | restParameter
    ;

ftParameter:
    identifier ':' type
    ;

restParameter:
    '...' ftParameter
    ;

ftReturnType:
    '=>' type
    ;

The rest parameter is described in Rest Parameter.

1 let binaryOp: (x: number, y: number) => number
2 function evaluate(f: (x: number, y: number) => number) { }

A type alias can set a name for a function type (see Type Alias Declaration).

1 type BinaryOp = (x: number, y: number) => number
2 let op: BinaryOp

If the function type contains the ‘throws’ mark (see Throwing Functions), then it is the throwing function type.

Function types assignability is described in Assignment-like Contexts, and conversions in Function Types Conversions.


3.8.8. null Type

The only value of type null is represented by the keyword null (see Null Literal).

Using type null as type annotation is not recommended, except in nullish types (see Nullish Types).


3.8.9. undefined Type

The only value of type undefined is represented by the keyword undefined (see Undefined Literal).

Using type undefined as type annotation is not recommended, except in nullish types (see Nullish Types).

The undefined type can be used as the type argument that instantiates a generic type if specific value of the type argument is irrelevant.

1class A<T> {}
2let a = new A<undefined>() // ok, type parameter is irrelevant
3function foo<T>(x: T) {}
4
5foo<undefined>(undefined) // ok

3.8.10. Tuple Types

tupleType:
    '[' (type (',' type)*)? ']'
    ;

A tuple type is a reference type created as a fixed set of other types. The value of a tuple type is a group of values of types that comprise the tuple type. The types are specified in the same order as declared within the tuple type declaration. Each element of the tuple is thus implied to have its own type. The operator [] (square brackets) is used to access the elements of a tuple in a manner similar to that used to access elements of an array.

An index expression belongs to integer type. The index of the 1st tuple element is 0. Only constant expressions can be used as the index to get the access to tuple elements.

1let tuple: [number, number, string, boolean, Object] =
2           [     6,      7,  "abc",    true,    666]
3tuple[0] = 666
4console.log (tuple[0], tuple[4]) // `666 666` be printed

Object (see Object Class Type) is the supertype for any tuple type.

An empty tuple is a corner case. It is only added to support compatibility with TypeScript:

1let empty: [] = [] // empty tuple with no elements in it

3.8.11. Union Types

unionType:
    type|literal ('|' type|literal)*
    ;

A union type is a reference type created as a combination of other types or values. Valid values of all types and literals the union is created from are the values of a union type.

A compile-time error occurs if the type in the right-hand side of a union type declaration leads to a circular reference.

If a union uses a primitive type (see Primitive types in Types by Category), then automatic boxing occurs to keep the reference nature of the type.

The reduced form of union types allows defining a type which has only one value:

1type T = 3
2let t1: T = 3 // OK
3let t2: T = 2 // Compile-time error

A typical example of the usage of union type is shown below:

 1 class Cat {
 2   // ...
 3 }
 4 class Dog {
 5   // ...
 6 }
 7 class Frog {
 8   // ...
 9 }
10 type Animal = Cat | Dog | Frog | number
11 // Cat, Dog, and Frog are some types (class or interface ones)
12
13 let animal: Animal = new Cat()
14 animal = new Frog()
15 animal = 42
16 // One may assign the variable of the union type with any valid value

Different mechanisms can be used to get values of particular types from a union:

 1 class Cat { sleep () {}; meow () {} }
 2 class Dog { sleep () {}; bark () {} }
 3 class Frog { sleep () {}; leap () {} }
 4
 5 type Animal = Cat | Dog | Frog
 6
 7 let animal: Animal = new Cat()
 8 if (animal instanceof Frog) {
 9         // animal is of type Frog here, conversion can be used:
10     let frog: Frog = animal as Frog
11     frog.leap()
12 }
13
14 animal.sleep () // Any animal can sleep

The following example is for primitive types:

1 type Primitive = number | boolean
2 let p: Primitive = 7
3 if (p instanceof Number) { // type of 'p' is Number here
4    let i: number = p as number // Explicit conversion from Primitive to number
5 }

The following example is for values:

1 type BMW_ModelCode = 325 | 530 | 735
2 let car_code: BMW_ModelCode = 325
3 if (car_code == 325){
4    car_code = 530
5 } else if (car_code == 530){
6    car_code = 735
7 } else {
8    // pension :-)
9 }

Note: A compile-time error occurs if an expression of union type is compared to a literal value that does not belong to the values of that union type:

 1 type BMW_ModelCode = 325 | 530 | 735
 2 let car_code: BMW_ModelCode = 325
 3 if (car_code == 666){ ... }
 4 /*
 5    compile-time error as 666 does not belong to
 6    values of type BMW_ModelCode
 7 */
 8
 9 function model_code_test (code: number) {
10    if (car_code == code) { ... }
11    // This test is to be resolved during program execution
12 }

3.8.11.1. Union Types Normalization

Union types normalization allows minimizing the number of types and literals within a union type, while keeping the type’s safety. Some types or literals can also be replaced for more general types.

Formally, union type T1 | … | TN, where N > 1, can be reduced to type U1 | … | UM, where M <= N, or even to a non-union type or value V. In this latter case V can be a primitive value type or value that changes the reference nature of the union type.

The normalization process presumes performing the following steps one after another:

  1. All nested union types are linearized.

  2. Identical types within the union type are replaced for a single type.

  3. Identical literals within the union type are replaced for a single literal.

  4. If at least one type in the union is Object, then all other non-nullish types are removed.

  5. If there is type never among union types, then it is removed.

  6. If there is a non-empty group of numeric types in a union, then the largest (see Numeric Types Hierarchy) numeric type is to stay in the union while the others are removed. All numeric literals (if any) that fit into the largest numeric type in a union are removed.

  7. If a primitive type after widening (see Widening Primitive Conversions) and boxing (see Boxing Conversions) is equal to another union type, then the intial type is removed.

  8. If a literal of union type belongs to the values of a type that is part of the union, then the literal is removed.

  9. If a numeric literal fits into the unboxed type of one of union numeric class type, then the literal is removed.

  10. This step is performed recursively until no mutually compatible types remain (see Type Compatibility), or the union type is reduced to a single type:

    • If a union type includes two types Ti and Tj (i != j), and Ti is compatible with Tj (see Type Compatibility), then only Tj remains in the union type, and Ti is removed.

    • If Tj is compatible with Ti (see Type Compatibility), then Ti remains in the union type, and Tj is removed.

The result of the normalization process is a normalized union type. The process is presented in the examples below:


 1 ( T1 | T2) | (T3 | T4) => T1 | T2 | T3 | T4  // Linearization
 2
 3 1 | 1 | 1  =>  1                             // Identical values elimination
 4
 5 number | number => number                    // Identical types elimination
 6
 7 number | Number => Number                    // The same after boxing
 8 Int | float => Float                         // tbd
 9 Int | 3.14  => Int | 3.14                    // Values fits into unboxed version of type Int
10
11 int|short|float|2 => float                   // the largest numeric type stays
12 int|long|2.71828 => long|2.71828             // the largest numeric type stays and the literal
13 1 | number | number => number
14 int | double | short => double
15 Byte | Int | Long => Long
16 Byte | Int | Float | Number => Number
17 Int | 3.14 | Float => Float | 3.14
18
19
20 1 | string | number => string | number       // Union value elimination
21
22 1 | Object => Object                         // Object wins
23 AnyNonNullishType | Object => Object
24
25 class Base {}
26 class Derived1 extends Base {}
27 class Derived2 extends Base {}
28 Base | Derived1 => Base                      // Base wins
29 Derived1 | Derived2 => Derived1 | Derived2   // End of normalization

The ArkTS compiler applies such normalization while processing union types and handling the type inference for array literals (see Array Type Inference from Types of Elements).


3.8.11.2. Keyof Types

A special form of union types are keyof types built with help of the keyword keyof. The keyword keyof is applied to the class or interface type (see Classes and Interfaces). The resultant new type is a union of names of all members of the class or interface type.

keyofType:
    'keyof' typeReference
    ;

A compile-time error occurs if typeReference is not a class or interface type. The semantics of the keyof type is presented in the following example:

1 class A {
2    field: number
3    method() {}
4 }
5 type KeysOfA = keyof A // "field" | "method"
6 let a_keys: KeysOfA = "field" // OK
7 a_keys = "any string different from field or method"
8   // Compile-time error: invalid value for the type KeysOfA

If the class or the interface is empty, then its keyof type is equivalent to type never:

1 class A {} // Empty class
2 type KeysOfA = keyof A // never

3.8.12. Nullish Types

ArkTS has nullish types that are in fact a special form of union types (see Union Types):

nullishType:
      type '|' 'null' (| 'undefined')?
    | type '|' 'undefined' ('|' 'null')?
    ;

All predefined and user-defined type declarations create non-nullish types. Non-nullish types cannot have a null or undefined value at runtime.

T | null or T | undefined can be used as the type to specify a nullish version of type T.

A variable declared to have type T | null can hold the values of type T and its derived types, or the value null. Such a type is called a nullable type.

A variable declared to have type T | undefined can hold the values of type T and its derived types, or the value undefined.

A variable declared to have type T | null | undefined can hold values of type T (and its derived types), and the values undefined or null.

A nullish type is a reference type (see Union Types). A reference that is null or undefined is called a nullish value.

An operation that is safe with no regard to the presence or absence of nullish values (e.g., re-assigning one nullable value to another) can be used ‘as is’ for nullish types.

The following nullish-safe options exist for operations on nullish type T that can potentially violate null safety (e.g., access to a property):


3.8.13. DynamicObject Type

The interface DynamicObject is used to provide seamless interoperability with dynamic languages (e.g., JavaScript and TypeScript), and to support advanced language features such as dynamic import (see Dynamic Import Expression). This interface is defined in Standard Library.

This interface (defined in Standard Library) is common for a set of wrappers (also defined in Standard Library) that provide access to underlying objects.

An instance of DynamicObject instance cannot be created directly. Only an instance of a specific wrapper object can be instantiated. For example, a result of the dynamic import expression (see Dynamic Import Expression) is an instance of the dynamic object implementation class, which wraps an object that contains exported entities of an imported module.

DynamicObject is a predefined type. The following operations applied to an object of type DynamicObject are handled by the compiler in a special way:

  • Field access;

  • Method call;

  • Indexing access;

  • New;

  • Cast.


3.8.13.1. DynamicObject Field Access

The field access expression D.F, where D is of type DynamicObject, is handled as an access to a property of an underlying object.

If the value of a field access is used, then it is wrapped in the instance of DynamicObject, since the actual type of the field is not known at compile time.

1function foo(d: DynamicObject) {
2   console.log(d.f1) // access of the property named "f1" of underlying object
3   d.f1 = 5 // set a value of the property named "f1"
4   let y = d.f1 // 'y' is of type DynamicObject
5}

The wrapper can raise an error if:

  • No property with the specified name exists in the underlying object; or

  • The field access is in the right-hand side of the assignment, and the type of the assigned value is not compatible (see Type Compatibility) with the type of the property.


3.8.13.2. DynamicObject Method Call

The method call expression D.F(arguments), where D is of type DynamicObject, is handled as a call of the instance method of an underlying object.

If the result of a method call is used, then it is wrapped in the instance of DynamicObject, since the actual type of the returned value is not known at compile time.

1function foo(d: DynamicObject) {
2   d.foo() // call of a method "foo" of underlying object
3   let y = d.goo() // 'y' is of type DynamicObject
4}

The wrapper must raise an error if:

  • No method with the specified name exists in the underlying object; or

  • The signature of the method is not compatible with the types of the call arguments.


3.8.13.3. DynamicObject Indexing Access

The indexing access expression D[index], where D is of type DynamicObject, is handled as an indexing access to an underlying object.

1function foo(d: DynamicObject) {
2   let x = d[0]
3}

The wrapper must raise an error if:

  • The indexing access is not supported by the underlying object;

  • The type of the index expression is not supported by the underlying object.


3.8.13.4. DynamicObject New Expression

The new expression new D(arguments) (see New Expressions), where D is of type DynamicObject, is handled as a new expression (constructor call) applied to the underlying object.

The result of the expression is wrapped in an instance of DynamicObject, as the actual type of the returned value is not known at compile time.

1function foo(d: DynamicObject) {
2   let x = new d()
3}

The wrapper must raise an error if:

  • A new expression is not supported by the underlying object; or

  • The signature of the constructor of the underlying object is not compatible with the types of call arguments.


3.8.13.5. DynamicObject Cast Expression

The cast expression D as T (see Cast Expressions), where D is of type DynamicObject, is handled as attempt to cast the underlying object to a static type T.

A compile-time error occurs if T is not a class or interface type.

The result of a cast expression is an instance of type T.

1interface I {
2   bar(): void
3}
4
5function foo(d: DynamicObject) {
6   let x = d as I
7   x.bar() // a call of interface method     (not dynamic)
8}

The wrapper must raise an error if an underlying object cannot be converted to the target type specified by the cast operator.


3.9. Default Values for Types

Note: This is the ArkTS’s experimental feature.

Some types use so-called default values for variables without explicit initialization (see Variable Declarations), including the following:

  • All primitive types (see the table below);

  • All union types that have at least one nullish (see Nullish Types) value, and use an appropriate nullish value as default (see the table below).

All other types, including reference types and enumeration types, have no default values. Variables of such types must be initialized explicitly with a value before the first use of a type.

Default values of primitive types are as follows:

Data Type

Default Value

number

0 as number

byte

0 as byte

short

0 as short

int

0 as int

long

0 as long

float

+0.0 as float

double

+0.0 as double

char

‘u0000’

boolean

false

The default values of nullish union types are as follows:

Data Type

Default Value

type | null

null

type | undefined

undefined

null | undefined

undefined

1class A {
2  f1: number|null
3  f2: string|undefined
4  f3?: boolean
5}
6let a = new A()
7console.log (a.f1, a.f2, a.f3)
8// Output: null, undefined, undefined