Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions error_cases/array_param/mutate_correct.kv

This file was deleted.

14 changes: 0 additions & 14 deletions error_cases/array_param/mutate_literal.kv

This file was deleted.

1 change: 0 additions & 1 deletion error_cases/recursion_error/stack_overflow.kv
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@ rwfunc f() -> () {
}

rwfunc test() -> () {

f()
}
6 changes: 0 additions & 6 deletions error_cases/runtime_error/bootstrap_missing.kv

This file was deleted.

4 changes: 0 additions & 4 deletions layout/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,6 @@ impl FuncSig {
sb
}

pub fn param_names(&self) -> Vec<String> {
self.params.iter().map(|p| p.name.clone()).collect()
}

/// 参数 langtype 列表(读参在前、写参在后),落盘于 rwir/rwfunc body。
/// 末读参尾缀 `...` 是签名层变参标记:此处剥离,langtype 串保持纯净(变参落 dynamic 字节)。
pub fn langtype_list(&self) -> Vec<String> {
Expand Down
20 changes: 18 additions & 2 deletions layout/src/langtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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/<name>`,使 kv 源可写 `x:Node` / `[int64]·Node`,runtime 恒收到完整 `/lib/…` 路径。
/// 已 `/` 开头或 known kind 原样返回。mapexpr 只对 value 递归展开(key 恒 `[…]` 非 struct)。
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -435,8 +453,6 @@ mod tests {
"[2,]float32",
"[,2]float32",
"[2 3]float32",
"*int64",
"@int64",
"int64*",
"float64|",
"int ",
Expand Down
107 changes: 85 additions & 22 deletions layout/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ impl Parser {
}
self.check_param_types(&decl.sig);
self.check_variadic(&decl.sig);
self.check_param_dup(&decl.sig);
decl
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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());
}
}

Expand Down Expand Up @@ -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() {
Expand All @@ -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"),
Expand All @@ -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/<name>`,显示时剥回原名)。
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(),
});
}
}

// ── 辅助 ─────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -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/<name>`);不判种类名;
/// 其余(非字面量右值如 `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 {
Expand Down
115 changes: 115 additions & 0 deletions layout/tests/kindexpr_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,118 @@ fn reject_malformed_type_expression() {
"expected errors for malformed type"
);
}

fn errors(src: &str) -> Vec<String> {
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::<String>::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::<String>::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::<String>::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::<String>::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::<String>::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::<String>::new());
}
Loading
Loading