Package Import

Using the import Statement to Import Declarations or Definitions from Other Packages

In Cangjie, you can use the import fullPackageName.itemName syntax to import a top-level declaration or definition from another package into a source file. That imported package member may then be used by its name throughout the file, except for scopes where its name is shadowed by another declaration or definition, if any. fullPackageName is the full path package name, and itemName is the name of the imported package member. Import declarations must be placed after the package declaration and before other declarations or definitions in the source file. For example:

package a
import std.math.*
import package1.foo
import {package1.foo, package2.bar}

If multiple itemName to be imported belong to the same fullPackageName, you can use the import fullPackageName.{itemName[, itemName]*} syntax. For example:

import package1.{foo, bar, fuzz}

This is equivalent to:

import package1.foo
import package1.bar
import package1.fuzz

In addition to importing a specific top-level declaration or definition using the import fullPackagename.itemName syntax, you can also use the import packageName.* syntax to import all visible top-level declarations and definitions of the packageName package. For example:

import package1.*
import {package1.*, package2.*}

Note:

  • import can be modified by an access modifier private, internal, protected, or public (see Re-exporting an Imported Name below). An import without an access modifier is equivalent to a private import.
  • The scope level of imported members is lower than that of members declared in the current package.
  • If the module name or package name of an exported package is tampered with, causing it to differ from the name specified at the time of export, an error will occur during the import.
  • Only top-level declarations and definitions visible to the current file can be imported. Importing invisible declarations or definitions will result in an error at the import location.
  • As there is no need to import the declarations and definitions from the package to which the current source file belongs, the compiler treats such import as an error.
  • import is not allowed for packages with cyclic dependency. If a cyclic dependency exists between packages, the compiler reports an error.

The following is an example:

// pkga/a.cj
package pkga    // Error, packages pkga pkgb are in circular dependencies.
import pkgb.*

class C {}
public struct R {}

// pkgb/b.cj
package pkgb

import pkga.*

// pkgc/c1.cj
package pkgc

import pkga.C // Error, 'C' is not accessible in package 'pkga'.
import pkga.R // OK, R is an external top-level declaration of package pkga.
import pkgc.f1 // Error, package 'pkgc' should not import itself.

public func f1() {}

// pkgc/c2.cj
package pkgc

func f2() {
    /* OK, the imported declaration is visible to all source files of the same package
     * and accessing import declaration by its name is supported.
     */
    R()

    // OK, accessing imported declaration by fully qualified name is supported.
    pkga.R()

    // OK, the declaration of current package can be accessed directly.
    f1()

    // OK, accessing declaration of current package by fully qualified name is supported.
    pkgc.f1()
}

In Cangjie, an imported declaration or definition that has the same name as a top-level declaration or definition in the current package will be shadowed unless it constitutes an overloaded function. If it does constitute an overloaded function, function resolution during a function call follows the normal rules for overloaded functions.

// pkga/a.cj
package pkga

public struct R {}            // R1
public func f(a: Int32) {}    // f1
public func f(a: Bool) {} // f2

// pkgb/b.cj
package pkgb
import pkga.*

func f(a: Int32) {}         // f3
struct R {}                 // R2

func bar() {
    R()     // OK, R2 shadows R1.
    f(1)    // OK, invoke f3 in current package.
    f(true) // OK, invoke f2 in the imported package
}

Importing the core Package in Implicit Mode

Types such as String and Range can be used directly not because they are built into the language, but because the compiler automatically imports all public declarations from the core package into all source files in implicit mode.

Using "import as" to Rename the Imported File

Namespaces of different packages are separate, so it is possible to have top-level declarations with the same name in different packages. When importing multiple top-level declarations with the same name from different packages, you can use import packageName.name as newName to rename all but one of them to avoid conflicts. (Of course, you can also use import as for renaming even if there is no name conflict.) The rules for import as are as follows:

  • After you use import as to rename an imported entity, the current package can use only the new name.

  • If the new names conflict with names of members of the current package, and declarations corresponding to these names are functions, they are involved in function overloading; otherwise, a redefinition error occurs.

  • Packages can be renamed in import pkg as newPkgName format to solve the name conflicts of packages with the same name in different modules.

    // a.cj
    package p1
    public func f1() {}
    
    // d.cj
    package p2
    public func f3() {}
    
    // b.cj
    package p1
    public func f2() {}
    
    // c.cj
    package pkgc
    public func f1() {}
    
    // main.cj
    import p1 as A
    import p1 as B
    import p2.f3 as f  // OK
    import pkgc.f1 as a
    import pkgc.f1 as b // OK
    
    func f(a: Int32) {}
    
    main() {
        A.f1()  // OK, package name conflict is resolved by renaming package name.
        B.f2()  // OK, package name conflict is resolved by renaming package name.
        p1.f1() // Error, the original package name cannot be used.
        a()     // Ok.
        b()     // Ok.
        pkgc.f1()    // Error, the original name cannot be used.
    }
    
  • If conflicting imported packages are not renamed, no error will occur in the import statement. However, an error will be reported during usage because the imported names are not unique. In this case, you can use import as to define an alias or use import fullPackageName to import a package as the namespace.

    // a.cj
    package p1
    public class C {}
    
    // b.cj
    package p2
    public class C {}
    
    // main1.cj
    package pkga
    import p1.C
    import p2.C
    
    main() {
        let _ = C() // Error
    }
    
    // main2.cj
    package pkgb
    import p1.C as C1
    import p2.C as C2
    
    main() {
        let _ = C1() // Ok
        let _ = C2() // Ok
    }
    
    // main3.cj
    package pkgc
    import p1
    import p2
    
    main() {
        let _ = p1.C() // Ok
        let _ = p2.C() // Ok
    }
    

Re-exporting an Imported Name

During the development of a large-scale project with various functionalities, the following scenario is common: Package p2 uses a large number of declarations imported from package p1. For a package p3 to import package p2 and use its functions, the declarations from package p1 must also be visible to package p3. If p3 is required to import the declarations of p1 that are used in p2, the process becomes cumbersome. Therefore, it is desirable to simultaneously import package p2 and the declarations of p1 that are used in p2.

In Cangjie, import can be modified by the access modifiers private, internal, protected, and public. An 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 directly import and use the entities re-exported by the current package according to visibility, without the need to import it from the original package.

  • private import: The imported content can be accessed only in the current file. private is the default modifier of import. An import without an access modifier is equivalent to a private import.
  • internal import: The imported content can be accessed in the current package and its subpackages (including nested subpackages). Explicit import is required for non-current packet access.
  • protected import: The imported content can be accessed in the current module. Explicit import is required for non-current packet access.
  • public import: The imported content can be accessed externally. Explicit import 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

It is worth noting that entire 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