diff --git a/error_cases/array_param/mutate_correct.kv b/error_cases/array_param/mutate_correct.kv deleted file mode 100644 index e4ec5401..00000000 --- a/error_cases/array_param/mutate_correct.kv +++ /dev/null @@ -1,16 +0,0 @@ -// ✅ 正确:数组在写参位置,函数返回修改后的数组。 -// expected: -// has no type annotation -rwfunc mutate() -> (a) { - arr:[]int64 = [10, 20] - 99 -> xv·set(arr, 0, 99) // set 返回修改后的数组 - 1 -> xv·set(arr, 1, 1) - arr -> a // 返回修改后的数组给调用方 -} - -rwfunc test() -> () { - mutate() -> result - xv·at(result, 0) -> v0 - xv·at(result, 1) -> v1 - println(v0, v1) // 99 1 -} diff --git a/error_cases/array_param/mutate_literal.kv b/error_cases/array_param/mutate_literal.kv deleted file mode 100644 index d4aff1a5..00000000 --- a/error_cases/array_param/mutate_literal.kv +++ /dev/null @@ -1,14 +0,0 @@ -// ❌ 错误:数组字面量放读参位置,函数内写 a[k] = v 被 fix-027 拒绝。 -// expected: -// read param "a" cannot be used as write slot -// kvlang 铁律:读参只读。要修改数组,必须放写参位置。 -// 正确: rwfunc mutate() -> (a) { array(10,20) -> a; 99 -> a[0]; ... } -rwfunc mutate(a) -> () { - 99 -> a[0] // ❌ a 是读参,a[0] 展开为 kv·set(a,0,99) -> a,写入读参槽被拒绝 -} - -rwfunc test() -> () { - - a:[]int64 = [10, 20] - mutate(a) -} diff --git a/error_cases/recursion_error/stack_overflow.kv b/error_cases/recursion_error/stack_overflow.kv index 11303aa3..de406a01 100644 --- a/error_cases/recursion_error/stack_overflow.kv +++ b/error_cases/recursion_error/stack_overflow.kv @@ -5,6 +5,5 @@ rwfunc f() -> () { } rwfunc test() -> () { - f() } diff --git a/error_cases/runtime_error/bootstrap_missing.kv b/error_cases/runtime_error/bootstrap_missing.kv deleted file mode 100644 index 548f6f11..00000000 --- a/error_cases/runtime_error/bootstrap_missing.kv +++ /dev/null @@ -1,6 +0,0 @@ -// expected: -// error: NameError: rwir/rwfunc not found: absent -// NameError: rwir/rwfunc not found: absent -rwfunc test() -> () { - absent() -} diff --git a/layout/src/ast.rs b/layout/src/ast.rs index 51c767f9..93799c60 100644 --- a/layout/src/ast.rs +++ b/layout/src/ast.rs @@ -105,10 +105,6 @@ impl FuncSig { sb } - pub fn param_names(&self) -> Vec { - self.params.iter().map(|p| p.name.clone()).collect() - } - /// 参数 langtype 列表(读参在前、写参在后),落盘于 rwir/rwfunc body。 /// 末读参尾缀 `...` 是签名层变参标记:此处剥离,langtype 串保持纯净(变参落 dynamic 字节)。 pub fn langtype_list(&self) -> Vec { diff --git a/layout/src/langtype.rs b/layout/src/langtype.rs index 12667ad2..8147d7eb 100644 --- a/layout/src/langtype.rs +++ b/layout/src/langtype.rs @@ -198,6 +198,19 @@ pub fn valid_langtype(expr: &str) -> bool { !expr.is_empty() && expr.split('|').all(valid_atom) } +/// 是否只由**已知种类名**(或 `any`)构成——即「标量字面量可写入的类型」。 +/// `*`/`@` 前缀先剥(源码传递方式,不是类型本体);structref(`/lib/…`)与形状、mapexpr 皆否。 +/// 用途见 `parser`(局部声明的写目标、struct 字段默认值):裸名 `int`/`intg64` 经 +/// [`expand_struct_refs`] 变成 `/lib/int` 后,正是靠这条落网——它**不是**种类名,标量写不进去 +/// (见 [[文法与合法性]])。 +pub fn is_plain_kind(ty: &str) -> bool { + !ty.is_empty() + && ty.split('|').all(|a| { + let a = a.trim_start_matches(['*', '@']); + a == "any" || known_kind(a) + }) +} + /// 隐式 struct 名解析:把 langtype 中裸 struct 名(非 known kind / any 的标识符)展开为 /// `/lib/`,使 kv 源可写 `x:Node` / `[int64]·Node`,runtime 恒收到完整 `/lib/…` 路径。 /// 已 `/` 开头或 known kind 原样返回。mapexpr 只对 value 递归展开(key 恒 `[…]` 非 struct)。 @@ -367,6 +380,11 @@ mod tests { "/lib/geom/Point", "/lib/Node", "/lib/Point|/lib/Node", + // `*`/`@` 是**源码**前缀(layout 剥离落 head.ref),书于类型标注最前——合法(见 [[文法与合法性]])。 + "*int64", + "@int64", + "@[256,256]uint8", + "*[int32,int32]·[]char/utf8", ] { assert!(valid_langtype(e), "{e} should be valid"); } @@ -435,8 +453,6 @@ mod tests { "[2,]float32", "[,2]float32", "[2 3]float32", - "*int64", - "@int64", "int64*", "float64|", "int ", diff --git a/layout/src/parser.rs b/layout/src/parser.rs index e5da0afd..0653677d 100644 --- a/layout/src/parser.rs +++ b/layout/src/parser.rs @@ -380,6 +380,7 @@ impl Parser { } self.check_param_types(&decl.sig); self.check_variadic(&decl.sig); + self.check_param_dup(&decl.sig); decl } @@ -420,7 +421,14 @@ impl Parser { let mut default = None; if self.peek().kind == Kind::Arrow && self.peek().value == "=" { self.advance(); + let pos = self.peek().pos; default = self.parse_pratt(0); + // 字段默认值同样是字面量给的类型(`f:int=0` 的 `0` 即 int64),标注须是已知种类名。 + if default.as_ref().is_some_and(expr_is_scalar_lit) + && !super::langtype::is_plain_kind(&ty) + { + self.push_unknown_type("struct field", &fname, &ty, pos); + } } fields.push(Field { name: fname, @@ -691,24 +699,34 @@ impl Parser { } } + /// 参数名在函数内**全局唯一**(见 [[函数]] 的参数同名规则):读参列表内、写参列表内、读写之间 + /// 均不得同名——变量名即指针,同名即同址。**调用**时不受限:同一变量可同时占读槽与写槽 + /// (`inc(x) -> x` 合法)。 fn check_param_dup(&mut self, sig: &FuncSig) { - let mut seen = std::collections::HashSet::new(); - for name in sig.param_names() { - seen.insert(name); + let mut reads = std::collections::HashSet::new(); + let mut writes = std::collections::HashSet::new(); + let err = |p: &mut Self, msg: String| { + p.errors.push(Diagnostic { + pos: Pos { line: 0, col: 0 }, + message: msg, + warn: false, + info: false, + source: String::new(), + src_file: String::new(), + src_name: String::new(), + }); + }; + for p in &sig.params { + if !reads.insert(p.name.as_str()) { + err(self, format!("func {}: duplicate param {:?} in read-params — read-params, write-params and their union must all be name-unique (a name is an address)", sig.name, p.name)); + } } - for ret in &sig.returns { - if seen.contains(&ret.name) { - self.errors.push(Diagnostic { - pos: Pos { line: 0, col: 0 }, - message: format!("func {}: param {:?} appears in both read-params and write-params — a param is either read-only or write-only, pick one", sig.name, ret.name), - warn: false, - info: false, - source: String::new(), - src_file: String::new(), - src_name: String::new(), - }); + for r in &sig.returns { + if reads.contains(r.name.as_str()) { + err(self, format!("func {}: param {:?} appears in both read-params and write-params — a param is either read-only or write-only, pick one", sig.name, r.name)); + } else if !writes.insert(r.name.as_str()) { + err(self, format!("func {}: duplicate param {:?} in write-params — read-params, write-params and their union must all be name-unique (a name is an address)", sig.name, r.name)); } - seen.insert(ret.name.clone()); } } @@ -2177,12 +2195,7 @@ impl Parser { None => return, }; let expr_is_array = e.op == "array"; - // 字符串字面量恒一维 []char/编码(非标量),故排除;仅 int/float/bool 字面量算标量。 - let expr_is_scalar_lit = e.is_leaf() - && e.lit != ast::LitKind::LitNone - && e.lit != ast::LitKind::LitNil - && e.lit != ast::LitKind::LitString - && e.lit != ast::LitKind::LitRawString; + let scalar_lit = expr_is_scalar_lit(e); for (j, wt) in inst.write_types.iter().enumerate() { if wt.is_empty() { @@ -2199,7 +2212,7 @@ impl Parser { src_file: String::new(), src_name: String::new(), }); - } else if is_array_langtype(wt) && expr_is_scalar_lit { + } else if is_array_langtype(wt) && scalar_lit { self.errors.push(Diagnostic { pos: Pos { line: 0, col: 0 }, message: format!("write {name:?} declared {wt} but assigned a scalar literal"), @@ -2209,9 +2222,31 @@ impl Parser { src_file: String::new(), src_name: String::new(), }); + } else if !write_type_ok(wt, scalar_lit, expr_is_array) { + self.push_unknown_type("write", &name, wt, Pos { line: 0, col: 0 }); } } } + + /// 「标注收不住它收的字面量」的统一诊断:`int`/`intg64` 这类不是种类名的标注在此落网 + /// (裸名已被 [`super::langtype::expand_struct_refs`] 展开成 `/lib/`,显示时剥回原名)。 + fn push_unknown_type(&mut self, ctx: &str, name: &str, ty: &str, pos: Pos) { + let disp = ty.strip_prefix("/lib/").unwrap_or(ty); + self.errors.push(Diagnostic { + pos, + message: format!( + "{ctx} {name:?}: unknown type {disp:?} — 不是已知种类名,\ + kvlang 无 int/uint/float/num/char 家族简写,数字须带位宽\ + (int8/int16/int32/int64、uint8/uint16/uint32/uint64、float32/float64),\ + 字符须带编码(char/utf8、char/utf32、char/ascii)" + ), + warn: false, + info: false, + source: String::new(), + src_file: String::new(), + src_name: String::new(), + }); + } } // ── 辅助 ───────────────────────────────────────────────────────────── @@ -2263,6 +2298,34 @@ fn is_array_langtype(t: &str) -> bool { t.contains('[') } +/// 标量字面量:`1`/`1.5`/`true`。字符串字面量恒一维 `[]char/<编码>`(非标量),故排除。 +fn expr_is_scalar_lit(e: &Expr) -> bool { + e.is_leaf() + && e.lit != ast::LitKind::LitNone + && e.lit != ast::LitKind::LitNil + && e.lit != ast::LitKind::LitString + && e.lit != ast::LitKind::LitRawString +} + +/// 字面量右值的标注必须收得住该字面量,**按右值形态分三类**(见 [[文法与合法性]]): +/// `1`/`1.5`/`true`(标量字面量)→ 标注须是已知种类名或 `any`——类型由字面量自己给出; +/// `[1,2]`(数组字面量) → 标注须是合法 langtype(`[2]int64`); +/// `{…}`(结构/容器字面量) → 标注是 struct 名或 map langtype,**裸名在此合法**(它就是 +/// struct 名,已展开成 `/lib/`);不判种类名; +/// 其余(非字面量右值如 `x:int = y`)无从推断 → 放行。 +/// 前两类里裸名 `int`/`intg64` 已由 [`super::langtype::expand_struct_refs`] 变成 `/lib/int`, +/// 既非种类名也非合法形状 → 在此落网。layout 只判种类名,不查 kvspace 里 `/lib/…` 有无原型 +/// (存在性/字段一致性归 runtime:`x:int = {}` 放行,runtime 报 "/lib/int is not a struct type")。 +fn write_type_ok(wt: &str, scalar_lit: bool, array_lit: bool) -> bool { + if scalar_lit { + super::langtype::is_plain_kind(wt) + } else if array_lit { + super::langtype::valid_langtype(wt) + } else { + true + } +} + /// 定长数组类型:`[N]T` / `[d0,d1]T`,方括号内全为正整数(非空、无 `?`)。 /// `[]T`(动态一维)与 `[?,N]T`(含未知维)不算。 fn is_fixed_dim_array(t: &str) -> bool { diff --git a/layout/tests/kindexpr_test.rs b/layout/tests/kindexpr_test.rs index 749f78b2..c0885176 100644 --- a/layout/tests/kindexpr_test.rs +++ b/layout/tests/kindexpr_test.rs @@ -24,3 +24,118 @@ fn reject_malformed_type_expression() { "expected errors for malformed type" ); } + +fn errors(src: &str) -> Vec { + let (_, diags) = parser::parse_code(src).unwrap(); + diags + .iter() + .filter(|d| !d.warn && !d.info) + .map(|d| d.message.clone()) + .collect() +} + +/// 字面量右值的类型由字面量自己给出(`1` 即 int64),写目标标注必须是已知种类名—— +/// 裸名 `int`/`intg64` 经 expand_struct_refs 成了 `/lib/int`,不是种类名,须按非法种类拒绝 +/// (见 spec 类型系统/文法与合法性)。layout 只判种类名,不查 kvspace 原型是否存在。 +#[test] +fn reject_non_kind_annotation_on_literal() { + for ty in [ + "int", "intg64", "uint", "float", "num", "char", "int4", "string", "[]int", "[2]float", + ] { + let rhs = if ty.starts_with('[') { "[1]" } else { "1" }; + let src = format!("rwfunc f() -> () {{\n\tx:{ty} = {rhs}\n}}\n"); + let msgs = errors(&src); + assert!( + msgs.iter() + .any(|m| m.contains(&format!("unknown type {ty:?}"))), + "{src} should reject {ty:?}, got {msgs:?}" + ); + } +} + +/// 已知种类名、`any`,以及容器/结构字面量的对应标注,都不得误伤。 +#[test] +fn accept_known_kinds_on_literal() { + let cases = [ + "\tx:int64 = 7\n", + "\tx:float64 = 7\n", + "\tx:any = 7\n", + "\ts:char/utf8 = \"hi\"\n", + "\tx:[]int64 = [1, 2]\n", + "\tx:[2]float32 = [1.0, 2.0]\n", + "\tp:Point = {x=1}\n", + "\tm:[]char/utf8·int64 = {}\n", + ]; + for body in cases { + let src = format!("struct Point {{\n\tx:int64=0\n}}\nrwfunc f() -> () {{\n{body}}}\n"); + assert_eq!(errors(&src), Vec::::new(), "src: {src}"); + } +} + +/// 非字面量右值无从推断类型,放行(不因标注而报错)。 +#[test] +fn accept_non_literal_rhs() { + let src = "rwfunc f() -> () {\n\t7 -> y\n\tx:int = y\n}\n"; + assert_eq!(errors(src), Vec::::new()); +} + +/// struct 字段默认值是同一构造(标注 + 字面量),同一套判定。 +#[test] +fn reject_non_kind_struct_field_default() { + for ty in ["int", "intg64", "float", "uint"] { + let src = format!("struct S {{\n\tf:{ty}=0\n}}\n"); + let msgs = errors(&src); + assert!( + msgs.iter() + .any(|m| m.contains("struct field") && m.contains(&format!("unknown type {ty:?}"))), + "{src} should reject {ty:?}, got {msgs:?}" + ); + } + // 合法字段默认值不得误伤:种类名、字符串、指针空值(None 非标量,无从推断故放行)。 + let ok = "struct S {\n\tf:int64=0\n\tg:float64=0.0\n\th:bool=false\n\tn:[]char/utf32=\"a\"\n}\nstruct N {\n\tv:int64=0\n\tnext:*N=None\n}\n"; + assert_eq!(errors(ok), Vec::::new()); +} + +/// 大括号右值把标注归到「struct 名」一类,裸名在那里**合法**:`x:int = {}` layout 放行, +/// `/lib/int` 是不是 struct 由 runtime 判(`x:int = 1` 才由 layout 拒)。 +#[test] +fn accept_bare_name_as_struct_on_brace_literal() { + let src = "rwfunc f() -> () {\n\tx:int = {}\n\ty:intg64 = {a=1}\n\tp:Point = {}\n}\nstruct Point {\n\tx:int64=0\n}\n"; + assert_eq!(errors(src), Vec::::new()); +} + +/// 参数名在函数内全局唯一:读参列表内、写参列表内、读写之间均不得同名(见 spec layout语义/函数)。 +/// **调用**时不受限——同一变量可同时占读槽与写槽(`inc(x) -> x`),那是调用点的事,不进本检查。 +#[test] +fn reject_duplicate_param_names() { + let cases = [ + ( + "rwfunc f(a:int64, a:int64) -> () {\n}\n", + "duplicate param \"a\" in read-params", + ), + ( + "rwfunc f() -> (a:int64, a:int64) {\n}\n", + "duplicate param \"a\" in write-params", + ), + ( + "rwfunc f(a:int64) -> (a:int64) {\n}\n", + "appears in both read-params and write-params", + ), + ]; + for (src, want) in cases { + let msgs = errors(src); + assert!( + msgs.iter().any(|m| m.contains(want)), + "{src} should report {want:?}, got {msgs:?}" + ); + } +} + +/// 合法的参数名组合不得误伤,调用点同名(读写槽同一变量)也不受影响。 +#[test] +fn accept_unique_param_names() { + let sig = "rwfunc f(a:int64, b:int64) -> (c:int64, d:int64) {\n\ta + b -> c\n\t0 -> d\n}\n"; + assert_eq!(errors(sig), Vec::::new()); + let call = "rwfunc inc(a:int64) -> (b:int64) {\n\ta + 1 -> b\n}\nrwfunc f() -> () {\n\t5 -> x\n\tinc(x) -> x\n}\n"; + assert_eq!(errors(call), Vec::::new()); +} diff --git "a/stdlib/kvlang/spec/03-\347\261\273\345\236\213\347\263\273\347\273\237/07-\346\226\207\346\263\225\344\270\216\345\220\210\346\263\225\346\200\247.kv" "b/stdlib/kvlang/spec/03-\347\261\273\345\236\213\347\263\273\347\273\237/07-\346\226\207\346\263\225\344\270\216\345\220\210\346\263\225\346\200\247.kv" index c9484ba6..0f796b39 100644 --- "a/stdlib/kvlang/spec/03-\347\261\273\345\236\213\347\263\273\347\273\237/07-\346\226\207\346\263\225\344\270\216\345\220\210\346\263\225\346\200\247.kv" +++ "b/stdlib/kvlang/spec/03-\347\261\273\345\236\213\347\263\273\347\273\237/07-\346\226\207\346\263\225\344\270\216\345\220\210\346\263\225\346\200\247.kv" @@ -58,14 +58,27 @@ lib kvlang/spec/类型系统/文法与合法性 { 实参,旧写法末读参尾缀 `...`)是**签名**层的 arity 构造,**不属于类型表达式**,见 [[函数]];它落成 签名行锚点 `[0,0]` 的 `dynamic` 字节,参数 langtype 子槽保持纯净(不含 `...`)。 -**合法性校验**(layout 与 runtime 一致):每个 atom 的基须为 `known` 种类名、`any`,或**已声明的 -struct 原型路径**(`/lib/`)。裸标识符只有在能解析为 struct 原型时才可作 atom ——拼错的种类名 -(如 `intg64`)、家族简写(`int` 等)**必须**按非法种类拒绝,**不得**静默当成 structref 放行。键须为 -`[]char/<编码>` 或全标量元组。非法示例及拒因: +**合法性校验**(layout 与 runtime 一致):每个 atom 的基须为 `known` 种类名、`any`,或 struct 原型 +路径(`/lib/`)。**「标注 + 字面量」处**(局部声明 `x:T = …`、struct 字段默认值 `f:T=…`)由 +**右值形态**判标注——右值是什么,标注就该是哪一类: + +| 右值 | 标注须是 | 裸标识符 | +|------|---------|---------| +| 标量字面量 `1` `1.5` `true` | 已知种类名或 `any`(`1` 即 `int64`) | 非法:`int`/`intg64` 不是种类名 | +| 数组字面量 `[1,2]` | 合法 langtype(`[2]int64`) | 非法(元素非种类名) | +| 结构/容器字面量 `{…}` | struct 名或 map langtype | **合法**:在此它就是 struct 名 | + +拼错的种类名(如 `intg64`)、家族简写(`int` 等)**仅在前两行**按非法种类拒绝,**不得**静默当成 +structref 放行——裸名已被隐式展开为 `/lib/`,正是靠「不是种类名」落网。**layout 只判右值形态与 +种类名,不查 kvspace**:`/lib/…` 有无原型、是不是 struct,归 **runtime** 判——`x:int = {}` layout +放行,runtime 报 `TypeError: /lib/int is not a struct type`(见 [[langtype语义类型]])。非字面量右值 +(`x:int = y`)无从推断,layout 放行。键须为 `[]char/<编码>` 或全标量元组。非法示例及拒因: | 非法 | 拒因 | |------|------| | `int` `float` `char` | 家族简写,非种类名 | +| `intg64` | 拼错的种类名;标量/数组字面量的标注须是已知种类名 | +| `[]int` `[2]float` | 数组字面量的标注不是合法 langtype(元素非种类名) | | `int64*` | 后缀 `*` 位置非法;`*`/`@` 前缀**仅**可书写在源码类型标注最前,layout 剥离落 `ref` 字节,wire langtype 不含前缀 | | `char/utf8·int64` | `char/*` 只能作 `[]char/*` 字符串键,不可裸作键(裸键仅限标量 `scalar`,如 `int64·int64` 合法) | | `[]int32·int64` | map 字符串键的编码非 `char/*` | diff --git "a/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/03-\345\207\275\346\225\260.kv" "b/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/03-\345\207\275\346\225\260.kv" index 8769dbdc..b8dc9e12 100644 --- "a/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/03-\345\207\275\346\225\260.kv" +++ "b/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/03-\345\207\275\346\225\260.kv" @@ -131,7 +131,9 @@ rwfunc inc(x:int64) -> (r:int64) { x -> local local = local + 1 local -> r } ## 参数同名规则 - **定义时**:读参列表内、写参列表内、以及读写列表之间均**不得**同名(变量名即指针,同名即同址)—— - `rwfunc f(A:int64) -> (A:int64)` 报 `param "A" appears in both read-params and write-params`。 + `rwfunc f(A:int64) -> (A:int64)` 报 `param "A" appears in both read-params and write-params`; + `rwfunc f(a:int64, a:int64)` 报 `duplicate param "a" in read-params`(写参列表内同理报 + `in write-params`)。 - **调用时**:同一变量**可**同时出现在读槽与写槽——`inc(x) -> x` 合法,读写独立解析、互不冲突。 "#### -> /lib/kvlang/spec/layout语义/函数 } diff --git "a/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/10-\345\270\270\350\247\201\350\257\212\346\226\255.kv" "b/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/10-\345\270\270\350\247\201\350\257\212\346\226\255.kv" index d082ed59..449334b1 100644 --- "a/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/10-\345\270\270\350\247\201\350\257\212\346\226\255.kv" +++ "b/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/10-\345\270\270\350\247\201\350\257\212\346\226\255.kv" @@ -8,7 +8,12 @@ lib kvlang/spec/layout语义/常见诊断 { - 读参被写——`read param "A" cannot be used as write slot (read params are read-only)`。 - 值容器按值传参——`param "p": 值容器类型只能按地址传递 —— 写 "*/lib/Point"`(值容器无值本体可拷,须带 `*`/`@`)。 -- 读写参同名——`param "A" appears in both read-params and write-params`。 +- 参数名重复(读参列表内、写参列表内、读写之间均不得同名,见 [[函数]])——读写之间 + `param "A" appears in both read-params and write-params`;列表内 `duplicate param "a" in read-params` + / `in write-params`。**调用**时不受限:`inc(x) -> x` 合法。 +- 标注不是种类名——`write "x": unknown type "int"` / `struct field "f": unknown type "intg64"`: + 标量字面量右值处标注须是已知种类名或 `any`,数组字面量右值处须是合法 langtype;**大括号右值 + 除外**——那里裸名是 struct 名,存在性归 runtime(见 [[文法与合法性]])。 - 变参位置非法——`variadic param "x" must be the last read-param` / `write-param "r" cannot be variadic`。 - 顶层控制流——`top-level while is not supported — wrap in main()`。