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:
- Extensions cannot add fields.
- Extensions cannot add abstract members.
- Extensions cannot add
open
members. - Extensions cannot use
override
orredef
on existing members. - 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:
- The interface extension and the type definition are in the same package.
- 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