Packages and Modules
In Cangjie, a program is built as a package, which is the minimum unit for compilation. A package can define subpackages to form a tree structure. A package without a superpackage is called a root package. The entire tree consisting of the root package and its subpackages (including subpackages of subpackages) is called a module. The name of the module is the same as that of the root package. A module is the minimum release unit of Cangjie.
A package consists of one or more source code files. The source code files of the same package must be stored in the same directory, and the source code files in the same directory must belong to the same package. The directory of a subpackage is a subdirectory of its superpackage's directory.
Packages
Package Declaration
The package declaration starts with the keyword package
, followed by the names of all packages in the path from the root package to the current package, and the names are separated by .
(the path itself is not the package name).
The syntax for package declaration is as follows:
packageHeader : packageModifier? (MACRO NL*)? PACKAGE NL* fullPackageName end+ ; fullPackageName : (packageNameIdentifier NL* DOT NL*)* packageNameIdentifier ; packageNameIdentifier : Ident | contextIdent ; packageModifier : PUBLIC | PROTECTED | INTERNAL ;
Each package has a package name, which is a unique identifier of the package.
Except for the root package, the package name must match the name of the directory it resides in.
The package declaration must be in the first line of the file (excluding comments and whitespace characters). Each file can have only one package
declaration. In particular, files in the root package can omit the package
declaration. For files without a package
declaration, default
is used as the package name.
// ./src/main.cj
// Leaving package declaration out is equivalent to 'package default'
main() {
println("Hello World")
}
package
can be modified by internal
, protected
, or public
. The default modifier of package
is public
. (Default modifiers can be explicitly specified, and are used when no access modifiers are specified) The package
declaration in different files of the same package must use the same access modifier. In particular, the root package cannot be modified by internal
or protected
.
internal
: Members are visible only in the current package, its superpackage (only the upper-level superpackage), and its subpackages (including subpackages of subpackages). The package or its members can be imported to the superpackage (only the upper-level superpackage) and the subpackages (including subpackages of subpackages) of the current package.protected
: Members are visible only in the current module. Other packages in the same module can import the package or its members, but packages from other modules cannot access them.public
: Members are visible both inside and outside the module. Other packages can import this package or its members.
Package Members
Package members are classes, interfaces, structs, enums, type aliases, global variables, extensions, and functions declared at the top level. For details about the content included in the top-level declaration, see the corresponding section.
The superpackages and subpackages of the current package are not its members, and accessing them requires the package import mechanism. The names of the packages that are not imported are not in the top-level scope.
In the following example, package a.b
is a subpackage of package a
.
// src/a.cj
package a
let a = 0 // ok
// src/b/b.cj
package a.b
let a = 0 // ok
let b = 0 // ok
In the following example, package a.b
is a subpackage of package a
.
// src/a.cj
package a
let u = 0 // ok
let _ = b.x // error: undeclared identifier 'b'
let _ = a.u // error: undeclared identifier 'a'
let _ = a.b.x // error: undeclared identifier 'a'
// src/b/b.cj
package a.b
let x = 1 // ok
let _ = a.u // error: undeclared identifier 'a'
let _ = a.b.x // error: undeclared identifier 'a'
let _ = b.x // error: undeclared identifier 'b'
In particular, a subpackage cannot have the same name as a member in the current package to ensure that the name in the access path is unique.
Access Modifiers
In Cangjie, access modifiers can be used to protect access to elements such as types, variables, and functions. Cangjie has four access modifiers.
private
internal
protected
public
Modifying Top-level Members
The semantics of different access modifiers when modifying top-level members are as follows:
private
: Members are visible only in the current file and cannot be accessed from other files.internal
: Members are visible only in the current package and its subpackages (including subpackages of subpackages). These members can be accessed from the same package without being imported or from subpackages (including subpackages of subpackages) of the package through imports.protected
: Members are visible only in the current module. Files in the same package can access these members without imports. Packages in the same module can access them through imports, but packages in different modules cannot access them.public
: Members are visible both inside and outside the module. Files in the same package can access these members without imports, and other packages can access them through imports.
File | Package & Sub-Packages | Module | All Packages | |
---|---|---|---|---|
private | Y | N | N | N |
internal | Y | Y | N | N |
protected | Y | Y | Y | N |
public | Y | Y | Y | Y |
The access modifiers and default modifiers supported by different top-level declarations are specified as follows:
package
: supportsinternal
,protected
, andpublic
, withpublic
as the default modifier. In particular, whenpackage
is modified byinternal
, it is also visible to its superpackage (only the upper-level superpackage).import
: supports all access modifiers, withprivate
as the default modifier.- Other top-level declarations support all access modifiers, with
internal
as the default modifier.
Modifying Non-top-level Members
The semantics of different access modifiers when modifying non-top-level members are as follows:
private
: Members are visible only to the current type or extended definition.internal
: Members are visible only in the current package and its subpackages (including subpackages of subpackages).protected
: Members are visible in the current module and subclasses of the current class.public
: Members are visible both inside and outside the module.
Type/Extend | Package & Sub-Packages | Module & Sub-Classes | All Packages | |
---|---|---|---|---|
private | Y | N | N | N |
internal | Y | Y | N | N |
protected | Y | Y | Y | N |
public | Y | Y | Y | Y |
The access modifier of a type member can be different from that of the type itself. The default modifier for a type member except interface is internal
. (Default modifiers can be explicitly specified, and are used when no access modifiers are specified) The member functions and properties in interfaces cannot write access modifiers. Their access level is public
.
Validity Check of Access Modifiers
The hierarchy of access levels in Cangjie is as follows: public > protected > internal > private
.
Type access levels are detailed as follows:
- The access level of a non-general type is determined by the access modifier specified in its type declaration.
- The access level of an instantiated generic type is the same as the lowest access level between the generic type and an argument of the generic type.
The level of the access modifier of a declaration cannot be higher than that of the class used in the declaration. Specifically:
- The access level of a variable or property declaration cannot be higher than that of its type.
- The access level of a function declaration cannot be higher than that of the parameter type, return value type, and type upper bound in the
where
constraint. - The access level of the type alias cannot be higher than that of the original type.
- The access level of a type declaration cannot be higher than that of the type upper bound in the
where
constraint. - The access level of a subpackage cannot be higher than that of its superpackage.
- The access modifier of
import
cannot have a higher access level than its import declaration.
private open class A {}
protected let a = A() // error: 'protected' declaration 'a' uses 'private' type 'A'
let (a, b) = (A(), 1) // error: 'internal' declaration 'a' uses 'private' type 'A'
func f(_: A) {} // error: 'internal' declaration 'f' uses 'private' type 'A'
func f() { A() } // error: 'internal' declaration 'f' uses 'private' type 'A'
func f<T>() where T <: A {} // error: 'internal' declaration 'f' uses 'private' type 'A'
public type X = A // error: 'public' declaration 'X' uses 'private' type 'A'
public type ArrayA = Array<A> // error: 'public' declaration 'ArrayA' uses 'private' type 'A'
protected struct S<T> where T <: A {} // error: 'protected' declaration 'S' uses 'private' type 'A'
// src/a.cj
public package a
// src/a/b/b.cj
protected package a.b // ok
// src/a/b/c/c.cj
public package a.b.c // error
In particular, the subclass access level and superclass access level during class inheritance, and the subtype access level and superinterface access level during type implementation or interface inheritance are not restricted by the preceding rules.
private open class A {}
public enum E { U | V }
interface I {}
public class C <: A {} // ok
public interface J <: I {} // ok
extend E <: I {} // ok
Package Import
Import is a mechanism used to introduce other packages or members of other packages to the current Cangjie program.
If the source code does not contain the import
declaration, the current file can access only the members in the current package and the members imported by the compiler by default. The import
declaration allows the compiler to find the required external name when compiling the Cangjie file.
The import
statement must be placed after the package declaration and before other declarations or definitions in the file.
The syntax related to import
is as follows:
importList : importModifier? NL* IMPORT NL* importContent end+ ; importSingle : (packageNameIdentifier NL* DOT NL*)* (identifier | packageNameIdentifier) ; importSpecified : (identifier '.')+ identifier ; importAlias : importSingle NL* AS NL* identifier ; importAll : (packageNameIdentifier NL* DOT NL*)+ MUL ; importMulti : (packageNameIdentifier NL* DOT NL*)* LCURL NL* (importSingle | importAlias | importAll) NL* (COMMA NL* (importSingle | importAlias | importAll))* NL* COMMA? NL* RCURL ;
The import
syntax has the following forms:
- Single import
- Alias import
- Full import
- Batch import
You can use import
to import one or more packages or members of other packages. You can also use the as
syntax to define an alias for the imported name. If the imported name is a package, you can use it to access members of the package (the subpackage is not a member of the package), but the package name itself cannot be used as an expression.
package a
public let x = 0
package demo
import a
main() {
println(a.x) // ok, prints 0
}
Cyclic dependencies between any two packages are not allowed, even within the same module. For any two packages p1
and p2
, if p2
or members of p2
are imported to p1
, p1
and p2
have a dependency relationship, and p1
depends on p2
. The dependency is transitive. If p1
depends on p2
and p2
depends on p3
, p1
depends on p3
. Cyclic dependency of packages refers to the situation where packages depend on each other.
package p.a
import p.b // error
pacakge p.b
import p.a // error
Do not use import
to import the current package or members in the current package.
package a
import a // error
import a.x // error
public let x = 0
The scope level of the imported member is lower than that of the member declared in the current package. An imported non-function member is shadowed by a member with the same name in the current package. If an imported function member can constitute overloading with a function with the same name in the current package, the function resolution is performed based on the rules of [Generic Function Overloading] and [Function Overloading] during function call. If they do not constitute overloading, the function member is shadowed.
package a
public let x = 0
public func f() {}
import a.x // warning: imported 'x' is shadowed
import a.f
let x = 1
func f(x: Int64) { x }
let _ = f() // ok, found 'a.f'
let _ = f(1) // ok, found 'f' defined in this package
Single Import
The single import syntax is used to import a single member. The target member must be visible to the current package. The imported member name is used as the name that can be accessed in the current scope.
The last name of the path in the import
syntax indicates the specified member. The name can be a top-level variable, function, type, or package.
The following is an example of importing a top-level variable, function, type, and package. There are two packages: a
and b
. Import the members of the a
package to the b
package.
package a
public let x = 0
public func f() { 0 }
public class A {}
import a.x
import a.f
import a.A
private func g(_: A) { x + f() } // ok
In the following example, c
is a subpackage of a
.
package a.c
public let y = 1
import a
private func g(_: a.A) { a.x + a.f() } // ok
privage func h(_: A) { // error: undeclared identifier 'A'
x + f() // error: undeclared identifier 'x' and 'f'
}
let _ = a.c.y // error: 'c' is not a member of 'a'
let _ = a // error: undeclared identifier 'a'
import a.c
let _ = c.y // ok
If a member imported through single import is shadowed by a member in the current package, the compiler displays an alarm indicating that the import is useless.
import a.x // warning: imported 'x' is shadowed
import a.f // warning: imported 'f' is shadowed
func f() { 1 }
let x = 1
let _ = f() // ok, call 'f' defined in this package, value is 1
Alias Import
You can use the as
syntax to rename imported members during alias import. The content imported by alias is imported to the scope in the current package as an alias, not the original name. (However, the original name and alias can be imported separately.) The imported content can be a package or a member of a package.
package a
public let x = 0
public let y = 1
public func f() { 0 }
import a as pkgA
import a.x as x1
import a.x as x2 // ok
let _ = x // error: undeclared identifier 'x'
let _ = a.x // error: undeclared identifier 'a'
let _ = x1 // ok
let _ = x2 // ok
let _ = pkgA.x // ok
let _ = pkgA.x1 // error: 'x1' is not a member of 'pkgA'
Full Import
The *
syntax is used to import all top-level members (excluding subpackages) that are visible to the current package from other packages.
Example:
package a
public let x = 0
public func f() { 0 }
public class A {}
import a.*
private func g(_: A) { x + f() } // ok
Unlike single import, the compiler does not display an alarm when an imported member is shadowed by a member in the current package during full import.
import a.*
let x = 1
func f() { x } // ok, 'x' defined in this package
let _ = f() // ok, call 'f' defined in this package, value is 1
If an imported member is not shadowed by a member in the current package, but the names of multiple imported members are the same, the compiler does not display an alarm. However, if the imported members with the same name do not constitute overloading, the name is unavailable in the current package. When this name is used, the compiler reports an error because it cannot find a unique name.
package b
public let x = 1
public func f(x: Int64) { x }
import a.*
import b.*
let _ = x // error: ambiguous 'x'
If the imported members with the same name constitute function overloading, the function resolution is performed based on the rules of [Generic Function Overloading] and [Function Overloading].
import a.*
import b.*
func f(b: Bool) { b }
let _ = f() // ok, call 'a.f'
let _ = f(1) // ok, call 'b.f'
let _ = f(true) // ok, call 'f' defined in this package
A full import with access modifiers does not import declarations with lower access level.
package a
protected import a.b.*
let _ = x // ok
let _ = y // ok
let _ = z // error: undeclared identifier 'z'
package a.b
public let x = 0
protected let y = 1
internal let z = 2
Batch Import
The {}
syntax is used for importing multiple members in a import
declaration during batch import. It is usually used to omit duplicate package path prefixes.
The curly braces ({}
) imported in batches support single import, alias import, and full import, but do not support nested batch import.
import std.{
time,
fs as fileSystem,
io.*,
collection.{HashMap, HashSet} // syntax error
}
The prefix of {}
can be left empty.
import {
std.time,
std.fs as fileSystem,
std.io.*,
}
The batch import syntax is equivalent to the syntax of using independent import
syntax for multiple times.
import std.{
os.process,
time,
io.*,
fs as fileSystem
}
The preceding is equivalent to:
import std.os.process
import std.time
import std.io.*
import std.fs as fileSystem
Import Name Conflict Check
If the names of multiple single imports are the same (including repeated imports) and do not constitute function overloading, and the name is not shadowed in the package, the compiler displays a name conflict alarm, indicating that the name is unavailable in the package. When this name is used, the compiler reports an error because it cannot find a unique name. If the name is shadowed by a member in the current package, the compiler displays an alarm indicating that the import is useless.
package b
public let x = 1
public func f(x: Int64) { x }
package c
public let f = 0
import a.x // warning: imported 'x' is shadowed
import a.x // warning: imported 'x' is shadowed
import b.x // warning: imported 'x' is shadowed
let x = 0
let y = x // y = 0
import a.x
import a.x // warning: 'x' has been imported
import b.x // warning: 'x' has been imported
let _ = x // error: ambiguous 'x'
If the imported members with the same name constitute function overloading or an imported member can constitute overloading with a function with the same name in the current package, the function resolution is performed based on the rules of [Generic Function Overloading] and [Function Overloading].
import a.f
import b.f
func f(b: Bool) { b }
let _ = f() // ok, call 'a.f'
let _ = f(1) // ok, call 'b.f'
let _ = f(true) // ok, call 'f' defined in this package
The handling rules for multiple alias imports of the same name, or for alias imports that conflict with a name defined in the current package, are the same as those for single import.
import a.x as x1 // warning: imported 'x1' is shadowed
let x1 = 10
let _ = x1 // ok, 'x1' defined in this package
package b
public let x = 1
public func f(x: Int64) { x }
import a.x as x1
import a.x as x1 // warning: 'x1' has been imported
import b.x as x1 // warning: 'x1' has been imported
let _ = x1 // error: ambiguous 'x1'
import a.f as g
import b.f as g
func g(b: Bool) { b }
let _ = g() // ok, call 'a.f'
let _ = g(1) // ok, call 'b.f'
let _ = g(true) // ok, call 'g' defined in this package
If one of the conflicting imports comes from a full import, the compiler does not display an alarm, but the conflicting declarations are unavailable.
Name conflict check is performed based on the equivalent single imports, alias imports, and multiple imports.
Access Modifiers of import
import
can be modified by the access modifiers private
, internal
, protected
, and public
. The import
modified by public
, protected
, or internal
can re-export the imported members (unless the imported members are unavailable in the package due to name conflicts or being shadowed). Other packages can import these re-exported objects through import
according to the access rules of [Access Modifiers]. Specifically:
private import
: The imported content can be accessed only in the current file.private
is the default modifier ofimport
. Animport
without an access modifier is equivalent to aprivate import
.internal import
: The imported content can be accessed in the current package and its subpackages (including subpackages of subpackages). Explicitimport
is required for non-current packet access.protected import
: The imported content can be accessed in the current module. Explicitimport
is required for non-current packet access.public import
: The imported content can be accessed externally. Explicitimport
is required for non-current packet access.
In the following example, b
is a subpackage of a
, and a
re-exports the f
function that is defined in b
through public import
:
package a
public let x = 0
public import a.b.f
internal package a.b
public func f() { 0 }
import a.f // ok
let _ = f() // ok
//// case 1
package demo
public import std.time.Duration // warning: imported 'Duration' is shadowed
struct Duration {}
//// case 2
// ./a.cj
package demo
public import std.time.Duration
// ./b.cj
package demo
func f() {
let a: Duration = Duration.second // ok, access the re-exported 'Duration'
}
//// case 3
// ./a/a.cj
package demo.a
public let x = 0
// ./b/b.cj
package demo.b
public import demo.a.* // warning: imported 'x' is shadowed, will not re-export 'demo.a.x'
var x = 0
//// case 4
// ./a/a.cj
package demo.a
public let x = 0
// ./b/b.cj
package demo.b
public let x = 0
// ./c/c.cj
package demo.c
public import demo.a.* // warning, because there are duplicate names, will not re-export 'demo.a.x'
public import demo.b.* // warning, because there are duplicate names, will not re-export 'demo.b.x'
It is worth noting that packages cannot be re-exported. If a package is imported by an import
, the import
cannot be modified by public
, protected
, or internal
.
public import a.b // error: cannot re-export package
Program Entry
The syntax of the program entry is as follows:
mainDefinition
: 'main' functionParameters (':' type)? block
;
The entry of the Cangjie program is main
, which is not a function. Each package can have at most one program entry main
which can be defined only at the top level. When a package is compiled into a library, main
is ignored by the compiler and is not recognized as a program entry.
If the package is compiled by generating an executable file (specified by the user), the compiler searches for main
at the top level of the package. If no main
is found or multiple main
entries are found, the compiler reports an error. If only one main
is found, the compiler checks its parameters and return value types.
A main
may have no parameters or have parameters of the Array<String>
type, and the return value type can be Unit
or integer.