TinyActor 中文 · English

分层类型模型(Layered Type Model)

设计原则:类型检查只发生在上层语言;下层实现是无类型的。 但底层原语不是完全不承诺类型——按语义通用性取光谱。

本文档对应 docs/improvement-plan.md 阶段 C4,是对 B3/C3 落地行为的正式描述。


1. 两层模型

┌─────────────────────────────────────────────────┐
│  上层 typed surface(TA 源码)                    │
│  · ADT 构造器 + 模式匹配                         │
│  · 带签名 fn(a -> b 注解、泛型、HOF)            │
│  · 类型化封装函数("kons" 类,见 §5)             │
├─────────────────────────────────────────────────┤
│  下层 untyped(VM / C 模块 / 字节码)             │
│  · tavm 运行时(NaN-boxing 值,tag=0xff01 等)    │
│  · C builtin 模块(str / vm / buf / file / net)  │
│  · 跨模块点号调用(module.fn)——模块加载期解析    │
└─────────────────────────────────────────────────┘

类型检查器(lib/bootstrap/typecheck.ta)只对上层源码做 Hindley-Milner 推断。 下层要么被上层显式承诺类型(§3),要么在加载期/运行时自行解析(§4), 类型检查器对后者保持宽容。


2. 光谱规则(逐原语类型承诺)

按语义通用性,原语分成两类:

类别 判定标准 类型承诺 示例
真·通用原语 对任意值都成立,语义与具体类型无关 宽松多态(forall) cons / car / cdr / print / null? / pair? / int? / string? / symbol? / spawn / send / recv / monitor / receive-scan
语义绑定原语 语义强绑定到具体类型,传错类型必然是 bug 具体类型(无 forall) str.* 全部 / vm.get_arg / + - * / / < > <= >= / not / self

示例(C3 落地):


3. 内置类型承诺清单(C3 审计产出)

来源:lib/bootstrap/typecheck.tamake_builtin_env。分类:宽松 = forall 多态,绑定 = 具体类型。

宽松(真·通用)

原语 承诺类型 备注
car / cdr forall(a b, a -> b) / forall(a, a -> a) heterogeneous pair 合法
cons forall(a b, a -> b -> b) 元素类型不受约束
print forall(a c, a -> c) 返回类型自由
null? pair? int? string? symbol? forall(a, a -> bool) 类型谓词
spawn forall(a, a -> pid) 任意消息初始值
send forall(a b, a -> b -> b) 任意消息
recv / recv-commit / recv-skip forall(a, a) 消息类型自由
monitor forall(a b, a -> b)  
receive-scan forall(a b, (a -> b) -> b)  
vm.parse_source forall(a, string -> a) 解析产物类型自由
vm.is_builtin_module forall(a, a -> bool)  
vm.load_bytecode forall(a, a -> int)  

绑定(语义绑定)

原语 承诺类型 备注
str.eq string -> string -> bool  
str.concat string -> string -> string  
str.length string -> int  
str.char_at string -> int -> int  
str.substr string -> int -> int -> string  
str.to_int string -> int  
str.from_int int -> string  
str.index_of string -> string -> int  
str.to_sym string -> symbol C3 收紧(原 forall(a, string -> a)
str.sym_to_str symbol -> string C3 收紧(原 forall(a, a -> string)
vm.get_arg int -> string  
self pid 无参
+ - * / int -> int -> int  
< > <= >= int -> int -> bool  
not bool -> bool  

4. 下层的宽容面(B3 落地行为)

4.1 跨模块点号调用(module.fn)

typecheck.count_errorscodegen.compile 这类跨模块函数不在当前 env—— 它们由模块加载期解析(import 时注册)。B3 的 undefined 检测对 带点符号(str.index_of(sym, ".") > 0)豁免,保持历史宽松行为:

4.2 语言关键字:match / receive

matchreceive模式位置(构造器、_ 通配、绑定变量)是 codegen 的职责,不参与类型推断(B3 前会误入函数调用路径报 E0002):

4.3 块级 let(braced block 语句)

{ let f1 = make_closure(20) let f2 = f1(10) ... } 中,let 是无 body 语句,绑定延伸到同块后续语句。typechecker 的 let 形式假设 Lisp 嵌套语义, 因此预注册块内所有 let 绑定名(fresh tvar,宽松):

4.4 C 模块(buf / file / net / bytes)

这些模块不在 typecheck env(tavm 加载期注册)。调用是宽松的(点号豁免), 运行时负责错误处理。bytes 模块未实现——bytes(...)/bytes-length(...) 编译被 typecheck 拒绝([E0002],见 test/basic/bytes-basic-errors.ta)。


5. “kons” 类封装的定位

当下层行为通用但类型信息丢失时,在上层用带签名函数封装恢复类型安全:

// 例:给宽松的 cons 语义一个强类型的 List 门面
fn kons(x: int, xs: List(int)) -> List(int) {
  Cons(x, xs)          // 走 ADT 构造器,类型安全
}

6. 错误码

含义 示例输出
E0001 类型不匹配 [E0001] in function 'main' (line 5): arg 2 of map: cannot unify string with List(int)
E0002 未定义函数/符号 [E0002] in function 'main' (line 1): undefined function 'foo'

结构化错误((kind . (ctx . payload)))为未来 LSP 铺路(阶段 B 收尾)。


7. 验证