Extensions

Extensions can be used to add new functionalities to any type that is visible to the current package, except functions, tuples, and interfaces.

You can add the following functionalities:

  • Instance member function
  • Static member function
  • Operator overloading
  • Instance member property
  • Static member property
  • Implementation interface

Extensions can add a functionality after a type is defined, but cannot damage the encapsulation of the original type. Therefore, the following functionalities are prohibited:

  1. Extensions cannot add fields.
  2. Extensions cannot add abstract members.
  3. Extensions cannot add open members.
  4. Extensions cannot use override or redef on existing members.
  5. Extensions cannot access private members of the original type.

Extension Syntax

A simple extension example is as follows:

extend String {
    func printSize() {
        print(this.size)
    }
}

"123".printSize() // 3

The syntax of extension definition is as follows:

extendDefinition
    : 'extend' extendType
      ('<:' superInterfaces)? genericConstraints?
      extendBody
    ;

extendType
    : (typeParameters)? (identifier NL* DOT  NL*)* identifier (NL* typeArguments)?
    | INT8
    | INT16
    | INT32
    | INT64
    | INTNATIVE
    | UINT8
    | UINT16
    | UINT32
    | UINT64
    | UINTNATIVE
    | FLOAT16
    | FLOAT32
    | FLOAT64
    | CHAR
    | BOOLEAN
    | NOTHING
    | UNIT
    ;

extendBody
    : '{' extendMemberDeclaration* '}'
    ;

extendMemberDeclaration
    : (functionDefinition
    | operatorFunctionDefinition
    | propertyDefinition
    | macroExpression
    ) end*
    ;

The extend keyword is used to define an extension. The extension definition includes the extend keyword, optional generic parameters, the type being extended, optional implemented interfaces, optional generic constraints, and the body of the extension. {} cannot be omitted in the definition of an extension body.

Extensions can be defined only at the top level.

Extensions are classified into direct extensions and interface extensions. Direct extensions do not require the declaration of an additional interface.

Direct Extensions

Direct extensions do not require the declaration of an additional interface and can be used to directly add new functionalities to existing types.

class Foo {}

extend Foo {
    func f() {}
}

main() {
    let a = Foo()
    a.f() // call extension function
}

Interface Extensions

Interface extensions can be used to add new functionalities to existing types and implement interfaces to enhance abstraction flexibility. When using interface extension, you must declare the interface being implemented.

interface I {
    func f(): Unit
}

class Foo {}

extend Foo <: I {
    public func f() {}
}

Using interface extension functionalities to implement interface I for a type is equivalent to implementing interface I during type definition, but the scope is subject to extension import and export limitations, as detailed in [Importing and Exporting Extensions].

func g(i: I) {
    i.f()
}

main() {
    let a = Foo()
    g(a)
}

You can implement multiple interfaces within the same extension, separating them with &. The order of the interfaces does not matter.

interface I1 {
    func f1(): Unit
}

interface I2 {
    func f2(): Unit
}

interface I3 {
    func f3(): Unit
}

class Foo {}

extend Foo <: I1 & I2 & I3 {
    public func f1() {}
    public func f2() {}
    public func f3() {}
}

If the type being extended has already implemented an interface, it cannot be re-implemented through an extension, including any interfaces implemented through extensions.

interface I {
    func f(): Unit
}

class Foo <: I {
    func f(): Unit {}
}
extend Foo <: I {} // error, can not repeat the implementation of the interface

class Bar {}
extend Bar <: I {
    func f(): Unit {}
}
extend Bar <: I {} // error, already implemented through the extension can not repeat the implementation

If the type being extended has directly implemented a non-generic interface, it cannot re-implement that interface through an extension. If the type has directly implemented a generic interface, it cannot re-implement that interface with the same type parameters through an extension.

interface I1 {}

class Foo <: I1 {}

extend Foo <: I1 {} // error

interface I2<T> {}

class Goo<T> <: I2<Int32> {}

extend<T> Goo<T> <: I2<Int32> {} // error

extend<T> Goo<T> <: I2<T> {} // ok

If the type being extended already contains the functions required by the interface, the interface extension cannot re-implement these functions, nor does it use the default implementations from the interface.

class Foo {
    public func f() {}
}

interface I {
    func f(): Unit
}

extend Foo <: I {} // ok

extend Foo {
    public func g(): Unit {
        print("In extend!")
    }
}

interface I2 {
    func g(): Unit {
        print("In interface!")
    }
}

extend Foo <: I2 {} // ok, default implementation of g in I2 is no longer used

Orphan extensions are prohibited. An orphan extension is an interface extension that is defined neither in the same package as the interface (including all interfaces in the interface inheritance chain) nor in the same package as the type being extended.

That is, interface extension allows only the following two cases:

  1. The interface extension and the type definition are in the same package.
  2. The interfaces implemented by the interface extension, along with all interfaces in the inheritance chain that have not been implemented by the type being extended, must all be in the same package.

In other cases, interface extension cannot be defined.

// package pkg1
public class Foo {}
public class Goo {}

// package pkg2
public interface Bar {}
extend Goo <: Bar {}

// package pkg3
import pkg1.Foo
import pkg2.Bar

extend Foo <: Bar {} // error

interface Sub <: Bar {}

extend Foo <: Sub {} // error

extend Goo <: Sub {} // ok, 'Goo' has implemented the interface 'Bar' on the inheritance chain in pkg2.

Members of Extensions

The members of extensions include static member functions, instance member functions, static member properties, instance member properties, and operator overloading functions.

Functions

Extensions can add functions to the type being extended. These functions can be generic and support generic constraints, overloading, default parameters, and named parameters. However, these functions cannot be abstract.

For example:

interface I {}
extend Int64 {
    func f<T>(a: T, b!: Int64 = 0) where T <: I {}
    func f(a: String, b: String) {}
}

Modifiers

Function definitions within an extension support the use of private, internal, protected, or public modifiers.

Functions modified by private can be used within the extension and are not visible externally.

Member functions modified by protected can be accessed in this module (restricted by export rules). When the type being extended is class, the functions can also be accessed within the subclass of that class.

The accessibility modifier of a function within an extension is internal by default.

// file1 in package p1
package p1

public open class Foo {}

extend Foo {
    private func f1() {}   // ok
    public func f2() {}    // ok
    protected func f3() {} // ok
    func f4() {}           // visible in the package
}

main() {
    let a = Foo()
    a.f1() // error, can not access private function
    a.f2() // ok
    a.f3() // ok
    a.f4() // ok
}

// file2 in package p2
package p2

import p1.*

class Bar <: Foo {
    func f() {
        f1() // error, can not access private function
        f2() // ok
        f3() // ok
        f4() // error, can not access default function
    }
}

Functions in an extension can be modified using static.

class Foo {}

extend Foo {
    static func f() {}
}

Extensions of the struct type can define the mut function.

struct Foo {
    var i = 0
}

extend Foo {
    mut func f() { // ok
        i += 1
    }
}

The function definition in the extension cannot be modified by open, override, or redef.

class Foo {
    public open func f() {}
    static func h() {}
}

extend Foo {
    public override func f() {} // error
    public open func g() {} // error
    redef static func h() {} // error
}

Properties

Extensions can add properties to the type being extended. These properties cannot be abstract.

For example:

extend Int64 {
    mut prop i: Int64 {
        get() {
            0
        }
        set(value) {}
    }
}

Modifiers

Property definitions within an extension support the use of private, internal, protected, or public modifiers.

Properties modified by private can be used within the extension and are not visible externally.

Properties modified by protected can be accessed in this module (restricted by export rules). When the type being extended is class, the functions can also be accessed within the subclass of that class.

The accessibility modifier of a property within an extension is internal by default.

// file1 in package p1
package p1

public open class Foo {}

extend Foo {
    private prop v1: Int64 {   // ok
        get() { 0 }
    }
    public prop v2: Int64 {    // ok
        get() { 0 }
    }
    protected prop v3: Int64 { // ok
        get() { 0 }
    }
    prop v4: Int64 {           // visible in the package
        get() { 0 }
    }
}

main() {
    let a = Foo()
    a.v1 // error, can not access private property
    a.v2 // ok
    a.v3 // ok
    a.v4 // ok
}

// file2 in package p2
package p2

import p1.*

class Bar <: Foo {
    func f() {
        v1 // error, can not access private function
        v2 // ok
        v3 // ok
        v4 // error, can not access default function
    }
}

Properties in an extension can be modified using static.

class Foo {}

extend Foo {
    static prop i: Int64 {
        get() { 0 }
    }
}

The property definition in the extension cannot be modified by open, override, or redef.

class Foo {
    open prop v1: Int64 {
        get() { 0 }
    }
    static prop v2: Int64 {
        get() { 0 }
    }
}

extend Foo {
    override prop v1: Int64 { // error
        get() { 0 }
    }
    open prop v3: Int64 { // error
        get() { 0 }
    }
    redef static prop v2: Int64 { // error
        get() { 0 }
    }
}

Generic extensions

When the type being extended is a generic type, there are two syntaxes available for extending its functionalities.

One is the non-generic extension described above. For generic types, a non-generic extension can be applied to specific instantiated generic types.

Any fully instantiated generic type is allowed after extend. The functionalities added for these types can be used only when the types are fully matched.

extend Array<String> {} // ok
extend Array<Int> {} // ok

In the extension, the type parameters of the generic type must comply with the constraints defined for the generic type. Otherwise, a compilation error is reported.

class Foo<T> where T <: ToString {}

extend Foo<Int64> {} // ok

class Bar {}
extend Foo<Bar> {} // error

The other is to introduce a generic extension of a generic parameter after extend. Generic extensions can be used to extend generic types that are not instantiated or partially instantiated.

Generic parameters can be declared after extend, and they must be directly or indirectly used in the extended generic type. The functionalities added for these types can be used only when the types and constraints are fully matched.

extend<T> Array<T> {} // ok

The generic variant introduced by the generic extension must be used in the type being extended. Otherwise, an error is reported, indicating that the generic variant is not used.

extend<T> Array<T> {} // ok
extend<T> Array<Option<T>> {} // ok
extend<T> Array<Int64> {} // error
extend<T> Int64 <: Equatable<T> { // error
    ...
}

Regardless of generic extensions or non-generic extensions, extensions for generic types cannot redefine members or re-implement interfaces.

When a generic variant of a generic extension is directly used in the type parameter of the type being extended, the corresponding generic variant implicitly introduces the generic constraint defined by the type being extended.

class Foo<T> where T <: ToString {}

extend<T> Foo<T> {} // T <: ToString

Generic parameters introduced by generic extension cannot be used for type being extended or interface being implemented. Otherwise, a compilation error is reported.

extend<T> T {} // error
extend<T> Unit <: T {} // error

Additional generic constraints can be used in the generic extension. Members or interfaces added in this way can be used only when instances of this type meet the generic constraints of the extension. Otherwise, an error is reported.

class Foo<T> {
    var item: T
    init(it: T) {
        item = it
    }
}

interface Eq<T> {
    func equals(other: T): Bool
}

extend<T> Foo<T> <: Eq<Foo<T>> where T <: Eq<T> {
    public func equals(other: Foo<T>) {
        item.equals(other.item)
    }
}

class A {}
class B <: Eq<B> {
    public func equals(other: B) { true }
}

main() {
    let a = Foo(A())
    a.equals(a) // error, A has not implement Eq
    let b = Foo(B())
    b.equals(b) // ok, B has implement Eq
}

A generic type cannot re-implement the same interface. For the same generic type, whether fully instantiated or not, re-implementing the same interface is not allowed. Otherwise, a compilation error is reported.

interface I {}
extend Array<String> <: I {} // error, can not repeatedly implement the same interface
extend<T> Array<T> <: I {} // error, can not repeatedly implement the same interface

interface Bar<T> {}
extend Array<String> <: Bar<String> {} // error, can not repeatedly implement the same interface
extend<T> Array<T> <: Bar<T> {} // error, can not repeatedly implement the same interface

For the same generic type, whether fully instantiated or not, defining members with the same type or signature is not allowed. Otherwise, a compilation error is reported.

// case 1
extend Array<String> {
    func f() {} // error, cannot be repeatedly defined
}
extend<T> Array<T> {
    func f() {} // error, cannot be repeatedly defined
}
// case 2
extend Array<String> {
    func g(a: String) {} // error, cannot be repeatedly defined
}
extend<T> Array<T> {
    func g(a: T) {} // error, cannot be repeatedly defined
}
// case 3
extend<T> Array<T> where T <: Int {
    func g(a: T) {} // error, cannot be repeatedly defined
}
extend<V> Array<V> where V <: String {
    func g(a: V) {} // error, cannot be repeatedly defined
}
// case 4
extend Array<Int>  {
    func g(a: Int) {} // ok
}
extend Array<String>  {
    func g(a: String) {} //ok
}

Access and Hiding in Extensions

Instance members of an extension can use this, whose meaning remains consistent with that in the type definition. You can also omit this when accessing members.

However, instance members of an extension cannot use super.

class A {
    var v = 0
}

extend A {
    func f() {
        print(this.v) // ok
        print(v) // ok
    }
}

The extension cannot access the private member of the type being extended. The members modified by other modifiers comply with the visibility principle.

class A {
    private var v1 = 0
    protected var v2 = 0
}

extend A {
    func f() {
        print(v1) // error
        print(v2) // ok
    }
}

Extensions cannot hide any members of the type being extended.

class A {
    func f() {}
}

extend A {
    func f() {} // error
}

Extensions also cannot hide any members of the type being extended added by other extensions.

class A {}

extend A {
    func f() {}
}

extend A {
    func f() {} // error
}

You can extend the same type multiple times within the same package.

In an extension, you can directly use (without any prefix) members that are not modified by private from other extensions of the same type.

class Foo {}

extend Foo { // OK
    private func f() {}
    func g() {}
}

extend Foo { // OK
    func h() {
        g() // OK
        f() // Error
    }
}

When extending generic types, you can use additional generic constraints.

In addition to the preceding accessibility rules, the following constraints must be met to determine whether members in other extensions of the same type can be directly used in the extension of a generic type:

  • If two extensions have the same constraints, they can directly use each other's members.
  • If two extensions have different constraints, and there is an inclusion relationship between the two, the extension with the stricter constraint can directly use members from the extension with the looser constraint, but not the other way around.
  • If two extensions have different constraints and there is no inclusion relationship, then neither extension can directly use members from the other.

For example: Assume that there are two extensions of the same type E<X>, where the constraints on X in extension 1 are stricter than those in extension 2. In this case, extension 1 can directly use members in extension 2, but extension 2 cannot directly use those in extension 1.

// B <: A
class E<X> {}

interface I1 {
    func f1(): Unit
}
interface I2 {
    func f2(): Unit
}

extend<X> E<X> <: I1 where X <: B {  // extension 1
    public func f1(): Unit {
        f2() // OK
    }
}

extend<X> E<X> <: I2 where X <: A   { // extension 2
    public func f2(): Unit {
        f1() // Error
    }
}

Inheritance of Extensions

If the type being extended is class, the members being extended are inherited by its subclass.

open class A {}

extend A {
    func f() {}
}

class B <: A {
    func g() {
        f() // ok
    }
}

main() {
    let x = B()
    x.f() // ok
}

Note that if members are extended in the superclass, due to inheritance rules, the subclass cannot define members with the same name, nor can it override or re-implement them (function overloading is allowed).

open class A {}

extend A {
    func f() {}
    func g() {}
}

class B <: A {
    func f() {} // error
    override func g() {} // error
}

If the superinterface and subinterface are extended for the same type in the same package, the compiler checks the extension of the superinterface and then that of the subinterface.

class Foo {}

interface I1 {
   func f() {}
}
interface I2 <: I1 {
   func g() {}
}

extend Foo <: I1 {} // first check
extend Foo <: I2 {} // second check

Importing and Exporting Extensions

No modifier is allowed before extend. Extensions can be imported or exported only with the types or interfaces being extended.

Exporting Direct Extensions

When a direct extension and the type being extended are defined in the same package, if the extended type is exported, the extension is also exported; otherwise, the extension is not exported.

package pkg1

public class Foo {}

extend Foo {
    public func f() {}
}

///////

package pkg2
import pkg1.*

main() {
    let a = Foo()
    a.f() // ok
}

When a direct extension and the type being extended are not defined in the same package, the extension is never exported and can only be used within the current package.

package pkg1
public class Foo {}

///////

package pkg2
import pkg1.*

extend Foo {
    public func f() {}
}

func g() {
    let a = Foo()
    a.f() // ok
}

///////

package pkg3
import pkg1.*
import pkg2.*

main() {
    let a = Foo()
    a.f() // error
}

Exporting Interface Extensions

When an interface extension and the type being extended are in the same package, only the externally visible interface can be extended for the externally visible type.

package pkg1
public class Foo {}

interface I1 {
    func f(): Unit
}

extend Foo <: I1 { // error
    public func f(): Unit {}
}

public interface I2 {
    func g(): Unit
}

extend Foo <: I2 { // ok
    public func g(): Unit {}
}

///////

package pkg2
import pkg1.*

main() {
    let a = Foo()
    a.g() // ok
}

When an interface extension and the type being extended are not in the same package, the access level of the extension is the same as that of the corresponding interface. If multiple interfaces are extended, the extension's access level is public only when all interfaces are public.

package pkg1
public class Foo {}

public class Bar {}

///////

package pkg2

import pkg1.*

interface I1 {
    func f(): Unit
}

public interface I2 {
    func g(): Unit
}

extend Foo <: I1 & I2 { // not external
    public func f(): Unit {}
    public func g(): Unit {}
}

extend Bar <: I2 { // external
    public func g(): Unit {}
}

Importing Extensions

An extension is imported together with the type being extended and the interface it implements. The imported extension cannot be specified.

package pkg1
public class Foo {}

extend Foo {
    public func f() {}
}

///////

package pkg2
import pkg1.Foo // import Foo

main() {
    let a = Foo()
    a.f() // ok
}

In particular, because extensions cannot hide any members of the type being extended, an error is reported when the imported extension is redefined.

package pkg1
public class Foo {}

///////

package pkg2
import pkg1.Foo

public interface I1 {
    func f(): Unit {}
}

extend Foo <: I1 {} // ok, external

///////

package pkg3
import pkg1.Foo

public interface I2 {
    func f(): Unit {}
}

extend Foo <: I2 {} // ok, external

///////

package pkg4
import pkg1.Foo
import pkg2.I1 // error
import pkg3.I2 // error