包和模块管理

在仓颉编程语言中,程序以包的形式进行组织,包是最小的编译单元。包可以定义子包,从而构成树形结构。没有父包的包称为 root 包,root 包及其子包(包括子包的子包)构成的整棵树称为 module。module 的名称与 root 包相同。module 是仓颉的最小发布单元。

包由一个或多个源码文件组成,同一个包的源码文件必须在同一个目录,并且同一个目录里的源码文件只能属于同一个包。子包的目录是其父包目录的子目录。

包的声明

包声明以关键字 package 开头,后接 root 包至当前包路径上所有包的包名,以 . 分隔(路径本身不是包名)。

包声明的语法如下:

packageHeader
    : packageModifier? (MACRO NL*)? PACKAGE NL* fullPackageName end+
    ;

fullPackageName
    : (packageNameIdentifier NL* DOT NL*)* packageNameIdentifier
    ;

packageNameIdentifier
    : Ident
    | contextIdent
    ;

packageModifier
    : PUBLIC
    | PROTECTED
    | INTERNAL
    ;

每个包都有包名,包名是这个包可唯一识别的标识符。

除 root 包外,包名必须和其所在的目录名一致。

包声明必须在文件的首行(注释和空白字符不计),每个文件只能有一个 package 声明。特别地,root 包的文件可以不声明 package,对于不包含 package 声明的文件,它会用 default 作为包名。

// ./src/main.cj
// Leaving package declaration out is equivalent to 'package default'

main() {
    println("Hello World")
}

package 可以被 internalprotected 或者 public 修饰。package 的默认修饰符(默认修饰符是指在省略情况下的修饰符语义,这些默认修饰符也允许显式写出)为 public。同一个包在不同文件中的 package 声明必须使用相同的访问修饰符修饰。特别地,root 包不能被 internal 或者 protected 修饰。

  • internal 表示仅当前包及子包(包括子包的子包)内可见。当前包的子包(包括子包的子包)内可以导入这个包或者这个包的成员。
  • protected 表示仅当前 module 内可见。同一个 module 内的其它包可以导入这个包或者这个包的成员,不同 module 的包无法访问。
  • public 表示 module 内外均可见。其它包可以导入这个包或者这个包的成员。

包的成员

包的成员是在顶层声明的类、接口、struct、enum、类型别名、全局变量、扩展、函数。顶层声明包括的内容,可以参考其对应的章节。

当前包的父包和子包并不是当前包的成员,访问父包或者子包需要包的导入机制,未被导入的包名不在 top-level 作用域中。

如下所示的例子中,package a.bpackage 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

如下所示的例子中,package a.bpackage 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'

特别地,子包不能和当前包的成员同名,这是为了保证访问路径中的名称是唯一的。

访问修饰符

仓颉中,可以使用访问修饰符来保护对类型、变量、函数等元素的访问。仓颉有 4 种不同的访问修饰符。

  • private
  • internal
  • protected
  • public

修饰顶层元素

在修饰顶层元素时不同访问修饰符的语义如下:

  • private 表示仅当前文件内可见。不同的文件无法访问这类成员。
  • internal 表示仅当前包及子包(包括子包的子包)内可见。同一个包内可以不导入就访问这类成员,当前包的子包(包括子包的子包)内可以通过导入来访问这类成员。
  • protected 表示仅当前 module 内可见。同一个包的文件可以不导入就访问这类成员,不同包但是在同一个 module 内的其它包可以通过导入访问这些成员,不同 module 的包无法访问。
  • public 表示 module 内外均可见。同一个包的文件可以不导入就访问这类成员,其它包可以通过导入访问这些成员。
FilePackage & Sub-PackagesModuleAll Packages
privateYNNN
internalYYNN
protectedYYYN
publicYYYY

不同顶层声明支持的访问修饰符和默认修饰符规定如下:

  • pacakge 支持使用 internalprotectedpublic,默认修饰符为 public
  • import 支持使用全部访问修饰符,默认修饰符为 private
  • 其他顶层声明支持使用全部访问修饰符,默认修饰符为 internal

修饰非顶层成员

在修饰非顶层成员时不同访问修饰符的语义如下:

  • private 表示仅当前类型或扩展定义内可见。
  • internal 表示仅当前包及子包(包括子包的子包)内可见。
  • protected 表示当前 module 及当前类的子类可见。
  • public 表示 module 内外均可见。
Type/ExtendPackage & Sub-PackagesModule & Sub-ClassesAll Packages
privateYNNN
internalYYNN
protectedYYYN
publicYYYY

类型成员的访问修饰符可以不同于类型本身。除接口外类型成员的默认修饰符(默认修饰符是指在省略情况下的修饰符语义,这些默认修饰符也允许显式写出)是 internal,接口中的成员函数和属性不可以写访问修饰符,它们的访问级别等同于 public

访问修饰符的合法性检查

仓颉的访问级别排序为 public > protected > internal > private

类型的访问级别:

  • 非泛型类型的访问级别由类型声明的访问修饰符决定
  • 泛型实例化类型的访问级别等同于该泛型类型与该泛型类型实参的访问级别中最低的一个

一个声明的访问修饰符不得高于该声明中用到的类型的访问修饰符的级别。具体地:

  • 变量、属性声明的访问级别不得高于其类型的访问级别
  • 函数声明的访问级别不得高于参数类型、返回值类型,以及 where 约束中的类型上界的访问级别
  • 类型别名的访问级别不得高于原类型的访问级别
  • 类型声明的访问级别不得高于 where 约束中的类型上界的访问级别
  • 子包的访问级别不得高于其父包的访问级别
  • import 的访问修饰符不得高于其导入声明的访问级别
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

特别地,类继承时子类访问级别与父类访问级别、类型实现/继承接口时子类型访问级别与父接口访问级别不受上述规则限制。

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

包的导入

导入是一种用来将其他包或其他包中的成员引入到当前仓颉程序中的机制。

当源码中没有 import 声明的时候,当前文件只能访问当前包中的成员和编译器默认导入的成员。通过 import 声明,可以让编译器在编译这个仓颉文件时找到所需要的外部名称。

import 语句在文件中的位置必须在包声明之后,其他声明或定义之前。

import 相关的语法如下:

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
   ;

import 语法有如下几种形式:

  • 单导入
  • 别名导入
  • 全导入
  • 批量导入

通过 import,可以导入一个或多个其他包或者其他包中的成员,也可以通过 as 语法为导入的名称定义别名。如果导入的名称是包,则可以用它继续访问包中的成员(子包不是包的成员),但包名本身不能作为表达式。

package a
public let x = 0
package demo

import a

main() {
    println(a.x) // ok, prints 0
}

任意包和包之间不能产生循环依赖,即使是同一个 module 下的包之间也不可以。对于任意两个包 p1p2,如果 p1 导入了 p2 或者 p2 的成员,那么我们称 p1p2 具有依赖关系,p1 依赖 p2。依赖关系具有传递性,如果 p1 依赖 p2p2 依赖 p3,那么 p1 依赖 p3。包的循环依赖是指存在包相互依赖的情况。

package p.a
import p.b // error
pacakge p.b
import p.a // error

禁止使用 import 导入当前包或当前包中的成员。

package a

import a // error
import a.x // error

public let x = 0

导入的成员的作用域级别低于当前包声明的成员。导入的非函数成员会被当前包的同名成员遮盖;导入的函数成员若可以和当前包的同名函数构成重载,调用时会根据 [泛型函数重载] 和 [函数重载] 的规则进行函数决议;导入的函数成员若和当前包的同名函数不构成重载,则按照遮盖处理。

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

单导入

单导入语法用来导入单个成员,目标成员必须是对当前包可见的。导入的成员名称会作为当前作用域内可以访问的名称。

import 语法中的路径最后一个名称表示指定的成员,这个名称可以是顶层变量、函数、类型,也可以是包。

下面是导入顶层变量、函数、类型的例子。有两个包分别是 ab,在 b 包中导入 a 包的成员。

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

如下所示的例子中,ca 的子包。

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

单导入的成员被当前包成员遮盖时,编译器会给出告警提示无用导入。

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

别名导入

别名导入可以使用 as 语法为导入成员重命名。以别名导入的内容在当前包中只会以别名的形式引入作用域,而不会引入原来的名称(但不禁止分别导入原名和别名)。导入的内容可以是包或者包的成员。

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'

全导入

全导入通过 * 语法导入其他包中所有对当前包可见的顶层成员(不包括子包)。

示例如下:

package a

public let x = 0
public func f() { 0 }
public class A {}
import a.*

private func g(_: A) { x + f() } // ok

与单导入不同,当全导入的成员被当前包成员遮盖时,编译器不会给出告警。

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

如果导入的成员不被当前包的成员遮盖,但多个导入成员重名时,编译器不会给出告警,但如果这些重名的导入不构成重载,这个名字在本包中不可用,在使用该名称时编译器会因无法找到唯一的名称而报错。

package b

public let x = 1
public func f(x: Int64) { x }
import a.*
import b.*

let _ = x // error: ambiguous 'x'

如果导入的重名成员可以构成函数重载,调用时会根据 [泛型函数重载] 和 [函数重载] 的规则进行函数决议。

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

带访问修饰符的全导入不会导入比其访问级别低的声明。

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

批量导入

批量导入使用 {} 语法,在一个 import 声明里同时导入多个成员。通常用来省略重复的包路径前缀。

批量导入的 {} 中支持单导入、别名导入和全导入,但不允许嵌套批量导入。

import std.{
    time,
    fs as fileSystem,
    io.*,
    collection.{HashMap, HashSet} // syntax error
}

{} 的前缀可以为空。

import {
    std.time,
    std.fs as fileSystem,
    std.io.*,
}

使用批量导入语法与使用多个独立 import 的语法是等价的。

import std.{
    os.process,
    time,
    io.*,
    fs as fileSystem
}

等价于:

import std.os.process
import std.time
import std.io.*
import std.fs as fileSystem

导入名称冲突检查

如果多个单导入的名称产生重名(包括重复导入)且不构成函数重载,并且该名字在本包中没有被遮盖,编译器会给出名称冲突告警,这个名字在本包中不可用,在使用该名称时编译器会因无法找到唯一的名称而报错。若该名称被当前包成员遮盖时,编译器会给出告警提示无用导入。

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'

如果导入的重名成员之间或者导入的成员与当前包中的同名函数之间可以构成函数重载,调用时会根据 [泛型函数重载] 和 [函数重载] 的规则进行函数决议。

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

多个别名导入同名,或者别名导入和本包定义同名时的处理规则与单导入相同。

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

如果导入名称冲突的其中一方来自全导入,这种情况下编译器也不会给出报警,但冲突的声明都不可用。

批量导入依据其等价的单导入、别名导入、多导入做名称冲突检查。

import 的访问修饰符

import 可以被 privateinternalprotectedpublic 访问修饰符修饰。其中,被 publicprotected 或者 internal 修饰的 import 可以把导入的成员重导出(如果这些导入的成员没有因为名称冲突或者被遮盖导致在本包中不可用)。其他包可以根据 [访问修饰符] 的访问规则通过 import 导入这些被重导出的对象。具体地:

  • private import 表示导入的内容仅当前文件内可访问,privateimport 的默认修饰符,不写访问修饰符的 import 等价于 private import
  • internal import 表示导入的内容在当前包及其子包(包括子包的子包)均可访问。非当前包访问需要显式 import
  • protected import 表示导入的内容在当前 module 内都可访问。非当前包访问需要显式 import
  • public import 表示导入的内容外部都可访问。非当前包访问需要显式 import

在下面的例子中,ba 的子包,在 a 中通过 public import 重导出了 b 中定义的函数 f

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'

特别地,包不可以被重导出:如果被 import 导入的是包,那么该 import 不允许被 publicprotected 或者 internal 修饰。

public import a.b // error: cannot re-export package