15. Semantic Rules

This Chapter contains semantic rules to be used throughout the Specification document.

Note that the description of the rules is more or less informal.

Some details are omitted to simplify the understanding. See the formal language description for more information.


15.1. Subtyping

The subtype relationships are binary relationships of types.

The subtyping relation of S as a subtype of T is recorded as S <: T. It means that any object of type S can be safely used in any context in place of an object of type T.

By the definition of S <: T, type T belongs to the set of supertypes of type S. The set of supertypes includes all direct supertypes (see below), and all their respective supertypes.

More formally speaking, the set is obtained by reflexive and transitive closure over the direct supertype relation.

Direct supertypes of a non-generic class, or of the interface type C are all of the following:

Direct supertypes of the generic type C <F1,…, Fn> (for a generic class or interface type declaration C <F1,…, Fn> with n>0) are all of the following:

  • The direct superclass of C <F1,…, Fn>.

  • The direct superinterfaces of C <F1,…, Fn>.

  • Type Object if C <F1,…, Fn> is a generic interface type with no direct superinterfaces.

The direct supertype of a type parameter is the type specified as the constraint of that type parameter.


15.2. Override-Equivalent Signatures

Two functions, methods, or constructors M and N have the same signature if their names, type parameters (if any, see Generic Declarations), and formal parameter types are the same—after the formal parameter types of N are adapted to type parameters of M.

Formal definition for the same signatures is given below:

M < T1, … TMm > ( U1 , … UMn ): RM

N < T1, … TNm > ( U1 , … UNn ): RN

  • Mm = Nm and for any i in 1 .. Mm => Constraint ( TMi ) fits Constraint ( TNi )

  • Mn = Nn and for any i in 1 .. Mn => Constraint ( UMi ) fits Constraint ( UNi )

    • where Constraint of any type except type parameter returns the type itself, and

    • fits means the following:

      • The types are identical;

      • The data value sets of the first and the second ones have an intersection that leads to potential call ambiguities; or

      • Type T and rest parameters of T[] have a potential ambiguity.

type T1 = A | B
type T2 = A | C
// Types T1 and T2 fit each other as their data values have intersection with data values of type A

Signatures S1 and S2 are override-equivalent only if S1 and S2 are the same.

A compile-time error occurs if:

  • A package declares two or more functions with override-equivalent signatures.

  • A class declares two or more methods or constructors with override-equivalent signatures.

  • An interface declares two or more methods with override-equivalent signatures.

The examples below illustrate the concept:

// The same signatures

foo <T1, T2> ()
foo <G1, G2> ()
// The same number of type parameters and their constraints are identical

foo <T extends U1> (p1: U1, p2: U2)
foo <V extends U1> (r1: V, r2: U2)
/* The same number of parameters and their types are identical replacing
   type parameter with its constraint */

foo (p1: U1, p2: U2): R1
foo (q1: U1, q2: U2): R2
// The same number of parameters and their types are identical

class Base {}
class Derived extends Base {}

foo (p: Base)
foo (p: Derived)
/* The same number of parameters and intersection of data values of Derived
   and Base produce data set of Derived values */

foo (p: A | B)
foo (p: A | C)
/* The same number of parameters and intersection of data values of A | B
   and A | C produce data set of A values */

foo (p1: String)
foo (...p2: String[])
// The same due to ambiguity of the type String and rest parameter String[]
// foo("some string") fits both signatures


// Different signatures

foo (p1: String)
foo (p2: String[])
// As signatures String and String[] do not lead to call ambiguities
// foo ("some string") fits the first signature
// foo (["some string"]) fits the second one

15.3. Compatible Signature

Signature S1 with n parameters is compatible with the signature S2 with m parameters if:

  • n <= m;

  • All n parameter types in S2 are compatible (see Type Compatibility) with parameter types in the same positions in S1; and

  • All S2 parameters in positions from m - n up to m are optional (see Optional Parameters).

A return type, if available, is present in both signatures, and the return type of S1 is compatible (see Type Compatibility) with the return type of S2.


15.4. Overload Signature Compatibility

If several functions, methods, or constructors share the same body (implementation) or the same method with no implementation in an interface, then all first signatures without body must fit the last signature with or without the actual implementation for the interface method. Otherwise, a compile-time error occurs.

Signature S1 with n parameters fits signature S2 if:

  • S1 has n parameters, S2 has m parameters; and:

    • n <= m;

    • All n parameter types in S1 are compatible (see Type Compatibility) with parameter types in the same positions in S2; and

    • If n < m, then all S2 parameters in positions from n + 1 up to m are optional (see Optional Parameters).

  • Both S1 and S2 have return types, and the return type of S2 is compatible with the return type of S1 (see Type Compatibility).

It is illustrated by the example below:

 1class Base { ... }
 2class Derived1 extends Base { ... }
 3class Derived2 extends Base { ... }
 4class SomeClass { ... }
 5
 6interface Base1 { ... }
 7interface Base2 { ... }
 8class Derived3 implements Base1, Base2 { ... }
 9
10function foo (p: Derived2): Base1 // signature #1
11function foo (p: Derived1): Base2 // signature #2
12function foo (p: Derived2): Base1 // signature #1
13function foo (p: Derived1): Base2 // signature #2
14// function foo (p: SomeClass): SomeClass
15   // Error as 'SomeClass' is not compatible with 'Base'
16// function foo (p: number)
17   // Error as 'number' is not compatible with 'Base' and implicit return type 'void' also incompatible with Base
18function foo (p1: Base, p2?: SomeClass): Derived3 // // signature #3: implementation signature
19    { return p }

15.5. Type Compatibility

Type T1 is compatible with type T2 if:

  • T1 is the same as T2, or

  • There is an implicit conversion (see Implicit Conversions) that allows converting type T1 to type T2.


15.6. Compatibility Features

Some features are added to ArkTS in order to support smooth TypeScript compatibility. Using this features is not recommended in most cases while doing the ArkTS programming.


15.6.1. Extended Conditional Expressions

ArkTS provides extended semantics for conditional-and and conditional-or expressions to ensure better alignment with TypeScript. It affects the semantics of conditional expressions (see Conditional Expressions), while and do statements (see while Statements and do Statements), for statements (see for Statements), if statements (see if Statements), and assignment (see Simple Assignment Operator).

This approach is based on the concept of truthiness that extends the Boolean logic to operands of non-Boolean types, while the result of an operation (see Conditional-And Expression, Conditional-Or Expression, Logical Complement) is kept boolean. Depending on the kind of the value type, the value of any valid expression can be handled as true or false as described in the table below:

Value Type

When false

When true

ArkTS Code

string

empty string

non-empty string

s.length == 0

boolean

false

true

x

enum

enum constant treated as ‘false’

enum constant treated as ‘true’

x.getValue()

number (double/float)

0 or NaN

any other number

n != 0 && n != NaN

any integer type

== 0

!= 0

i != 0

char

== 0

!= 0

c != c’0’

let T - is any non-nullish type

T | null

== null

!= null

x != null

T | undefined

== undefined

!= undefined

x != undefined

T | undefined | null

== undefined or == null

!= undefined and != null

x != undefined && x != null

Boxed primitive type (Boolean, Char, Int …)

primitive type is false

primitive type is true

new Boolean(true) == true new Int (0) == 0

any other nonNullish type

never

always

new SomeType != null

The example below illustrates the way this approach works in practice. Any nonzero number is handled as true. The loop continues until it becomes zero that is handled as false:

 1 for (let i = 10; i; i--) {
 2    console.log (i)
 3 }
 4 /* And the output will be
 5      10
 6      9
 7      8
 8      7
 9      6
10      5
11      4
12      3
13      2
14      1
15  */