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
privatecan be used only within the extension and are not visible externally. - Members modified by
internalcan be used in the current package, its subpackages, and subpackages of subpackages. This is the default behavior. - Members modified by
protectedcan 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
staticcan be accessed only via the type name, not via an instance object. - Extensions of a
structtype can definemutmember 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
}
Extensions must not hide any members added by other extensions.
class A {}
extend 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-
privatefunctions 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 can also be imported and exported, but they cannot be modified by visibility modifiers. There are a set of special rules for exporting extensions.
If a direct extension and the type being extended are in the same package, whether the extension will be exported is determined by the access modifiers of both the type being extended and generic constraints (if any). If all generic constraints belong to public types (for details about modifiers and export rules, see Visibility of Top-Level Declarations), the extension will be exported. If the extension and the type being extended are not in the same package, the extension will not be exported.
In the following code, Foo is public. The extension of the f1 function is not exported, because none of its generic constraints is exported. In contrast, the extensions of the f2 and f3 functions are exported, because all their generic constraints are exported. The extension of the f4 function is not exported, because it has multiple generic constraints and I1 subject to these constraints is not exported. The extension of the f5 function also has multiple generic constraints but it is exported because all its generic constraints are public.
// package a.b
package a.b
private interface I1 {}
internal interface I2 {}
protected interface I3 {}
extend Int64 <: I1 & I2 & I3 {}
public class Foo<T> {}
// The extension will not be exported
extend<T> Foo<T> where T <: I1 {
public func f1() {}
}
// The extension will be exported, and only packages that import both Foo and I2 will be able to access it.
extend<T> Foo<T> where T <: I2 {
public func f2() {}
}
// The extension will be exported, and only packages that import both Foo and I3 will be able to access it.
extend<T> Foo<T> where T <: I3 {
public func f3() {}
}
// The extension will not be exported. The I1 with the lowest access level determines the export.
extend<T> Foo<T> where T <: I1 & I2 & I3 {
public func f4() {}
}
// The extension is exported. Only the package that imports Foo, I2, and I3 can access the extension.
extend<T> Foo<T> where T <: I2 & I3 {
public func f5() {}
}
// package a.c
package a.c
import a.b.*
main() {
Foo<Int64>().f1() // Cannot access.
Foo<Int64>().f2() // Cannot access. Visible only for sub-pkg.
Foo<Int64>().f3() // Ok.
Foo<Int64>().f4() // Cannot access.
Foo<Int64>().f5() // Cannot access. Visible only for sub-pkg.
}
// package a.b.d
package a.b.d
import a.b.*
main() {
Foo<Int64>().f1() // Cannot access.
Foo<Int64>().f2() // Ok.
Foo<Int64>().f3() // Ok.
Foo<Int64>().f4() // Cannot access.
Foo<Int64>().f5() // Ok.
}
For interface extensions, there are two cases:
- If an interface extension and the type being extended are in the same
package, the extension will be exported together with the type and generic constraints (if any), regardless of the access level of the interface type. Even without importing this interface type, the members of the extension can still be accessed outside the package. - If an interface extension and the type being extended are not in the same
package, whether the interface extension will be exported is determined by the interface type and the lowest access level of the types used in generic constraints (if any). Otherpackages must import the type being extended, the corresponding interface, and the types used in constraints (if any), so that the extended members within the interface can be accessed.
As shown in the following code, in package a, although the interface access modifier is private, the extension of Foo is still exported:
// package a
package a
private interface I0 {}
public class Foo<T> {}
// The extension is exported.
extend<T> Foo<T> <: I0 {}
When Foo is extended in another package, whether the extension will be exported is determined by the access modifiers of its implementation interfaces and generic constraints. The extension will be exported if at least one of its implementation interfaces is public and all its generic constraints are exportable.
// package b
package b
import a.Foo
private interface I1 {}
internal interface I2 {}
protected interface I3 {}
public interface I4 {}
// The extension will not be exported because I1 is not visible outside the file.
extend<T> Foo<T> <: I1 {}
// The extension is exported.
extend<T> Foo<T> <: I2 {}
// The extension is exported.
extend<T> Foo<T> <: I3 {}
// The extension is exported
extend<T> Foo<T> <: I1 & I2 & I3 {}
// The extension will not be exported. The I1 with the lowest access level determines the export.
extend<T> Foo<T> <: I4 where T <: I1 & I2 & I3 {}
// The extension is exported.
extend<T> Foo<T> <: I4 where T <: I2 & I3 {}
// The extension is exported.
extend<T> Foo<T> <: I4 & I3 where T <: I2 {}
Specifically, only the members within the interface will be exported along with the interface extension.
// package a
package a
public class Foo {}
// package b
package b
import a.Foo
public interface I1 {
func f1(): Unit
}
public interface I2 {
func f2(): Unit
}
extend Foo <: I1 & I2 {
public func f1(): Unit {}
public func f2(): Unit {}
public func f3(): Unit {} // f3 will not be exported
}
// package c
package c
import a.Foo
import b.I1
main() {
let x: Foo = Foo()
x.f1() // OK, because f1 is a member of I1.
x.f2() // error, I2 is not imported
x.f3() // error, f3 not found
}
Similar to exporting extensions, importing them does not require an explicit use of import. To import all accessible extensions, you only need to import the types being extended, interfaces, and generic constraints.
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.
To use interface extensions, you also need to import the types being extended, interfaces for extensions, and generic constraints (if any).Therefore, in package c, you must import both Foo and I to use the function g in the respective extension.
// package a
package a
public class Foo {}
extend Foo {
public func f() {}
}
// package b
package b
import a.Foo
public interface I {
func g(): Unit
}
extend Foo <: I {
public func g() {
this.f() // OK
}
}
// package c
package c
import a.Foo
import b.I
func test() {
let a = Foo()
a.f() // OK
a.g() // OK
}