Type System

本文定义 Markup/Code 双 Mode 下现行的类型与求值模型。精确语法见 vault::grammar,内置 Function 签名见 vault::functions。当前实现已经具备 mode-aware syntax tree、Function signature、argument binding 与 Document 静态检查;尚未完成的 typed IR 和 Value-returning Function 边界见“当前迁移边界”。

核心判断

Notist Document 是一个隐式 Markup expression。整篇文档具有静态类型 Content,求值结果也是 Content

source Document
-> parse Markup and embedded Code
-> resolve names and check expression types
-> evaluate Code expressions
-> concatenate Markup fragments and embedded outputs
-> Content

形式化地写:

Γ ⊢ document : Content
Γ ⊢ document ⇓ content

其中 Annotation 是独立的 source span metadata,不参与上述类型判断和求值关系。

Markup 与 Code 的类型边界

Document 默认处于 Markup。Markup 中的普通文字和宿主语法直接产生 Content:

Text                 : Content
Wiki Reference       : Content
Inline/Fenced Raw    : Content
Embedded Expression  : Content
Markup sequence      : Content

# 在 Markup 中开始一个 EmbeddedExpression,并切换到 Code。CodeExpression 先按自己的类型求值,再通过 Content insertion 规则进入外层 Markup。

Markup:  Before #expr after
                  │
                  └─ CodeExpression

# 不是运行时 unary operator,也不改变 expression 自身的类型。它是语法 mode switch 和插值边界。

Content Literal

Code mode 中的 [...] 是 Content literal。方括号内部切回 Markup,因此可以包含 Text、Reference、Raw、Function Call 和嵌套 EmbeddedExpression。

#[Plain Content]

#[Outer #[inner]@inner Content]

#quote[Body with [[vault::grammar]] and #raw(text="code").]

类型规则:

Γ ⊢ markup : Content
──────────────────────
Γ ⊢ [markup] : Content

在普通 Markup 中,裸 [abc] 是包含方括号的文字,不是 Content literal。只有进入 Code 后,[...] 才具有 literal 含义:

[abc]   // Markup text containing brackets
#[abc]  // embedded Content literal

未来变量系统可以直接复用相同规则:

#let x = [stored Content]
#x

let 本身不属于当前阶段,但不需要为它重新设计 Content literal。

Content 插入规则

EmbeddedExpression 的结果必须能够进入外层 Content。本阶段定义三个允许的结果:

Content -> 原样插入
String  -> 转换为一个 Text Element
None    -> 空 Content

形式化规则:

Γ ⊢ e : Content
────────────────
Γ ⊢ #e : Content

Γ ⊢ e : String
─────────────────────────
Γ ⊢ #e : Content(Text(e))

Γ ⊢ e : None
────────────────────
Γ ⊢ #e : Content()

Bool、Int、Float 和其他未来 Value 不自动 stringify;直接插入时产生 type diagnostic。需要显示时应显式转换为 String。这样避免 Agent 和 renderer 依赖隐式格式化规则。

#"text"     // String -> Text Content
#[content]  // Content -> Content
#none       // empty Content
#42         // type error in Markup position

Static Type

本阶段静态类型集合为:

Type
├─ None
├─ Bool
├─ Int
├─ Float
├─ String
├─ Content
└─ Optional<T>

Type 描述 CodeExpression、Function parameter 和 Function result 可以产生或接受的值。显示名称使用 NoneBoolIntFloatStringContentOptional<T> 显示为 T?

None 与 Optional

None 是 literal none 的类型。T? 接受 noneT,不是独立运行时容器:

String? accepts None
String? accepts String
String? rejects Int

可选类型不等于具有 default。是否允许省略由 Function signature 的 default 决定:

lang: String? = none

String? 允许显式传入 none= none 允许完全省略 lang

数值兼容

Int 和 Float 是不同类型。Binding 允许 Int 实参用于 Float parameter,但不允许 Float 用于 Int parameter:

Float accepts Float
Float accepts Int
Int accepts Int
Int rejects Float

兼容绑定必须明确定义运行时行为。目标实现应在绑定时把 Int widening 为 Value::Float,或者为 Function API 暴露统一的 checked numeric accessor;不能让 signature 声明 Float、实际却在无约定的情况下传入 Int variant。

Runtime Value

CodeExpression 求值产生 Value:

Value
├─ None
├─ Bool(bool)
├─ Int(i64)
├─ Float(f64)
├─ String(String)
└─ Content(Content)

Optional 没有对应 Value variant;运行时使用 Value::None 或具体 Value。

Content 是有顺序的语义 Element sequence,不是 String,也不是待重新解析的源码。Content literal 内的 Markup 在产生 Value::Content 前已经完成递归 parse、analysis 和 evaluation。

String 与 Markup Text

String literal 只存在于 Code mode:

"abc"    // Markup Text,保留 quote
#"abc"   // Code String,插入时转成 Text("abc")

四种 String literal 都具有同一个 String 类型与 Value::String

Form

Escape style

Type

"..."

Escaped

String

triple quote 后立即换行

Escaped

String

r#"..."#

Raw

String

r# + triple quote 后立即换行

Raw

String

Inline/Multiline 与 Escaped/Raw 是 literal provenance,不是不同类型。Binder 可以保留:

  • 完整 literal range。

  • payload range。

  • Inline/Multiline form。

  • Escaped/Raw style。

这些信息不改变 String 类型,但 raw 等 native Function 可以读取。默认值和未来计算产生的 String 不保证具有 literal provenance。

Function Signature

Function signature 包含:

  • 有顺序的 parameters。

  • parameter name、Type 和可选 default。

  • trailing Content parameter binding。

  • result Type。

部分内置 Function:

heading(level: Int = 1, body: Content) -> Content
raw(text: String, lang: String? = none) -> Content
quote(attribution: Content? = none, body: Content) -> Content
callout(kind: String = "note", title: Content? = none, body: Content) -> Content
details(summary: Content? = none, open: Bool = false, body: Content) -> Content

Function Call 是 CodeExpression。普通 arguments 在 Code mode 中解析;紧随调用的 [...] 是普通 Content literal,并作为 trailing argument 参与 signature binding:

#heading(level=2)[Title]
#quote[Quoted Content]

等价的类型视图:

heading(level=2, body=[Title])
quote(attribution=none, body=[Quoted Content])

Parser 不根据 Function name 或 signature 改变 [...] 的语法。Analysis 负责确认 trailing Content 能绑定到哪个 parameter。

Function Result

Function result Type 必须是真实契约,而不能只用于 Hover 显示:

arguments satisfy signature
Function::call(arguments) -> value
value.type satisfies signature.result

当前 native Function 都产生 Content。在 Markup 中嵌入 Call 时,其结果还必须满足 Content insertion 规则。

foo() -> Content  // 可直接 #foo()
bar() -> String   // 可直接 #bar(),转成 Text
count() -> Int    // #count() 是 Content-position type error

如果第一阶段继续使用只返回 FunctionOutput/Content 的 trait,则 Registry 必须强制 result == Content;等一般 Value-returning Function 实现后,再把 runtime API 扩展为返回 Value。

Argument Binding

参数按以下规则绑定:

  1. Positional argument 按 parameter 顺序绑定。

  2. Named argument 按名称绑定。

  3. 第一个 named argument 后不能再出现 positional argument。

  4. Trailing Content literal 绑定到 signature 指定的 Content parameter。

  5. 同一个 parameter 不能由 positional、named 或 trailing argument 重复提供。

  6. 未提供的 parameter 使用 default;无 default 则产生 missing argument diagnostic。

  7. 所有 Value 必须满足 parameter Type。

  8. Default 本身必须在 Function 注册时满足 parameter Type。

Binding 在 Function 执行前报告:

  • unknown argument。

  • positional after named。

  • too many positional arguments。

  • duplicate argument。

  • missing required argument。

  • type mismatch。

  • Function 不接受 trailing Content。

  • 非法 signature/default/result。

任何 binding diagnostic 都会阻止 Function 执行。Parser 遇到缺失或非法 CodeExpression 时必须先产生 syntax diagnostic,不能静默丢弃 argument 后让 default 掩盖错误。

Document 的类型与求值

Markup sequence 的组合规则是 Content concatenation:

Γ ⊢ a : Content    Γ ⊢ b : Content
──────────────────────────────────
Γ ⊢ concat(a, b) : Content

Document 可以建模为隐式 Content expression:

Document
└─ Concat
   ├─ Text Content
   ├─ Reference Content
   ├─ Embedded Code output converted to Content
   ├─ Raw Content
   └─ Text Content

目标管线:

Source
-> mode-aware Syntax Tree
-> name resolution
-> Function signature resolution
-> static type checking
-> Typed Document : Content
-> evaluation
-> Content
-> structuring
-> StructuredDocument

静态 analysis 不执行 Function runtime。LSP diagnostics 应使用 resolved signatures 和 typed tree;只有 preview/build 等 evaluation 路径执行 native、user 或 plugin Function。

错误恢复与 Partial Content

Syntax、name resolution 或 type diagnostic 不应让整篇文档不可分析。实现可以保留 Error/Unknown typed node,并在 evaluation 或 preview 中产生可恢复的 UnresolvedCall。

但恢复必须显式:

  • 非法 expression 不能静默消失。

  • 错误 argument 不能偷偷回退到 default。

  • Function result Type 不匹配不能当作 Content 插入。

  • notist check、LSP 和 build 必须共享同一套静态 diagnostics。

Evaluation 可以同时返回 partial Content 和 diagnostics,但成功状态由 diagnostics 决定,不得因为存在 partial Content 就报告检查成功。

Annotation 不属于类型与求值

@... 是 EmbeddedExpression source scope 上的独立 metadata。它不属于:

  • Type。

  • Value。

  • Content。

  • Function argument。

  • Function result。

  • 类型推断或 Function execution。

#[Content]@concept,#important
#quote[Content]@citation,source="book"

Analysis 可以建立 Annotation index,供 Agent query、LSP 和 renderer source-range projection 使用。忽略 Annotation 后,typed tree 与 evaluation Content 必须保持不变:

type_of(erase_annotations(document)) == type_of(document)
eval(erase_annotations(document)) == eval(document)

Annotation 的 delimiter nesting 属于 syntax well-formedness,而不是类型兼容规则。

当前迁移边界

当前代码已实现:

  • Markup/Code mode-aware syntax tree 与一般 EmbeddedExpression。

  • literal Expression、Type、Value、Optional。

  • Function signature 与 argument binding。

  • trailing Content lowering。

  • built-in Function 返回 Content。

  • String/None 到 Content 的统一插入检查。

  • Annotation 从 evaluator 完全移出:经 syntax 层 Parse::annotations() 暴露,不再进入 EvaluationStructuredDocument

  • 注册时 signature 校验:default 满足参数类型、result == Content、trailing Content 参数已声明。

  • Document 的静态 Content type checking:notist-analysis 的 check pass 做名称解析、argument binding 检查和 Markup 插入检查,不执行 Function runtime;notist check 与 LSP 共享这套静态诊断。

但尚未完成目标模型中的:

  • 一般 Value-returning Function 及其 result runtime validation(当前 Registry 强制 result == Content)。

  • 跨 Module 的 Function name resolution(静态检查目前只解析 built-in 签名)。

  • analysis 侧统一的 Annotation index(目前由 syntax 层按需提供)。

迁移已按"先建立 mode-aware syntax tree 和显式错误恢复,再将 binder 扩展为 Typed Document analysis"完成第一阶段;不应继续在 flat syntax vectors 上增加新的 Code feature。

非本阶段类型系统

以下能力不属于当前双 Mode 规范:

  • let、变量和 lexical scope。

  • 一般 unary/binary Expression。

  • Array、Dict、record、union 和用户定义类型。

  • Function Type、Function Value 和 User Function。

  • 控制流与完整类型推断。

  • 跨 Module value import。

  • Annotation 作为 Value 或 Content metadata。

以后扩展这些能力时,应保持两个边界稳定:CodeExpression 在插入 Markup 前具有可检查的 Type;Document 最终仍静态检查并求值为 Content。