# TSL unit 与作用域

这一篇只讲“怎么写和怎么调用 `unit`”。`namespace`、查找路径、`unit` 生命周期、`tsl.conf` 与运行时入口统一放到 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。

## 本篇职责

回答下面几类最常见的问题：

- 什么时候该把一组函数写成 `unit`
- `uses` 到底能放在哪里
- `interface` 段 `uses` 和 `implementation` 段 `uses` 的可见性差别是什么
- 多个 `unit` 里有同名接口时，未限定调用到底命中谁
- 怎样显式指定要调用哪个 `unit` 的接口

## 智能体 unit/作用域判断流程

1. 先判断当前交付是 `.tsl` 可执行脚本、`.tsf` 扩展模块，还是 `unit` 文件。
2. `unit` 文件只描述可复用单元；脚本入口仍放在 `.tsl`。
3. `uses` 必须放在普通语句之前；普通语句后不要再追加顶层 `uses`。
4. 默认参数、接口段、实现段和作用域边界只照本页文档明确形态写。
5. `unit` 生命周期、命名空间、查找路径、`tsl.conf` 和命令行查找路径参数的事实见 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。
6. 没有对应代码块时不要发明 unit/作用域写法。

## 核心规则

- `unit` 是完整的顶层主体；常见完整形态是 `unit Name; interface ... implementation ... end.`。
- `unit` 也可以省略 `interface` / `implementation` 写成简写形态；这种简写里定义的函数对外可调用。
- 如果没有特殊需求，默认优先用完整形态；简写形态只在不需要显式区分 `interface` / `implementation` 时再用。
- 完整 `unit` 示例中，`implementation` 前保留空行，避免接口段和实现段挤在一起。
- `unit` 允许接口声明与实现段分离：`interface` 段可以只声明函数签名或类方法签名，函数体和类方法体放到 `implementation` 段。
- 类方法放在 `implementation` 段实现时，使用 `function ClassName.Method(...)` 形态。
- 顶层 `uses` 可以放在文件最前面，让后续整个文件直接看到被引入 `unit` 的接口。
- 顶层导入多个 `unit` 时，只生成单条逗号合并写法：`uses UnitA, UnitB;`；不要生成连续多条顶层 `uses`。真正会失败的是前面已经出现普通语句后，再写顶层 `uses`。
- 函数体里的 `uses` 必须是函数体第一条语句，而且一个函数体里只能写一次。
- 类定义体里的 `uses` 必须是类定义体第一条语句，而且一个类定义体里只能写一次。
- 如果函数体或类定义体需要多个 `unit`，默认把这些 `uses` 提升到顶层并合并成单条 `uses UnitA, UnitB;`；不要在函数体或类定义体里连写两条 `uses`。
- 写在 `interface` 段的 `uses` 对整个 `unit` 可见；写在 `implementation` 段的 `uses` 只对实现段和在实现段里定义的方法体可见。
- 只写在 `implementation` 里的函数，对 `unit` 外部不可见。
- 出现重名接口时，未限定调用按 `uses` 从后往前解析；最后一个 `uses` 的同名接口优先。
- 要显式指定某个 `unit` 的接口，可以用 `UnitA.Ping()`、`unit(UnitA).Ping()` 或 `call("UnitA.Ping")`。
- `unit` 生命周期、接口状态、命名空间和查找路径细节见 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。

## 示例与行为

### 最小 `unit`

代码块身份：可直接照写示例

```tsl
unit DemoUnit;

interface

function Ping();

implementation

function Ping();
begin
    return 1;
end;

end.
```

结果说明：

- 上面这就是最小完整 `unit` 骨架。
- `unit` 文件结尾要用 `end.`，不是普通函数或类的 `end;`。

如果该 `unit` 被脚本 `uses DemoUnit;` 后调用 `writeLn(Ping());`，可观察输出为：

代码块身份：输出片段

```text
1
```

### 简写 `unit`

代码块身份：可直接照写示例

```tsl
unit SpecialUnit;

function Abcd();
begin
    return 10;
end;
function Abcd2();
begin
    return 20;
end;

initialization
    writeLn("INIT");

finalization
    writeLn("FINAL");

end.
```

结果说明：

- `unit SpecialUnit;` 后可以省略 `interface` / `implementation`。
- 这种简写形态里定义的 `Abcd()`、`Abcd2()` 外部可以直接调用。
- 简写 `unit` 里同样可以写 `initialization` 和 `finalization`。

### 顶层、函数体、类体里的 `uses`

沿用同一个 `DemoUnit.tsf`，分别看顶层、函数体、类体三种 `uses` 位置：

代码块身份：配置片段 / 概念骨架
代码块说明：多文件结构骨架；依赖 `unit` 查找路径，不是可直接复制的单文件最小示例。

```text
// DemoUnit.tsf
unit DemoUnit;

interface

function Ping();

implementation

function Ping();
begin
    return 11;
end;

end.

// main.tsl
uses DemoUnit;
obj := new Worker();
writeLn(Ping());
writeLn(RunInFunction());
writeLn(obj.Run());

function RunInFunction();
begin
    uses DemoUnit;
    return Ping();
end;
type Worker = class
    uses DemoUnit;
public
    function Run();
    begin
        return Ping();
    end;
end;
```

结果说明：

- 顶层 `uses DemoUnit;` 后，脚本主体可以直接调用 `Ping()`。
- 函数体第一行写 `uses DemoUnit;` 后，`RunInFunction()` 输出 `11`。
- 类定义体第一行写 `uses DemoUnit;` 后，`obj.Run()` 也输出 `11`。
- 这一类多文件场景依赖真实查找路径；如果 `DemoUnit.tsf` 所在目录不在查找路径里，脚本仍会找不到对应接口。

### `uses` 的位置限制

代码块身份：反例 / 不可照写

```text
// 顶层前面已经有普通语句
a := 1;
uses DemoUnit;

// 函数里不是第一行
function Run();
begin
    a := 1;
    uses DemoUnit;
    return Ping();
end;

// 函数里写两次
function Run();
begin
    uses UnitA;
    uses UnitB;
    return Ping();
end;

// 类里不是第一行
type Worker = class
public
    value;
    uses DemoUnit;
end;

// 类里写两次
type Worker = class
    uses UnitA;
    uses UnitB;
end;
```

结果说明：

- 顶层前面如果已经出现 `a := 1;` 这类普通语句，再写顶层 `uses` 会报 `invalid statement`。
- 函数体里，`uses` 不是第一行或者重复出现，都会失败。
- 类定义体里，`uses` 不是第一行或者重复出现，都会失败。
- 函数体或类定义体里重复 `uses` 的失败点是“第二条 `uses`”，不是顶层导入多个 `unit`。
- 顶层导入多个 `unit` 时只生成 `uses UnitA, UnitB;` 这种逗号合并写法；连续多条顶层 `uses` 不再作为文档明确生成形态。

### `interface` 段 `uses` 和 `implementation` 段 `uses`

代码块身份：配置片段 / 概念骨架
代码块说明：多文件结构骨架；依赖 `unit` 查找路径，不是可直接复制的单文件最小示例。

```text
// UnitB.tsf
unit UnitB;

interface

function FB();

implementation

function FB();
begin
    return 100;
end;

end.

// UnitC.tsf
// 与 UnitB.tsf 同形，只把 FB / 100 改成 FC / 200

// UnitA.tsf
unit UnitA;

interface

uses UnitB;

type Box = class
public
    function FromInterface();
    begin
        return FB();
    end;
    function FromImplementation();
end;
function CallB();
function CallC();

implementation

uses UnitC;

function Box.FromImplementation();
begin
    return FC();
end;
function CallB();
begin
    return FB();
end;
function CallC();
begin
    return FC();
end;

end.

// main.tsl

uses UnitA;
obj := new Box();
writeLn(CallB());
writeLn(CallC());
writeLn(obj.FromInterface());
writeLn(obj.FromImplementation());
```

结果说明：

- 在 `interface` 段写 `uses UnitB;` 后，`FB()` 对整个 `unit` 都可见，`CallB()` 输出 `100`。
- 在 `implementation` 段写 `uses UnitC;` 后，`FC()` 对实现段函数和在实现段定义的方法体可见，`CallC()` 与 `obj.FromImplementation()` 都输出 `200`。
- 接口段里的内联方法 `FromInterface()` 能访问 `interface` 段 `uses` 引入的接口，因此输出 `100`。
- 接口段里的内联方法不能看到只在 `implementation` 段 `uses` 引入的接口。

### 接口声明与实现段分离

代码块身份：可直接照写示例

```tsl
unit SplitDemo;

interface

function MakeValue();
type UnitBox = class
public
    Value;
    function create(init_value);
    function ReadValue();
end;

implementation

function MakeValue();
begin
    return 10;
end;
function UnitBox.create(init_value);
begin
    Value := init_value;
end;
function UnitBox.ReadValue();
begin
    return Value;
end;

end.
```

结果说明：

- 普通函数可以在 `interface` 段只声明 `function MakeValue();`，再在 `implementation` 段写函数体。
- 类方法可以在类体里只声明签名，再在 `implementation` 段写 `function UnitBox.Method(...)` 实现。
- 类方法实现放在 `implementation` 段时，仍属于该 `unit` 的实现段。

### 实现段私有函数

代码块身份：配置片段 / 概念骨架
代码块说明：多文件结构骨架；依赖 `unit` 查找路径，不是可直接复制的单文件最小示例。

```text
// PrivateDemo.tsf
unit PrivateDemo;

interface

function PublicFunc();

implementation

function PublicFunc();
begin
    return PrivateFunc();
end;
function PrivateFunc();
begin
    return 77;
end;

end.

// main.tsl

uses PrivateDemo;
writeLn(PublicFunc());
```

结果说明：

- `PublicFunc()` 输出 `77`，说明接口函数可以调用实现段私有函数。
- 外部直接调用 `PrivateFunc()` 时，会报 `function:PrivateFunc compile error or not found`。

代码块身份：反例 / 不可照写

```text
uses PrivateDemo;
writeLn(PrivateFunc());
```

上面这种写法不可照写；`PrivateFunc()` 只写在 `implementation` 段，对 `unit` 外部不可见。

### 重名解析与限定调用

重名函数最小对照：

代码块身份：配置片段 / 概念骨架
代码块说明：多文件结构骨架；依赖 `unit` 查找路径，不是可直接复制的单文件最小示例。

```text
// UnitA.tsf
unit UnitA;

interface

function Ping();
function Hello();

implementation

function Ping();
begin
    return 101;
end;
function Hello();
begin
    return 111;
end;

end.

// UnitB.tsf
// 与 UnitA.tsf 同形，只把 101 / 111 改成 202 / 222

// main.tsl
uses UnitA, UnitB;
writeLn(Ping());
writeLn(UnitA.Hello());
writeLn(UnitB.Hello());
writeLn(unit(UnitA).Ping());
writeLn(call("UnitB.Ping"));
```

结果说明：

- 未限定的 `Ping()` 输出 `202`，说明解析顺序是 `uses` 从后往前。
- `UnitA.Hello()` 输出 `111`，`UnitB.Hello()` 输出 `222`。
- `unit(UnitA).Ping()` 输出 `101`。
- `call("UnitB.Ping")` 输出 `202`。

重名类最小对照：

代码块身份：配置片段 / 概念骨架
代码块说明：多文件结构骨架；依赖 `unit` 查找路径，不是可直接复制的单文件最小示例。

```text
// UnitA.tsf
unit UnitA;

interface

type Box = class
public
    value;
    function create();
end;

implementation

function Box.create();
begin
    value := 101;
end;

end.

// UnitB.tsf
// 与 UnitA.tsf 同形，只把 Box.create() 里写入的 101 改成 202

// main.tsl
uses UnitA, UnitB;
obj := new Box();
writeLn(obj.value);
objA := createObject("UnitA.Box");
writeLn(objA.value);
objB := createObject("UnitB.Box");
writeLn(objB.value);
```

结果说明：

- 未限定的 `new Box()` 输出 `202`。
- `createObject("UnitA.Box")` 输出 `101`。
- `createObject("UnitB.Box")` 输出 `202`。
- 重名类的未限定解析顺序和重名函数一致，也是按 `uses` 从后往前。

## 默认生成模板

如果你只是要先写出一个最小、可被其他文件引用的 `unit`，从下面这个骨架起步：

代码块身份：可直接照写示例

```tsl
unit DemoUnit;

interface

function Ping();

implementation

function Ping();
begin
    return 1;
end;

end.
```

## 禁止项

- 把依赖外部 `unit` 的 `uses` 例子误当成单文件最小示例。
- 在还没把目标 `unit` 放进查找路径前，就把“找不到接口”误判成 `uses` 语法错。
- 把函数体或类定义体里的 `uses` 写在第一条语句之后。
- 在同一个函数体或类定义体里重复写第二个 `uses`。
- 生成连续多条顶层 `uses`；多个 `unit` 默认写成 `uses UnitA, UnitB;`。
- 把重名接口的未限定解析顺序理解成“从前到后”。
- 忘了显式指定 `UnitA.Ping()`、`unit(UnitA).Ping()` 或 `call("UnitA.Ping")`，结果误调用到最后一个 `uses` 的同名接口。
