Access Rules

Modifiers in Extensions

Extensions themselves cannot have any modifiers.

In the following example, public appears in front of a direct extension of A. As a result, an error is reported during compilation.

public class A {}

public extend A {}  // Error, expected no modifier before extend

Extension members can use the following modifiers: static, public, protected, internal, private, and mut.

  • Members modified by private can be used only within the extension and are not visible externally.
  • Members modified by internal can be used in the current package, its subpackages, and subpackages of subpackages. This is the default behavior.
  • Members modified by protected can be accessed within the module (constrained by export rules). If the extended type is class, these members can also be accessed in the subclass definition of the class.
  • Members modified by static can be accessed only via the type name, not via an instance object.
  • Extensions of a struct type can define mut member functions.
package p1

public open class A {}

extend A {
    public func f1() {}
    protected func f2() {}
    private func f3() {}
    static func f4() {}
}

main() {
    A.f4()
    var a = A()
    a.f1()
    a.f2()
}

An extension member definition cannot be modified with open, override, or redef. In other words, extension member functions and properties can only be added to a type, not override or redefine the inherited ones, and cannot themselves be overridden or redefined.

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
}

No Orphan Extensions Rule

Implementing, by extension, an interface defined in some other package for a type defined in yet another package can lead to confusion.

To prevent a type from inadvertently implementing an inappropriate interface, Cangjie does not allow defining orphan extensions. An orphan extension is one that is neither defined in the same package as the interface (including all interfaces in its inheritance chain) nor defined in the same package as the type being extended.

As shown in the following code, we cannot implement Bar from package b for Foo in package a while in package c:

We can only implement Bar for Foo in package a or package b.

// package a
public class Foo {}

// package b
public interface Bar {}

// package c
import a.Foo
import b.Bar

extend Foo <: Bar {} // Error

Access and Hiding in Extensions

Instance members of an extension can use this, the semantics of which remains consistent with 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
    }
}

extend Int64 {
    func times(x: Int64): Int64 {
        this * x    // OK: The type of 'this' is Int64
    }
}

main() {
    println(2.times(3))    // OK
}

If you compile and run the above program, the following will be printed to the standard output:

6

Extensions cannot access members modified by private in the extended type.

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
}

In one package, a type can be extended for multiple times, and functions not modified by private in other extensions of the extended type can be directly called in the extension.

class Foo {}

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

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

However, extensions cannot hide any members added by other extensions:

class A {}

extend A {
    func f() {}
}

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

When extending generic types, you can use additional generic constraints so as to make the extension available only for particular combinations of type arguments of the type. The visibility rules between any two extensions of a generic type are as follows:

  • If two extensions have the same constraints, they are mutually visible, meaning their non-private functions and properties can be directly used in each other's contexts.
  • If the constraints are different but have an inclusion relationship, the extension with the looser constraints is visible to the one with the stricter constraints, but not vice versa.
  • If the constraints are different enough for there to be no inclusion relationship, the two extensions are invisible to each other.

Example: Suppose 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, functions and properties in extension 1 are not visible to extension 2, but those in extension 2 are visible to extension 1.

open class A {}
class 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
    }
}

Importing and Exporting Extensions

Extensions may be imported and exported only along with the types they extend. Described in this section are the additional restrictions that preserve encapsulation.

A direct extension is exported only if the extension itself and the type it extends are defined in the same package, and the extended type, as well as types used as generic constraints anywhere in the extension, if any, are modified with either public or protected.

Other direct extensions cannot be exported and can only be used within the package in which they are defined.

As shown in the following code, Foo is a type modified by public, and f and Foo are in the same package. Therefore, f is exported together with Foo. However, g and Foo are not in the same package. Therefore, g is not exported.

// package a

public class Foo {}

extend Foo {
    public func f() {}
}

// package b
import a.*

extend Foo {
    public func g() {}
}

// package c
import a.*
import b.*

main() {
    let a = Foo()
    a.f() // OK
    a.g() // Error
}

For interface extensions, there are two cases:

  1. If the interface extension and the type being extended are in the same package, the extension will be exported together with the type regardless of the access modifiers of the interface(s) implemented by the extension, and its members will be accessible without importing the interface(s).
  2. If the interface extension is in a different package from the type being extended, the extension will be exported only if the interface type(s) and the types used as generic constraints, if any, are visible outisde the package.

As shown in the following code, both Foo and I are modified by public. Therefore, the extension of Foo can be exported.

// package a

public class Foo {}

public interface I {
    func g(): Unit
}

extend Foo <: I {
    public func g(): Unit {}
}

// package b
import a.*

main() {
    let a: I = Foo()
    a.g()
}

Similar to exporting extensions, importing them does not require an explicit use of import. You only need to import the types being extended and, possibly, interfaces to import all accessible extensions automatically.

As shown in the following code, in package b you only need to import Foo itself to use the function f defined in an extension of Foo.

For interface extensions, you need to import both the types being extended and the interfaces of extensions at the same time. Therefore, in package c, you must import both Foo and I to use the function g in the respective extension.

// package a
public class Foo {}
extend Foo {
    public func f() {}
}

// package b
import a.Foo

public interface I {
    func g(): Unit
}
extend Foo <: I {
    public func g() {
        this.f() // OK
    }
}

// package c
import a.Foo
import b.I

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