// Expect retcode = 0 // 语义分析:名字/类型/def 解析与推断。持有类型表、作用域树、属性表;AST 与诊断 // 为借用。comptime 求值经 eval seam 注入。 type .{ .Error, .Status } = @import("utils/error.spl"); type .{ .Vec } = @import("utils/vec.spl"); type .{ .Map, .hash_kind_usize } = @import("utils/map.spl"); type .{ .ByteView } = @import("utils/byte_view.spl"); type .{ .ByteBuffer } = @import("utils/byte_buffer.spl"); type .{ .append_usize } = @import("utils/fmt.spl"); type BigInt = @import("utils/mp.spl"); type Ast = @import("./ast.spl"); type .{ .Node } = @import("./ast.spl"); type .{ .Span, .span_none, .Code, .no_file } = @import("./diag.spl"); type Diag = @import("./diag.spl"); type .{ .Sources, .set_at } = @import("utils/source.spl"); type LangType = @import("./lang_type.spl"); type .{ .LangKind, .LangEntry, .LangMethod, .LangExtra } = @import("./lang_type.spl"); type TypeTable = @import("./type_table.spl"); type .{ .TypeNode, .TypeEntry, .Member, .AggKind, .LayoutMode } = @import("./type_table.spl"); type .{ .DefTable, .DefKind, .DefNode, .DeclState } = @import("./def.spl"); type .{ .Scope, .Symbol, .SymbolKind } = @import("./scope.spl"); type .{ .Attr, .AttrArg, .AttrArgKind } = @import("./attr.spl"); type .{ .Intrin, .IntrinInfo, .IntrinForm, .IntrinResult, .IntrinOps } = @import("./intrin.spl"); type .{ .Eval, .EvalOutcome, .EvalValue, .DeclSink } = @import("./eval.spl"); type .{ .hash_usize, .eq_usize } = @import("utils/hash.spl"); type .{ .Punct, .Token } = @import("./token.spl"); type .{ .Lexer } = @import("./lexer.spl"); type .{ .Parser } = @import("./parser.spl"); type .{ .set_new, .set_add, .set_drop } = @import("utils/source.spl"); // 模块加载接口(seam):由驱动提供;可为 null。 type LoadResult = enum { Ok: *Ast, Missing, } type Loader = struct { load: fn(*any, ByteView, ByteView, *Diag): LoadResult, user: *any, } type Sema = @self; ast: *Ast, // 借用 diag: *Diag, // 借用 sources: *Sources, // 借用,可为 null(解析 import 的 from 文件名) types: LangType, scope: Scope, attr: Attr, eval: Eval, loader: *Loader, // 借用,可为 null current_aggregate_lang: usize, current_fn_ret_tid: usize, current_loop_break_tid: usize, label_stack: Vec, // ByteView ir_to_lang: Map, // ir_type_id -> lang_id 缓存(剪枝 lang_from_ir 的线性扫描) type SemaResult = enum { Ok: Sema, Err: Error, } fn list_at(v: Vec, i: usize): usize { if i >= v.len { ret 0; } ret ((v.data as *usize) + i).*; } fn push_u(v: *Vec, x: usize) { Vec.push(v, &x as *u8); } fn push_view(v: *Vec, x: ByteView) { Vec.push(v, &x as *u8); } fn ref_vec(): Vec { ret Vec.blank(@sizeof(usize), @alignof(usize)); } fn ulist_vec(): Vec { ret Vec.blank(@sizeof(usize), @alignof(usize)); } fn node(self: *Sema, ref: usize): *Node { ret Ast.node(self.ast, ref); } fn def_node(self: *Sema, id: usize): *DefNode { ret LangType.def_node(&self.types, id); } fn ir_entry(self: *Sema, ir_id: usize): *TypeEntry { ret TypeTable.node(&self.types.ir, ir_id); } fn ir_of(self: *Sema, lang_id: usize): usize { ret LangType.get_ir_type(&self.types, lang_id); } fn lang_of(self: *Sema, id: usize): *LangEntry { ret LangType.lang_node(&self.types, id); } fn kind_is(self: *Sema, lang_id: usize, want: LangKind): bool { var e = LangType.lang_node(&self.types, lang_id); if e == null { ret false; } ret @enum2int(e.kind) == @enum2int(want); } fn is_infer(self: *Sema, tid: usize): bool { ret kind_is(self, tid, .Infer); } fn lang_from_ir(self: *Sema, ir: usize): usize { if ir == 0 { ret 0; } var cached: usize = 0; match Map.get(&self.ir_to_lang, &ir as *u8, &cached as *u8) { .Found => { ret cached; }, .NotFound => {}, } var i: usize = 1; while i < self.types.lang_table.len { var e = LangType.lang_node(&self.types, i); if e != null && e.ir_type_id == ir { Map.put(&self.ir_to_lang, &ir as *u8, &i as *u8); ret i; } i = i + 1; } ret 0; } fn make_bool(self: *Sema): usize { ret LangType.make_bool(&self.types); } fn make_unit(self: *Sema): usize { ret LangType.make_unit(&self.types); } fn make_never(self: *Sema): usize { ret LangType.make_never(&self.types); } fn make_any(self: *Sema): usize { ret LangType.make_any(&self.types); } fn make_infer(self: *Sema): usize { ret LangType.make_infer(&self.types); } fn ptr_bits(self: *Sema): usize { ret @sizeof(usize) * 8; } fn make_usize(self: *Sema): usize { ret LangType.make_int(&self.types, ptr_bits(self), false); } fn make_isize(self: *Sema): usize { ret LangType.make_int(&self.types, ptr_bits(self), true); } fn def_is_type(self: *Sema, id: usize): bool { ret LangType.def_is_type(&self.types, id); } fn def_state_is(d: *DefNode, want: DeclState): bool { ret @enum2int(d.state) == @enum2int(want); } fn diag(self: *Sema, span: Span, code: Code, msg: []u8) { Diag.err(self.diag, span, code, msg); } fn diag_name(self: *Sema, span: Span, code: Code, prefix: []u8, name: ByteView) { var b = ByteBuffer.new(); defer { ByteBuffer.drop(&b); } ByteBuffer.append(&b, prefix); ByteBuffer.append(&b, ByteView.as_slice(name)); Diag.err(self.diag, span, code, ByteBuffer.as_slice(b)); } fn diag_ty(self: *Sema, span: Span, code: Code, prefix: []u8, tid: usize) { var b = ByteBuffer.new(); defer { ByteBuffer.drop(&b); } ByteBuffer.append(&b, prefix); LangType.dump(&self.types, tid, &b); Diag.err(self.diag, span, code, ByteBuffer.as_slice(b)); } fn diag_ty2(self: *Sema, span: Span, code: Code, prefix: []u8, a: usize, b: usize) { var buf = ByteBuffer.new(); defer { ByteBuffer.drop(&buf); } ByteBuffer.append(&buf, prefix); LangType.dump(&self.types, a, &buf); ByteBuffer.append(&buf, " vs "); LangType.dump(&self.types, b, &buf); Diag.err(self.diag, span, code, ByteBuffer.as_slice(buf)); } fn new(ast: *Ast, diag: *Diag): SemaResult { var types: LangType = undefined; match LangType.new() { .Ok(v) => { types = v; }, .Err(error) => { ret .Err(error); }, } var scope: Scope = undefined; match Scope.new() { .Ok(v) => { scope = v; }, .Err(error) => { LangType.drop(&types); ret .Err(error); }, } var attr: Attr = undefined; match Attr.new() { .Ok(v) => { attr = v; }, .Err(error) => { Scope.drop(&scope); LangType.drop(&types); ret .Err(error); }, } var s = Sema { .ast = ast, .diag = diag, .sources = null, .types = types, .scope = scope, .attr = attr, .eval = Eval.new(null, diag, false), .loader = null, .current_aggregate_lang = 0, .current_fn_ret_tid = 0, .current_loop_break_tid = 0, .label_stack = Vec.blank(@sizeof(ByteView), @alignof(ByteView)), .ir_to_lang = Map.new(@sizeof(usize), @alignof(usize), @sizeof(usize), @alignof(usize), hash_kind_usize()), }; ret .Ok(s); } fn drop(self: *Sema) { Map.drop(&self.ir_to_lang); Vec.drop(&self.label_stack); Attr.drop(&self.attr); Scope.drop(&self.scope); LangType.drop(&self.types); } fn push_scope(self: *Sema) { var id = Scope.alloc(&self.scope); self.scope.current_scope = id; } fn pop_scope(self: *Sema) { var n = Scope.node(&self.scope, self.scope.current_scope); if n != null { self.scope.current_scope = n.parent; } } fn node_attr_raw(self: *Sema, n: *Node, raw: Vec) { var i: usize = 0; while i < raw.len { var a = node(self, list_at(raw, i)); if a != null { var id = resolve_attr_item(self, a); if id != 0 { push_u(&n.attrs, id); } } i = i + 1; } } fn resolve_attr_item(self: *Sema, a: *Node): usize { var name = ByteView.empty(); var cargs = ref_vec(); var found = false; match a.body { .TagItem(t) => { name = t.name; cargs = t.args; found = true; }, .ExternalTagItem(t) => { name = t.name; cargs = t.args; found = true; }, _ => {}, } if !found { ret 0; } var idx = Attr.lookup(&self.attr, ByteView.empty(), name); if idx == 0 { diag_name(self, a.span, .UnknownAttr, "unknown attribute `", name); ret 0; } var info = Attr.info_at(&self.attr, idx); if info == null { ret 0; } var nargs = cargs.len; if (nargs as i32) < info.min_args || (info.max_args >= 0 && (nargs as i32) > info.max_args) { diag(self, a.span, .UnknownAttr, "attribute arg count mismatch"); ret 0; } var args = Vec.blank(@sizeof(AttrArg), @alignof(AttrArg)); var i: usize = 0; while i < cargs.len { var arg = node(self, list_at(cargs, i)); if arg == null { i = i + 1; continue; } match arg.body { .ExprIdent(e) => { var av: AttrArg = .Str(e.name); Vec.push(&args, &av as *u8); }, .ExprStringLit(sl) => { var av: AttrArg = .Str(sl.data); Vec.push(&args, &av as *u8); }, .ExprIntLit(iv) => { var num: i64 = 0; BigInt.to_i64(&num, iv.value); var av: AttrArg = .Int(num); Vec.push(&args, &av as *u8); }, _ => { diag(self, arg.span, .UnknownAttr, "unsupported attribute arg"); }, } i = i + 1; } var id = Attr.alloc(&self.attr, idx, args); ret id; } fn resolve_attrs(self: *Sema, n: *Node) { if n.attrs.len != 0 { ret; } match n.body { .FnDecl(&x) => { node_attr_raw(self, n, x.tag_list); }, .VarDecl(&x) => { node_attr_raw(self, n, x.attr_list); }, .ConstDecl(&x) => { node_attr_raw(self, n, x.attr_list); }, .TypeDecl(&x) => { node_attr_raw(self, n, x.attr_list); }, .MemberDecl(&x) => { node_attr_raw(self, n, x.attr_list); }, .ParamDecl(&x) => { node_attr_raw(self, n, x.attr_list); }, .TopLevelBlock(&x) => { node_attr_raw(self, n, x.tag_list); }, _ => {}, } } fn build_aggregate(self: *Sema, te: *Node, owner_def_id: usize): usize { var kind_name = ByteView.empty(); match te.body { .TypeAggregate(&x) => { kind_name = x.kind_name; }, _ => {}, } var lk: LangKind = .Struct; var tk: TypeNode = .Struct(.{ .mode = .Auto, .fixed_align_bits = 0, .tag_type = 0, .variants = ref_vec() }); if ByteView.eq(kind_name, ByteView.from_slice("union")) { lk = .Union; } else if ByteView.eq(kind_name, ByteView.from_slice("enum")) { lk = .Enum; } else if ByteView.eq(kind_name, ByteView.from_slice("shape")) { lk = .Shape; } var agg_ir = LangType.reserve_ir(&self.types); var ln = LangEntry { .hash_next = 0, .kind = lk, .ir_type_id = agg_ir, .methods = LangType.empty_methods(), .extra = .None, }; var agg_lang = LangType.push(&self.types, &ln); if owner_def_id != 0 { var d = def_node(self, owner_def_id); if d != null { d.lang_id = agg_lang; } } var saved = self.current_aggregate_lang; self.current_aggregate_lang = agg_lang; // 聚合体自成命名空间:成员类型可引用同体声明的嵌套类型(裸名)。 var saved_scope = self.scope.current_scope; var agg_scope = Scope.alloc(&self.scope); self.scope.current_scope = agg_scope; var members = Vec.blank(@sizeof(Member), @alignof(Member)); var methods = Vec.blank(@sizeof(LangMethod), @alignof(LangMethod)); var mlist = ref_vec(); match te.body { .TypeAggregate(&x) => { mlist = x.members; }, _ => {}, } // 第一遍:登记嵌套类型/函数的 def,挂进 methods 与聚合作用域。成员类型可能引用它们, // 必须早于成员类型推断,否则限定名(经 methods)与裸名(经作用域)都解析不到。 var i: usize = 0; while i < mlist.len { var mref = list_at(mlist, i); var mn = node(self, mref); if mn != null { match mn.body { .FnDecl(fd) => { var fn_def = mn.resolved_def_id; if fn_def == 0 { fn_def = LangType.alloc_def(&self.types, .Fn, fd.name, mn.span, self.ast as *any, mref); mn.resolved_def_id = fn_def; } var fn_d = def_node(self, fn_def); if fn_d != null { fn_d.owner_def = owner_def_id; } var meth = LangMethod { .name = fd.name, .def_id = fn_def }; Vec.push(&methods, &meth as *u8); Scope.insert(&self.scope, agg_scope, Symbol { .name = fd.name, .kind = .Fn, .node = fn_def }); }, .TypeDecl(td) => { var tpat = node(self, td.pattern); if tpat != null && is_pattern_ident(tpat) { var tname = pattern_name(tpat); var tdef = mn.resolved_def_id; if tdef == 0 { var dk: DefKind = .Scalar; var tek = node(self, td.type_expr); if tek != null && is_type_aggregate(tek) { dk = .Agg; } tdef = LangType.alloc_def(&self.types, dk, tname, mn.span, self.ast as *any, mref); mn.resolved_def_id = tdef; } var meth = LangMethod { .name = tname, .def_id = tdef }; Vec.push(&methods, &meth as *u8); Scope.insert(&self.scope, agg_scope, Symbol { .name = tname, .kind = .Type, .node = tdef }); } }, _ => {}, } } i = i + 1; } var lslot = LangType.lang_node(&self.types, agg_lang); if lslot != null { Vec.drop(&lslot.methods); lslot.methods = methods; } // 第二遍:成员(字段/变体)类型推断;此时嵌套名字已可解析。 i = 0; while i < mlist.len { var mref = list_at(mlist, i); var mn = node(self, mref); if mn != null { match mn.body { .MemberDecl(md) => { var mtid: usize = 0; if md.type_expr != 0 { mtid = infer(self, md.type_expr); } var mm = Member { .name = md.name, .type_id = ir_of(self, mtid), .ast_node_ref = mref }; Vec.push(&members, &mm as *u8); }, _ => {}, } } i = i + 1; } self.scope.current_scope = saved_scope; self.current_aggregate_lang = saved; // IR 节点 match lk { .Enum => { var tag_ir = TypeTable.make_int(&self.types.ir, 32, true); tk = .Enum(.{ .mode = .Auto, .fixed_align_bits = 0, .tag_type = tag_ir, .variants = members }); }, .Union => { tk = .Union(.{ .mode = .Auto, .fixed_align_bits = 0, .tag_type = 0, .variants = members }); }, .Shape => { tk = .Shape(.{ .mode = .Auto, .fixed_align_bits = 0, .tag_type = 0, .variants = members }); }, _ => { tk = .Struct(.{ .mode = .Auto, .fixed_align_bits = 0, .tag_type = 0, .variants = members }); }, } TypeTable.define(&self.types.ir, agg_ir, &tk); ret agg_lang; } fn build_container_aggregate(self: *Sema, n: *Node, owner_def_id: usize): usize { var agg_ir = LangType.reserve_ir(&self.types); var ln = LangEntry { .hash_next = 0, .kind = .Struct, .ir_type_id = agg_ir, .methods = LangType.empty_methods(), .extra = .None, }; var agg_lang = LangType.push(&self.types, &ln); if owner_def_id != 0 { var d = def_node(self, owner_def_id); if d != null { d.lang_id = agg_lang; } } var saved = self.current_aggregate_lang; self.current_aggregate_lang = agg_lang; var members = Vec.blank(@sizeof(Member), @alignof(Member)); var methods = Vec.blank(@sizeof(LangMethod), @alignof(LangMethod)); var decls = ref_vec(); match n.body { .ContainerMembers(x) => { decls = x.declarations; }, _ => {}, } var i: usize = 0; while i < decls.len { var dref = list_at(decls, i); var d = node(self, dref); if d != null { match d.body { .MemberDecl(md) => { var mtid: usize = 0; if md.type_expr != 0 { mtid = infer(self, md.type_expr); } var mm = Member { .name = md.name, .type_id = ir_of(self, mtid), .ast_node_ref = dref }; Vec.push(&members, &mm as *u8); }, .FnDecl(fd) => { var fn_def = d.resolved_def_id; if fn_def != 0 { var fn_d = def_node(self, fn_def); if fn_d != null { fn_d.owner_def = owner_def_id; } var meth = LangMethod { .name = fd.name, .def_id = fn_def }; Vec.push(&methods, &meth as *u8); } }, .TypeDecl(td) => { var tdef = d.resolved_def_id; if tdef != 0 { var tpat = node(self, td.pattern); if tpat != null && is_pattern_ident(tpat) { var meth = LangMethod { .name = pattern_name(tpat), .def_id = tdef }; Vec.push(&methods, &meth as *u8); } } }, .VarDecl(vd) => { container_var_method(self, vd.pattern, &methods); }, .ConstDecl(cd) => { container_var_method(self, cd.pattern, &methods); }, _ => {}, } } i = i + 1; } self.current_aggregate_lang = saved; var tk: TypeNode = .Struct(.{ .mode = .Auto, .fixed_align_bits = 0, .tag_type = 0, .variants = members }); TypeTable.define(&self.types.ir, agg_ir, &tk); var lslot = LangType.lang_node(&self.types, agg_lang); if lslot != null { Vec.drop(&lslot.methods); lslot.methods = methods; } ret agg_lang; } fn container_var_method(self: *Sema, pat_ref: usize, methods: *Vec) { var pat = node(self, pat_ref); if pat == null || !is_pattern_ident(pat) { ret; } var vdef = pat.resolved_def_id; if vdef == 0 { ret; } var meth = LangMethod { .name = pattern_name(pat), .def_id = vdef }; Vec.push(methods, &meth as *u8); } fn is_pattern_ident(n: *Node): bool { match n.body { .PatternIdent(&x) => { ret true; }, _ => { ret false; } } ret false; } fn is_type_aggregate(n: *Node): bool { match n.body { .TypeAggregate(&x) => { ret true; }, _ => { ret false; } } ret false; } fn is_param_decl(n: *Node): bool { match n.body { .ParamDecl(&x) => { ret true; }, _ => { ret false; } } ret false; } fn is_type_decl(n: *Node): bool { match n.body { .TypeDecl(&x) => { ret true; }, _ => { ret false; } } ret false; } fn is_pattern_field(n: *Node): bool { match n.body { .PatternField(&x) => { ret true; }, _ => { ret false; } } ret false; } fn is_expr_string_lit(n: *Node): bool { match n.body { .ExprStringLit(&x) => { ret true; }, _ => { ret false; } } ret false; } fn is_expr_ident(n: *Node): bool { match n.body { .ExprIdent(&x) => { ret true; }, _ => { ret false; } } ret false; } fn is_expr_int_lit(n: *Node): bool { match n.body { .ExprIntLit(&x) => { ret true; }, _ => { ret false; } } ret false; } fn pattern_name(pat: *Node): ByteView { match pat.body { .PatternIdent(&x) => { ret x.name; }, _ => { ret ByteView.empty(); }, } } fn sema_import(self: *Sema, n: *Node, args: Vec): usize { if self.loader == null { diag(self, n.span, .Internal, "@import: no module loader configured"); ret 0; } if args.len != 1 { diag(self, n.span, .IntrinArgCount, "@import expects 1 argument"); ret 0; } var arg = node(self, list_at(args, 0)); if arg == null || !is_expr_string_lit(arg) { diag(self, n.span, .TypeMismatch, "@import expects a string literal"); ret 0; } var path = ByteView.empty(); match arg.body { .ExprStringLit(sl) => { path = sl.data; }, _ => {}, } var fname = ByteView.empty(); if self.sources != null && n.span.file != no_file() { var src = set_at(self.sources, n.span.file); if src != null { fname = src.fname; } } var loaded = self.loader.load(self.loader.user, fname, path, self.diag); var mod_ast: *Ast = null; match loaded { .Ok(a) => { mod_ast = a; }, .Missing => { ret 0; }, } if mod_ast == null { ret 0; } var mod_root = Ast.node(mod_ast, mod_ast.root); if mod_root == null { ret 0; } var cm_ref: usize = 0; match mod_root.body { .Root(r) => { cm_ref = r.members; }, _ => {}, } if cm_ref == 0 { ret 0; } var cm = Ast.node(mod_ast, cm_ref); if cm != null && cm.resolved_def_id != 0 { var d = LangType.def_node(&self.types, cm.resolved_def_id); if d != null && d.lang_id != 0 { ret d.lang_id; } } var saved_ast = self.ast; var saved_root = self.scope.root_scope; var saved_cur = self.scope.current_scope; var saved_agg = self.current_aggregate_lang; self.ast = mod_ast; self.scope.root_scope = 0; self.scope.current_scope = 0; self.current_aggregate_lang = 0; run_module(self, cm_ref); var t: usize = 0; var cm_after = Ast.node(mod_ast, cm_ref); if cm_after != null && cm_after.resolved_def_id != 0 { var d = LangType.def_node(&self.types, cm_after.resolved_def_id); if d != null { t = d.lang_id; } } self.ast = saved_ast; self.scope.root_scope = saved_root; self.scope.current_scope = saved_cur; self.current_aggregate_lang = saved_agg; ret t; } fn eval_decl(self: *Sema, def_id: usize): usize { var d = def_node(self, def_id); if d == null { ret 0; } if def_state_is(d, .Resolved) || def_state_is(d, .Error) { ret d.lang_id; } if d.lang_id != 0 { if def_state_is(d, .Resolving) { ret d.lang_id; } d.state = .Resolved; ret d.lang_id; } if def_state_is(d, .Resolving) { diag_name(self, d.span, .CircularDep, "circular dependency on `", d.name); d.state = .Error; ret 0; } var owner = d.owner; var owner_def_id = d.owner_def; var node_ref = d.ast_node_ref; var dbg = d.span; if owner == null { diag(self, dbg, .Internal, "def has no owner_ast"); d.state = .Error; ret 0; } var saved_ast = self.ast; self.ast = owner as *Ast; d = def_node(self, def_id); if d != null { d.state = .Resolving; } var n = node(self, node_ref); var result: usize = 0; if n != null { match n.body { .TypeDecl(td) => { var pat = node(self, td.pattern); if pat != null && is_pattern_ident(pat) { if td.type_expr != 0 { var te = node(self, td.type_expr); if te != null && is_type_aggregate(te) { result = build_aggregate(self, te, def_id); } else { result = infer(self, td.type_expr); } } n.resolved_lang_tid = result; } }, .FnDecl(fd) => { var saved_agg = self.current_aggregate_lang; if owner_def_id != 0 { var owner_def = def_node(self, owner_def_id); if owner_def != null && owner_def.lang_id != 0 { self.current_aggregate_lang = owner_def.lang_id; } } result = infer_fn_signature(self, n); self.current_aggregate_lang = saved_agg; }, .VarDecl(vd) => { if vd.type_expr != 0 { result = infer(self, vd.type_expr); } else if vd.init_expr != 0 { result = infer(self, vd.init_expr); } }, .ConstDecl(cd) => { if cd.type_expr != 0 { result = infer(self, cd.type_expr); } else if cd.init_expr != 0 { result = infer(self, cd.init_expr); } }, .ContainerMembers(x) => { result = build_container_aggregate(self, n, def_id); n.resolved_lang_tid = result; }, _ => {}, } } var fd = def_node(self, def_id); if fd != null { if result != 0 { fd.lang_id = result; } fd.state = if result != 0 { .Resolved } else { .Error }; } self.ast = saved_ast; ret result; } fn infer_fn_signature(self: *Sema, n: *Node): usize { if n.resolved_def_id == 0 { ret 0; } var d = def_node(self, n.resolved_def_id); if d != null && d.lang_id != 0 { ret d.lang_id; } var params = ref_vec(); var params_list = ref_vec(); var ret_ref: usize = 0; match n.body { .FnDecl(fd) => { params_list = fd.params; ret_ref = fd.ret_type; }, _ => {}, } var i: usize = 0; while i < params_list.len { var pd = node(self, list_at(params_list, i)); if pd != null && is_param_decl(pd) { var tref: usize = 0; match pd.body { .ParamDecl(&x) => { tref = x.type_expr; }, _ => {} } if tref != 0 { push_u(¶ms, infer(self, tref)); } else { push_u(¶ms, LangType.make_varargs(&self.types)); } } else { push_u(¶ms, LangType.make_varargs(&self.types)); } i = i + 1; } var ret_tid = infer(self, ret_ref); if ret_tid == 0 { ret_tid = make_unit(self); } var fn_tid = LangType.make_fn(&self.types, ret_tid, params); LangType.resolve_def(&self.types, n.resolved_def_id, fn_tid); ret fn_tid; } fn collect_names(self: *Sema, ref: usize) { var n = node(self, ref); if n == null { ret; } match n.body { .ContainerMembers(cm) => { if self.scope.root_scope == 0 { self.scope.root_scope = Scope.alloc(&self.scope); } self.scope.current_scope = self.scope.root_scope; var mod_scope = Scope.alloc(&self.scope); self.scope.current_scope = mod_scope; var file_def = LangType.alloc_def(&self.types, .Agg, ByteView.from_slice("$file"), n.span, self.ast as *any, ref); n.resolved_def_id = file_def; var file_sym = Symbol { .name = ByteView.from_slice("$file"), .kind = .Type, .node = file_def }; Scope.insert(&self.scope, mod_scope, file_sym); var i: usize = 0; while i < cm.declarations.len { collect_names(self, list_at(cm.declarations, i)); i = i + 1; } }, .TypeDecl(td) => { var pat = node(self, td.pattern); if pat == null || !is_pattern_ident(pat) { ret; } var name = pattern_name(pat); var te = node(self, td.type_expr); if te == null { diag_name(self, n.span, .TypeMismatch, "type declaration missing type expr: `", name); ret; } var sym = Symbol { .name = name, .kind = .Type, .node = 0 }; if is_type_aggregate(te) { sym.node = LangType.alloc_def(&self.types, .Agg, name, te.span, self.ast as *any, ref); } else { sym.node = LangType.alloc_def(&self.types, .Scalar, name, te.span, self.ast as *any, ref); } n.resolved_def_id = sym.node; if !Scope.insert(&self.scope, self.scope.current_scope, sym) { diag_name(self, n.span, .DuplicateSymbol, "duplicate symbol `", name); } }, .FnDecl(fd) => { var sym = Symbol { .name = fd.name, .kind = .Fn, .node = 0 }; sym.node = LangType.alloc_def(&self.types, .Fn, fd.name, n.span, self.ast as *any, ref); n.resolved_def_id = sym.node; if !Scope.insert(&self.scope, self.scope.current_scope, sym) { diag_name(self, n.span, .DuplicateSymbol, "duplicate symbol `", fd.name); } }, .VarDecl(vd) => { collect_var(self, n, vd.pattern); }, .ConstDecl(cd) => { collect_var(self, n, cd.pattern); }, .TopLevelBlock(tb) => { if tb.block != 0 { collect_names(self, tb.block); } }, _ => {}, } } fn collect_var(self: *Sema, n: *Node, pat_ref: usize) { var pat = node(self, pat_ref); if pat == null || !is_pattern_ident(pat) { diag(self, n.span, .TypeMismatch, "container-level var only supports simple identifier"); ret; } var name = pattern_name(pat); var sym = Symbol { .name = name, .kind = .Var, .node = 0 }; sym.node = LangType.alloc_def(&self.types, .Var, name, n.span, self.ast as *any, pat_ref); pat.resolved_def_id = sym.node; if !Scope.insert(&self.scope, self.scope.current_scope, sym) { diag_name(self, n.span, .DuplicateSymbol, "duplicate symbol `", name); } } fn collect_types(self: *Sema, ref: usize) { var n = node(self, ref); if n == null { ret; } match n.body { .ContainerMembers(cm) => { var i: usize = 0; while i < cm.declarations.len { var d = node(self, list_at(cm.declarations, i)); if d != null && is_type_decl(d) { match d.body { .TypeDecl(td) => { var pat = node(self, td.pattern); if pat != null && is_pattern_ident(pat) && d.resolved_def_id != 0 { eval_decl(self, d.resolved_def_id); } }, _ => {}, } } i = i + 1; } }, .TopLevelBlock(tb) => { if tb.block != 0 { collect_types(self, tb.block); } }, _ => {}, } } fn collect_signatures(self: *Sema) { var i: usize = 1; while i < self.types.defs.table.len { var d = def_node(self, i); if d != null && d.owner == (self.ast as *any) && @enum2int(d.kind) == @enum2int(DefKind.Fn) && def_state_is(d, .Unresolved) { eval_decl(self, i); } i = i + 1; } } fn collect_type_unpacks(self: *Sema, ref: usize) { var n = node(self, ref); if n == null { ret; } match n.body { .ContainerMembers(cm) => { var i: usize = 0; while i < cm.declarations.len { var d = node(self, list_at(cm.declarations, i)); if d != null && is_type_decl(d) { match d.body { .TypeDecl(td) => { var pat = node(self, td.pattern); if pat != null && !is_pattern_ident(pat) { do_type_unpack(self, d, pat, td.type_expr); } }, _ => {}, } } i = i + 1; } }, .TopLevelBlock(tb) => { if tb.block != 0 { collect_type_unpacks(self, tb.block); } }, _ => {}, } } fn infer_type_context(self: *Sema, ref: usize): usize { var n = node(self, ref); if n == null { ret 0; } match n.body { .ExprIdent(&x) => { var builtin = LangType.lookup_builtin(&self.types, x.name); if builtin != 0 { n.resolved_lang_tid = builtin; ret builtin; } var sym = Symbol { .name = ByteView.empty(), .kind = .Error, .node = 0 }; if !Scope.find(&self.scope, x.name, &sym) || @enum2int(sym.kind) != @enum2int(SymbolKind.Type) { diag_name(self, n.span, .TypeMismatch, "`", x.name); ret 0; } if !def_is_type(self, sym.node) { diag_name(self, n.span, .TypeMismatch, "not a type: `", x.name); ret 0; } n.resolved_def_id = sym.node; var t = eval_decl(self, sym.node); n.resolved_lang_tid = t; ret t; }, .ExprField(&x) => { var owner_id = infer_type_context(self, x.obj); var owner = lang_of(self, owner_id); if owner == null { ret 0; } var i: usize = 0; while i < owner.methods.len { var m = method_at(&owner.methods, i); if m != null && ByteView.eq(m.name, x.name) { if !def_is_type(self, m.def_id) { ret 0; } n.resolved_def_id = m.def_id; var t = eval_decl(self, m.def_id); n.resolved_lang_tid = t; ret t; } i = i + 1; } diag_name(self, n.span, .TypeMismatch, "not a type member `", x.name); ret 0; }, .ExprUnary(&x) => { if punct_is(x.op, .Mul) { var pointee = infer_type_context(self, x.operand); if pointee == 0 { ret 0; } var r = LangType.make_ptr(&self.types, pointee); n.resolved_lang_tid = r; ret r; } ret 0; }, .ExprTuple(&x) => { var elems = ref_vec(); var i: usize = 0; while i < x.items.len { var e = infer_type_context(self, list_at(x.items, i)); if e == 0 { ret 0; } push_u(&elems, e); i = i + 1; } var r = LangType.make_tuple(&self.types, elems); n.resolved_lang_tid = r; ret r; }, .ExprComptimeCall(&x) => { var r = infer(self, ref); n.resolved_lang_tid = r; ret r; }, _ => {}, } diag(self, n.span, .TypeMismatch, "intrinsic argument is not a type expression"); ret 0; } fn punct_is(op: Punct, want: Punct): bool { ret @enum2int(op) == @enum2int(want); } fn is_assign_arith(op: Punct): bool { match op { .AddAssign => { ret true; }, .SubAssign => { ret true; }, .MulAssign => { ret true; }, .DivAssign => { ret true; }, .ModAssign => { ret true; }, .AndAssign => { ret true; }, .OrAssign => { ret true; }, .XorAssign => { ret true; }, .LShAssign => { ret true; }, .RShAssign => { ret true; }, _ => { ret false; }, } ret false; } fn is_cmp_op(op: Punct): bool { match op { .Eq => { ret true; }, .Neq => { ret true; }, .Lt => { ret true; }, .Le => { ret true; }, .Gt => { ret true; }, .Ge => { ret true; }, _ => { ret false; }, } ret false; } fn is_shift_op(op: Punct): bool { match op { .LSh => { ret true; }, .RSh => { ret true; }, _ => { ret false; }, } ret false; } fn is_addsub(op: Punct): bool { ret punct_is(op, .Add) || punct_is(op, .Sub); } fn intrin_is(a: Intrin, b: Intrin): bool { ret @enum2int(a) == @enum2int(b); } fn intrin_call(self: *Sema, n: *Node, in: Intrin, args: Vec): usize { var info = IntrinOps.info(in); var nargs = args.len; if (nargs as i32) < info.min_args || (info.max_args >= 0 && (nargs as i32) > info.max_args) { diag(self, n.span, .IntrinArgCount, "intrinsic argument count mismatch"); ret 0; } if intrin_is(in, .BitSizeof) || intrin_is(in, .Sizeof) || intrin_is(in, .Alignof) { if infer_type_context(self, list_at(args, 0)) == 0 { ret 0; } ret make_usize(self); } if intrin_is(in, .Assert) { var bt = make_bool(self); coerce(self, list_at(args, 0), bt); if nargs == 2 { infer(self, list_at(args, 1)); } ret make_unit(self); } if intrin_is(in, .IntCast) || intrin_is(in, .Truncate) || intrin_is(in, .BitCast) || intrin_is(in, .FloatCast) || intrin_is(in, .Float2Int) || intrin_is(in, .Int2Float) || intrin_is(in, .Int2Ptr) || intrin_is(in, .PtrCast) { var src = infer(self, list_at(args, 0)); if src == 0 { ret 0; } var dst = infer(self, list_at(args, 1)); if dst == 0 { ret 0; } var ft = ir_of(self, src); var tt = ir_of(self, dst); var expected = IntrinOps.lookup_cast(&self.types.ir, ft, tt); if intrin_is(expected, .None) { diag_name(self, n.span, .CastInvalid, "incompatible cast `", info.name); ret 0; } n.resolved_lang_tid = dst; ret dst; } if intrin_is(in, .Bool2Int) || intrin_is(in, .Ptr2Int) { infer(self, list_at(args, 0)); if nargs >= 2 { var dst = infer(self, list_at(args, 1)); if dst == 0 { ret 0; } n.resolved_lang_tid = dst; ret dst; } var r = make_usize(self); n.resolved_lang_tid = r; ret r; } if intrin_is(in, .Enum2Int) { var src = infer(self, list_at(args, 0)); if src == 0 { ret 0; } var sn = lang_of(self, src); if sn == null || !kind_is(self, src, .Enum) { diag(self, n.span, .TypeMismatch, "@enum2int requires an enum operand"); ret 0; } var sir = ir_entry(self, sn.ir_type_id); var tag_ir: usize = 0; if sir != null { match sir.node { .Enum(a) => { tag_ir = a.tag_type; }, _ => {}, } } if tag_ir == 0 { diag(self, n.span, .Internal, "@enum2int: enum has no tag type"); ret 0; } var tag = lang_from_ir(self, tag_ir); n.resolved_lang_tid = tag; ret tag; } if intrin_is(in, .FieldCount) { if infer_type_context(self, list_at(args, 0)) == 0 { ret 0; } ret make_usize(self); } if intrin_is(in, .OffsetOf) { var agg_lang = infer_type_context(self, list_at(args, 0)); if agg_lang == 0 { ret 0; } var agg_ir = ir_of(self, agg_lang); var fnode = node(self, list_at(args, 1)); if fnode == null { ret 0; } if is_expr_ident(fnode) { var fname = ByteView.empty(); match fnode.body { .ExprIdent(&x) => { fname = x.name; }, _ => {} } var idx: usize = 0; if !TypeTable.agg_member_idx(&self.types.ir, agg_ir, fname, &idx) { diag_name(self, n.span, .NoField, "no field `", fname); ret 0; } } else if !is_expr_int_lit(fnode) { diag(self, n.span, .TypeMismatch, "@offsetof expects field name or index"); ret 0; } ret make_usize(self); } ret 0; } fn intrin_field(self: *Sema, n: *Node, in: Intrin): usize { var info = IntrinOps.info(in); match info.result { .Usize => { ret make_usize(self); }, .Bool => { ret make_bool(self); }, .Unit => { ret make_unit(self); }, } ret 0; } fn block_body(self: *Sema, n: *Node, expected: usize): usize { var stmts = ref_vec(); var tail: usize = 0; match n.body { .ExprBlock(x) => { stmts = x.stmts; tail = x.tail_expr; }, _ => {}, } push_scope(self); var i: usize = 0; while i < stmts.len { infer(self, list_at(stmts, i)); i = i + 1; } var r: usize = 0; if tail != 0 { if expected != 0 { r = coerce(self, tail, expected); if r == 0 { pop_scope(self); ret 0; } } else { r = infer(self, tail); } } else { r = make_unit(self); } pop_scope(self); n.resolved_lang_tid = r; ret r; } fn pattern_is_literal(n: *Node): bool { match n.body { .ExprIntLit(x) => { ret true; }, .ExprFloatLit(x) => { ret true; }, .ExprCharLit(x) => { ret true; }, .ExprStringLit(x) => { ret true; }, .ExprBoolLit(x) => { ret true; }, .ExprNullLit => { ret true; }, _ => { ret false; }, } ret false; } fn bind_pattern(self: *Sema, pat_ref: usize, matched_tid: usize) { if pat_ref == 0 { ret; } var pat = node(self, pat_ref); if pat == null { ret; } if pattern_is_literal(pat) { var lt = infer(self, pat_ref); if lt != 0 && matched_tid != 0 { coerce(self, pat_ref, matched_tid); } ret; } match pat.body { .PatternIdent(x) => { if x.name.len == 0 { ret; } if ByteView.eq(x.name, ByteView.from_slice("_")) { ret; } if pat.resolved_def_id != 0 { LangType.resolve_def(&self.types, pat.resolved_def_id, matched_tid); pat.resolved_lang_tid = matched_tid; ret; } var sym = Symbol { .name = x.name, .kind = .Var, .node = 0 }; sym.node = LangType.alloc_def(&self.types, .Var, x.name, pat.span, self.ast as *any, pat_ref); LangType.resolve_def(&self.types, sym.node, matched_tid); if !Scope.insert(&self.scope, self.scope.current_scope, sym) { diag_name(self, pat.span, .DuplicateSymbol, "duplicate symbol `", x.name); } pat.resolved_def_id = sym.node; pat.resolved_lang_tid = matched_tid; }, .PatternUnderscore => {}, .PatternRef(x) => { if matched_tid == 0 { diag(self, pat.span, .TypeMismatch, "ref pattern requires a concrete type"); ret; } var ptr = LangType.make_ptr(&self.types, matched_tid); bind_pattern(self, x.pat, ptr); pat.resolved_lang_tid = ptr; }, .PatternList(x) => { var i: usize = 0; while i < x.items.len { bind_pattern(self, list_at(x.items, i), matched_tid); i = i + 1; } }, .PatternTuple(x) => { var tn = lang_of(self, matched_tid); if tn == null || !kind_is(self, matched_tid, .Tuple) { diag(self, pat.span, .TypeMismatch, "tuple pattern requires tuple value"); ret; } var elem_tids = ref_vec(); match tn.extra { .Tuple(v) => { elem_tids = v; }, _ => {}, } var natoms = x.items.len; var nelems = elem_tids.len; if natoms != nelems { diag(self, pat.span, .TypeMismatch, "tuple pattern arity mismatch"); ret; } var i: usize = 0; while i < nelems { bind_pattern(self, list_at(x.items, i), list_at(elem_tids, i)); i = i + 1; } pat.resolved_lang_tid = matched_tid; }, .PatternDot(x) => { if !kind_is(self, matched_tid, .Enum) { diag(self, pat.span, .TypeMismatch, "variant pattern requires enum value"); ret; } var agg_ir = ir_of(self, matched_tid); var idx: usize = 0; if !TypeTable.agg_member_idx(&self.types.ir, agg_ir, x.name, &idx) { diag_name(self, pat.span, .NoField, "no variant `", x.name); ret; } var payload_ir = agg_variant_ir(self, agg_ir, idx); var payload_lang = lang_from_ir(self, payload_ir); pat.resolved_def_id = idx + 1; pat.resolved_lang_tid = matched_tid; if x.sub_pattern != 0 && payload_lang != 0 { bind_pattern(self, x.sub_pattern, payload_lang); } }, .PatternAggregate(x) => { if !(kind_is(self, matched_tid, .Struct) || kind_is(self, matched_tid, .Union) || kind_is(self, matched_tid, .Enum)) { diag(self, pat.span, .TypeMismatch, "aggregate pattern requires aggregate value"); ret; } var agg_ir = ir_of(self, matched_tid); if kind_is(self, matched_tid, .Enum) && x.items.len != 1 { diag(self, pat.span, .TypeMismatch, "enum pattern must have one variant"); ret; } var i: usize = 0; while i < x.items.len { var f = node(self, list_at(x.items, i)); if f != null && is_pattern_field(f) { var fname = ByteView.empty(); var subp: usize = 0; match f.body { .PatternField(pf) => { fname = pf.name; subp = pf.pattern; }, _ => {}, } var idx: usize = 0; if !TypeTable.agg_member_idx(&self.types.ir, agg_ir, fname, &idx) { diag_name(self, f.span, .NoField, "no field `", fname); i = i + 1; continue; } var fir = agg_variant_ir(self, agg_ir, idx); var ft = lang_from_ir(self, fir); f.resolved_def_id = idx; if subp != 0 && ft != 0 { bind_pattern(self, subp, ft); } } i = i + 1; } pat.resolved_lang_tid = matched_tid; }, _ => { diag(self, pat.span, .UnhandledNode, "unsupported pattern kind"); }, } } fn agg_variant_ir(self: *Sema, agg_ir: usize, idx: usize): usize { var irn = ir_entry(self, agg_ir); if irn == null { ret 0; } match irn.node { .Struct(a) => { ret variant_at(a.variants, idx); }, .Union(a) => { ret variant_at(a.variants, idx); }, .Enum(a) => { ret variant_at(a.variants, idx); }, .Shape(a) => { ret variant_at(a.variants, idx); }, _ => { ret 0; }, } ret 0; } fn variant_at(v: Vec, idx: usize): usize { var m = TypeTable.member_at(&v, idx); if m == null { ret 0; } ret m.type_id; } fn type_unpack_one(self: *Sema, pat: *Node, name: ByteView, src_lang: usize) { if name.len == 0 { ret; } var src = lang_of(self, src_lang); if src == null { ret; } var i: usize = 0; while i < src.methods.len { var m = method_at(&src.methods, i); if m != null && ByteView.eq(m.name, name) { var d = def_node(self, m.def_id); var k: SymbolKind = .Var; if d != null { match d.kind { .Fn => { k = .Fn; }, .Agg, .Scalar, .Distinct, .Alias, .Builtin => { k = .Type; }, _ => {}, } } var sym = Symbol { .name = name, .kind = k, .node = m.def_id }; if !Scope.insert(&self.scope, self.scope.current_scope, sym) { diag_name(self, pat.span, .DuplicateSymbol, "type unpack duplicate `", name); } pat.resolved_def_id = m.def_id; ret; } i = i + 1; } diag_name(self, pat.span, .NoField, "type unpack: no symbol `", name); } fn type_unpack_walk(self: *Sema, pat: *Node, src_lang: usize) { if pat == null { ret; } match pat.body { .PatternUnderscore => {}, .PatternDot(x) => { if x.sub_pattern != 0 { diag(self, pat.span, .TypeMismatch, "type unpack: payload not allowed"); ret; } type_unpack_one(self, pat, x.name, src_lang); }, .PatternAggregate(x) => { var i: usize = 0; while i < x.items.len { var f = node(self, list_at(x.items, i)); if f != null && is_pattern_field(f) { var fname = ByteView.empty(); var subp: usize = 0; match f.body { .PatternField(pf) => { fname = pf.name; subp = pf.pattern; }, _ => {}, } if subp != 0 { diag(self, f.span, .TypeMismatch, "type unpack: field pattern not allowed"); } else { type_unpack_one(self, f, fname, src_lang); } } i = i + 1; } }, .PatternList(x) => { var i: usize = 0; while i < x.items.len { type_unpack_walk(self, node(self, list_at(x.items, i)), src_lang); i = i + 1; } }, _ => {}, } } fn do_type_unpack(self: *Sema, decl: *Node, pat: *Node, type_expr_ref: usize) { if type_expr_ref == 0 { diag(self, decl.span, .TypeMismatch, "type unpack missing source type"); ret; } var src_lang = infer(self, type_expr_ref); if src_lang == 0 { ret; } if !kind_is(self, src_lang, .Struct) { diag(self, decl.span, .TypeMismatch, "type unpack source must be struct"); ret; } type_unpack_walk(self, pat, src_lang); } fn var_body(self: *Sema, n: *Node): usize { var decl_tid: usize = 0; var tref: usize = 0; var iref: usize = 0; var pat_ref: usize = 0; match n.body { .VarDecl(x) => { tref = x.type_expr; iref = x.init_expr; pat_ref = x.pattern; }, .ConstDecl(x) => { tref = x.type_expr; iref = x.init_expr; pat_ref = x.pattern; }, _ => {}, } if tref != 0 { decl_tid = infer(self, tref); } if iref == 0 { diag(self, n.span, .VarUninitialized, "variable must be initialized"); ret 0; } if decl_tid != 0 { if coerce(self, iref, decl_tid) == 0 { ret 0; } } else { decl_tid = infer(self, iref); if decl_tid == 0 || is_infer(self, decl_tid) { diag(self, n.span, .CannotInfer, "cannot infer variable type"); ret 0; } } bind_pattern(self, pat_ref, decl_tid); n.resolved_lang_tid = decl_tid; ret decl_tid; } fn for_elem_type(self: *Sema, lang_tid: usize): usize { var e = lang_of(self, lang_tid); if e == null { ret 0; } ret elem_of_lang(e.*); } fn run_module(self: *Sema, module_ref: usize) { if self.ast == null || module_ref == 0 { ret; } var m = Ast.node(self.ast, module_ref); if m == null { ret; } match m.body { .ContainerMembers(x) => {}, _ => { ret; }, } var saved_scope = self.scope.current_scope; var saved_agg = self.current_aggregate_lang; collect_names(self, module_ref); collect_type_unpacks(self, module_ref); var container_lang: usize = 0; if m.resolved_def_id != 0 { container_lang = eval_decl(self, m.resolved_def_id); } self.current_aggregate_lang = container_lang; collect_types(self, module_ref); collect_signatures(self); infer(self, module_ref); self.current_aggregate_lang = saved_agg; self.scope.current_scope = saved_scope; } fn run(self: *Sema) { if self.ast == null || self.ast.root == 0 { ret; } var root = Ast.node(self.ast, self.ast.root); if root == null { ret; } var members: usize = 0; match root.body { .Root(r) => { members = r.members; }, _ => { ret; }, } run_module(self, members); } fn method_at(v: *Vec, i: usize): *LangMethod { match Vec.get(v, i) { .Ok(ptr) => { ret ptr as *LangMethod; }, .Err(error) => { ret null; }, } ret null; } // ============================================================ // infer / coerce:下面两块是主体(step 2 填入)。 // ============================================================ fn int_bits(self: *Sema, lang_id: usize): usize { var e = ir_entry(self, ir_of(self, lang_id)); if e == null { ret 0; } match e.node { .Int(x) => { ret x.bits; }, _ => { ret 0; }, } ret 0; } fn float_bits(self: *Sema, lang_id: usize): usize { var e = ir_entry(self, ir_of(self, lang_id)); if e == null { ret 0; } match e.node { .Float(x) => { ret x.bits; }, _ => { ret 0; }, } ret 0; } fn lang_find_method(self: *Sema, lang_id: usize, name: ByteView): usize { var e = lang_of(self, lang_id); if e == null { ret 0; } var i: usize = 0; while i < e.methods.len { var m = method_at(&e.methods, i); if m != null && ByteView.eq(m.name, name) { ret m.def_id; } i = i + 1; } ret 0; } fn label_name(self: *Sema, ref: usize): ByteView { if ref == 0 { ret ByteView.empty(); } var ln = node(self, ref); if ln == null { ret ByteView.empty(); } match ln.body { .Label(l) => { ret l.name; }, _ => { ret ByteView.empty(); }, } ret ByteView.empty(); } fn agg_variants_copy(self: *Sema, agg_ir: usize): Vec { var out = ref_vec(); var irn = ir_entry(self, agg_ir); if irn == null { ret out; } var v = ref_vec(); match irn.node { .Struct(a) => { v = a.variants; }, .Union(a) => { v = a.variants; }, .Enum(a) => { v = a.variants; }, .Shape(a) => { v = a.variants; }, _ => {}, } var i: usize = 0; while i < v.len { push_u(&out, variant_at(v, i)); i = i + 1; } ret out; } // 二元运算两侧都 coerce 到 expected;成功返回 expected,失败返回 0。 fn coerce_binop_same(self: *Sema, left: usize, right: usize, expected: usize): usize { if coerce(self, left, expected) == 0 { ret 0; } if coerce(self, right, expected) == 0 { ret 0; } ret expected; } fn coerce(self: *Sema, ref: usize, expected: usize): usize { if ref == 0 || expected == 0 { ret 0; } var n = node(self, ref); if n == null { ret 0; } var already = n.resolved_lang_tid; if already == expected { ret expected; } if already != 0 { var a_rebindable = is_infer(self, already) || (kind_is(self, already, .Int) && int_bits(self, already) == 0) || (kind_is(self, already, .Float) && float_bits(self, already) == 0); if !a_rebindable { if LangType.is_subtype(&self.types, already, expected) { n.expected_lang_tid = expected; ret expected; } diag_ty2(self, n.span, .TypeMismatch, "type mismatch: ", already, expected); ret 0; } n.resolved_lang_tid = 0; } var handled = false; var result: usize = 0; match n.body { .ExprUnary(&x) => { if !(punct_is(x.op, .And) || punct_is(x.op, .Mul)) { handled = true; result = coerce(self, x.operand, expected); } }, .ExprBinary(&x) => { handled = true; var op = x.op; if punct_is(op, .Assign) || is_assign_arith(op) { var lt = infer(self, x.left); if lt == 0 { ret 0; } if coerce(self, x.right, lt) == 0 { ret 0; } result = lt; } else if punct_is(op, .OrOr) || punct_is(op, .AndAnd) { var bt = make_bool(self); if coerce(self, x.left, bt) == 0 { ret 0; } if coerce(self, x.right, bt) == 0 { ret 0; } result = bt; } else if is_cmp_op(op) { var lt2 = infer(self, x.left); var rt2 = infer(self, x.right); if lt2 == 0 || rt2 == 0 { ret 0; } var common = if lt2 == rt2 { lt2 } else { LangType.join(&self.types, lt2, rt2) }; if common == 0 { diag_ty2(self, n.span, .TypeMismatch, "comparison mismatch: ", lt2, rt2); ret 0; } coerce(self, x.left, common); coerce(self, x.right, common); result = make_bool(self); } else if is_addsub(op) { var en = lang_of(self, expected); if en != null && kind_is(self, expected, .Ptr) { var left_tid = infer(self, x.left); var ln = lang_of(self, left_tid); if ln != null && kind_is(self, left_tid, .Ptr) { var rt3 = infer(self, x.right); if !kind_is(self, rt3, .Int) { diag(self, n.span, .TypeMismatch, "pointer offset must be integer"); ret 0; } if int_bits(self, rt3) == 0 { var it = make_isize(self); if coerce(self, x.right, it) == 0 { ret 0; } } result = expected; } else { result = coerce_binop_same(self, x.left, x.right, expected); } } else { result = coerce_binop_same(self, x.left, x.right, expected); } } else { result = coerce_binop_same(self, x.left, x.right, expected); } }, .ExprArrayLit(&x) => { var en = lang_of(self, expected); if en != null && (kind_is(self, expected, .Array) || kind_is(self, expected, .Slice)) { handled = true; var et = elem_of_lang(en.*); var i: usize = 0; while i < x.elems.len { if coerce(self, list_at(x.elems, i), et) == 0 { ret 0; } i = i + 1; } result = expected; } }, .ExprTuple(&x) => { var en = lang_of(self, expected); if en != null && kind_is(self, expected, .Tuple) { handled = true; var elems = ref_vec(); match en.extra { .Tuple(v) => { elems = v; }, _ => {} } if x.items.len != elems.len { diag(self, n.span, .TypeMismatch, "tuple arity mismatch"); ret 0; } var i: usize = 0; while i < x.items.len { if coerce(self, list_at(x.items, i), list_at(elems, i)) == 0 { ret 0; } i = i + 1; } result = expected; } }, .ExprAggregateLit(&x) => { var en = lang_of(self, expected); if en != null && (kind_is(self, expected, .Struct) || kind_is(self, expected, .Union) || kind_is(self, expected, .Enum) || kind_is(self, expected, .Slice)) { handled = true; if x.name.len != 0 { var sym = Symbol { .name = ByteView.empty(), .kind = .Error, .node = 0 }; if !Scope.find(&self.scope, x.name, &sym) { diag_name(self, n.span, .UndefinedSymbol, "unknown type `", x.name); ret 0; } var d = def_node(self, sym.node); if d == null || d.lang_id != expected { diag(self, n.span, .TypeMismatch, "aggregate does not match expected type"); ret 0; } } var agg_ir = ir_of(self, expected); var field_irs = agg_variants_copy(self, agg_ir); if kind_is(self, expected, .Enum) { if x.fields.len != 1 { diag(self, n.span, .TypeMismatch, "enum literal must have one variant"); ret 0; } var item = node(self, list_at(x.fields, 0)); if item == null || !kind_is_body_struct_init(item) { diag(self, n.span, .TypeMismatch, "invalid enum literal"); ret 0; } var iname = ByteView.empty(); var iexpr: usize = 0; match item.body { .StructInitItem(&si) => { iname = si.name; iexpr = si.expr; }, _ => {} } var idx: usize = 0; if !TypeTable.agg_member_idx(&self.types.ir, agg_ir, iname, &idx) { diag_name(self, item.span, .NoField, "no variant `", iname); ret 0; } if idx >= field_irs.len { ret 0; } var ft = lang_from_ir(self, list_at(field_irs, idx)); if ft != 0 && iexpr != 0 { if coerce(self, iexpr, ft) == 0 { ret 0; } } result = expected; } else { var i: usize = 0; while i < x.fields.len { var item = node(self, list_at(x.fields, i)); if item != null && kind_is_body_struct_init(item) { var iname = ByteView.empty(); var iexpr: usize = 0; match item.body { .StructInitItem(&si) => { iname = si.name; iexpr = si.expr; }, _ => {} } var idx: usize = 0; if !TypeTable.agg_member_idx(&self.types.ir, agg_ir, iname, &idx) { diag_name(self, item.span, .NoField, "no field `", iname); i = i + 1; continue; } if idx < field_irs.len { var ft = lang_from_ir(self, list_at(field_irs, idx)); if ft != 0 && iexpr != 0 { if coerce(self, iexpr, ft) == 0 { ret 0; } } } } i = i + 1; } result = expected; } } }, .ExprIf(&x) => { handled = true; var bt = make_bool(self); coerce(self, x.cond, bt); if x.then_block != 0 { coerce(self, x.then_block, expected); } if x.else_branch != 0 { coerce(self, x.else_branch, expected); } result = expected; }, .ExprLoop(&x) => { handled = true; var saved = self.current_loop_break_tid; self.current_loop_break_tid = expected; infer(self, ref); self.current_loop_break_tid = saved; result = expected; }, .ExprBlock(&x) => { ret block_body(self, n, expected); }, _ => {}, } if handled { if result == 0 { ret 0; } n.resolved_lang_tid = result; ret result; } var from = infer(self, ref); if from == 0 { ret 0; } if from == expected { ret expected; } if kind_is(self, from, .Int) && int_bits(self, from) == 0 && kind_is(self, expected, .Int) { if is_expr_int_lit(n) && int_bits(self, expected) != 0 { var fits = true; match n.body { .ExprIntLit(&v) => { if !LangType.int_fits(&self.types, expected, v.value) { fits = false; } }, _ => {}, } if !fits { diag(self, n.span, .FitsOverflow, "comptime value does not fit target type"); ret 0; } } n.resolved_lang_tid = expected; ret expected; } if kind_is(self, from, .Float) && float_bits(self, from) == 0 && kind_is(self, expected, .Float) { n.resolved_lang_tid = expected; ret expected; } if LangType.is_subtype(&self.types, from, expected) { n.expected_lang_tid = expected; ret expected; } diag_ty2(self, n.span, .TypeMismatch, "type mismatch: ", from, expected); ret 0; } fn elem_of_lang(e: LangEntry): usize { match e.extra { .Elem(x) => { ret x; }, .Array(a) => { ret a.elem; }, _ => { ret 0; }, } ret 0; } fn kind_is_body_struct_init(n: *Node): bool { match n.body { .StructInitItem(&x) => { ret true; }, _ => { ret false; } } ret false; } fn infer(self: *Sema, ref: usize): usize { if ref == 0 { ret 0; } var n = node(self, ref); if n == null { ret 0; } resolve_attrs(self, n); match n.body { .ExprIntLit(&x) => { var t = LangType.make_int(&self.types, 0, true); n.resolved_lang_tid = t; ret t; }, .ExprFloatLit(&x) => { var t = LangType.make_float(&self.types, 0); n.resolved_lang_tid = t; ret t; }, .ExprCharLit(&x) => { var t = LangType.make_int(&self.types, 8, false); n.resolved_lang_tid = t; ret t; }, .ExprStringLit(&x) => { var u8t = LangType.make_int(&self.types, 8, false); var s = LangType.make_slice(&self.types, u8t); n.resolved_lang_tid = s; ret s; }, .ExprBoolLit(&x) => { var t = make_bool(self); n.resolved_lang_tid = t; ret t; }, .ExprNullLit => { var nv = make_never(self); var p = LangType.make_ptr(&self.types, nv); n.resolved_lang_tid = p; ret p; }, .ExprUndefinedLit => { var t = make_never(self); n.resolved_lang_tid = t; ret t; }, .ExprIdent(&x) => { var sym = Symbol { .name = ByteView.empty(), .kind = .Error, .node = 0 }; if !Scope.find(&self.scope, x.name, &sym) { diag_name(self, n.span, .UndefinedSymbol, "undefined identifier `", x.name); ret 0; } if sym.node == 0 { diag_name(self, n.span, .UndefinedSymbol, "identifier has no def `", x.name); ret 0; } n.resolved_def_id = sym.node; var t = eval_decl(self, sym.node); n.resolved_lang_tid = t; ret t; }, .ExprUnary(&x) => { var op = infer(self, x.operand); if op == 0 { ret 0; } var tn = lang_of(self, op); if tn == null { ret 0; } if punct_is(x.op, .Sub) { if !(kind_is(self, op, .Int) || kind_is(self, op, .Float)) { diag(self, n.span, .TypeMismatch, "unary minus requires numeric operand"); ret 0; } n.resolved_lang_tid = op; ret op; } if punct_is(x.op, .BitNot) { if !kind_is(self, op, .Int) { diag(self, n.span, .TypeMismatch, "bitwise not requires integer operand"); ret 0; } n.resolved_lang_tid = op; ret op; } if punct_is(x.op, .Not) { var bt = make_bool(self); coerce(self, x.operand, bt); n.resolved_lang_tid = bt; ret bt; } if punct_is(x.op, .And) { var operand = node(self, x.operand); if operand != null && kind_is_body_ident(operand) { var d = def_node(self, operand.resolved_def_id); if d != null && @enum2int(d.kind) == @enum2int(DefKind.Fn) { diag(self, n.span, .TypeMismatch, "cannot take address of a function"); ret 0; } } var p = LangType.make_ptr(&self.types, op); n.resolved_lang_tid = p; ret p; } if punct_is(x.op, .Mul) { if !kind_is(self, op, .Ptr) { diag(self, n.span, .TypeMismatch, "deref requires pointer operand"); ret 0; } var elem = elem_of_lang(tn.*); n.resolved_lang_tid = elem; ret elem; } ret 0; }, .ExprBinary(&x) => { ret infer_binary(self, n, x.op, x.left, x.right); }, .ExprCall(&x) => { ret infer_call(self, n, x.callee, x.args); }, .ExprField(&x) => { ret infer_field(self, n, x.obj, x.name); }, .ExprIndex(&x) => { ret infer_index(self, n, x.obj, x.index); }, .ExprSlice(&x) => { ret infer_slice(self, n, x.obj, x.begin, x.end); }, .ExprCast(&x) => { ret infer_cast(self, n, x.expr, x.type_expr); }, .ExprTuple(&x) => { var es = ref_vec(); var i: usize = 0; while i < x.items.len { push_u(&es, infer(self, list_at(x.items, i))); i = i + 1; } var t = LangType.make_tuple(&self.types, es); n.resolved_lang_tid = t; ret t; }, .ExprArrayLit(&x) => { ret infer_array(self, n, x.elem_type, x.has_len, x.len, x.is_underscore_len, x.elems); }, .ExprAggregateLit(&x) => { ret infer_aggregate(self, n, x.name, x.fields); }, .ExprComptimeCall(&x) => { if ByteView.eq(x.name, ByteView.from_slice("import")) { var t = sema_import(self, n, x.args); n.resolved_lang_tid = t; ret t; } if ByteView.eq(x.name, ByteView.from_slice("self")) { if self.current_aggregate_lang == 0 { diag(self, n.span, .UndefinedSymbol, "`@self` outside of aggregate"); ret 0; } n.resolved_lang_tid = self.current_aggregate_lang; ret self.current_aggregate_lang; } var in = IntrinOps.lookup_call(x.name); if intrin_is(in, .None) { diag_name(self, n.span, .UnknownIntrin, "unknown comptime call `", x.name); ret 0; } var t = intrin_call(self, n, in, x.args); n.resolved_lang_tid = t; ret t; }, .ExprBlock(&x) => { ret block_body(self, n, 0); }, .ExprIf(&x) => { var bt = make_bool(self); coerce(self, x.cond, bt); var then_t = if x.then_block != 0 { infer(self, x.then_block) } else { 0 }; var else_t = if x.else_branch != 0 { infer(self, x.else_branch) } else { 0 }; var result: usize = 0; if then_t != 0 && else_t != 0 { result = if then_t == else_t { then_t } else { LangType.join(&self.types, then_t, else_t) }; } else if then_t != 0 { result = then_t; } else { result = else_t; } n.resolved_lang_tid = result; ret result; }, .ExprIfvar(&x) => { if x.var_decl == 0 { ret 0; } var vd = node(self, x.var_decl); if vd == null { ret 0; } push_scope(self); var_body(self, vd); if x.then_block != 0 { infer(self, x.then_block); } pop_scope(self); if x.else_branch != 0 { infer(self, x.else_branch); } ret 0; }, .ExprWhile(&x) => { push_view(&self.label_stack, label_name(self, x.label)); var bt = make_bool(self); coerce(self, x.cond, bt); if x.body != 0 { infer(self, x.body); } if x.else_branch != 0 { infer(self, x.else_branch); } pop_label(self); ret 0; }, .ExprLoop(&x) => { push_view(&self.label_stack, label_name(self, x.label)); if x.body != 0 { infer(self, x.body); } pop_label(self); ret 0; }, .ExprFor(&x) => { ret infer_for(self, n, x.label, x.iter_exprs, x.pattern, x.body, x.else_branch); }, .ExprMatch(&x) => { var matched = infer(self, x.matched_expr); var i: usize = 0; while i < x.arms.len { var arm = node(self, list_at(x.arms, i)); if arm != null { var pat: usize = 0; var body: usize = 0; match arm.body { .MatchArm(ma) => { pat = ma.pattern; body = ma.body; }, _ => {}, } push_scope(self); bind_pattern(self, pat, matched); if body != 0 { infer(self, body); } pop_scope(self); } i = i + 1; } ret 0; }, .ExprFnType(&x) => { var ps = ref_vec(); var i: usize = 0; while i < x.params.len { push_u(&ps, infer(self, list_at(x.params, i))); i = i + 1; } var rt = infer(self, x.ret_type); var fn_lang = LangType.make_fn(&self.types, rt, ps); n.resolved_lang_tid = fn_lang; ret fn_lang; }, .TypePath(&x) => { var b = LangType.lookup_builtin(&self.types, x.name); if b != 0 { n.resolved_lang_tid = b; ret b; } var sym = Symbol { .name = ByteView.empty(), .kind = .Error, .node = 0 }; if !Scope.find(&self.scope, x.name, &sym) { diag_name(self, n.span, .UndefinedSymbol, "type not found `", x.name); ret 0; } var t = eval_decl(self, sym.node); n.resolved_lang_tid = t; ret t; }, .TypePointer(&x) => { var p = infer(self, x.pointee); var r = LangType.make_ptr(&self.types, p); n.resolved_lang_tid = r; ret r; }, .TypeArray(&x) => { var e = infer(self, x.elem_type); var len: usize = 0; if x.has_len { len = x.len as usize; } var r = LangType.make_array(&self.types, e, len); n.resolved_lang_tid = r; ret r; }, .TypeSlice(&x) => { var e = infer(self, x.elem_type); var r = LangType.make_slice(&self.types, e); n.resolved_lang_tid = r; ret r; }, .TypeFn(&x) => { var ps = ref_vec(); var i: usize = 0; while i < x.params.len { push_u(&ps, infer(self, list_at(x.params, i))); i = i + 1; } var rt = infer(self, x.ret_type); var r = LangType.make_fn(&self.types, rt, ps); n.resolved_lang_tid = r; ret r; }, .TypeParen(&x) => { if x.types.len == 0 { var u = make_unit(self); n.resolved_lang_tid = u; ret u; } if x.types.len == 1 { var t = infer(self, list_at(x.types, 0)); n.resolved_lang_tid = t; ret t; } var es = ref_vec(); var i: usize = 0; while i < x.types.len { push_u(&es, infer(self, list_at(x.types, i))); i = i + 1; } var t = LangType.make_tuple(&self.types, es); n.resolved_lang_tid = t; ret t; }, .TypeAggregate(&x) => { ret build_aggregate(self, n, 0); }, .TypeComptimeCall(&x) => { if ByteView.eq(x.name, ByteView.from_slice("self")) { if self.current_aggregate_lang == 0 { diag(self, n.span, .UndefinedSymbol, "`@self` outside of aggregate"); ret 0; } n.resolved_lang_tid = self.current_aggregate_lang; ret self.current_aggregate_lang; } if ByteView.eq(x.name, ByteView.from_slice("import")) { var t = sema_import(self, n, x.args); n.resolved_lang_tid = t; ret t; } ret 0; }, .ContainerMembers(&x) => { var i: usize = 0; while i < x.declarations.len { infer(self, list_at(x.declarations, i)); i = i + 1; } ret 0; }, .FnDecl(&x) => { ret infer_fn_decl(self, n, x.params, x.ret_type, x.body); }, .TypeDecl(&x) => { ret infer_type_decl(self, n, x.pattern, x.type_expr); }, .VarDecl(&x) => { ret var_body(self, n); }, .ConstDecl(&x) => { ret var_body(self, n); }, .MemberDecl(&x) => { if x.type_expr != 0 { ret infer(self, x.type_expr); } ret 0; }, .ParamDecl(&x) => { if x.type_expr != 0 { ret infer(self, x.type_expr); } ret 0; }, .StatementRet(&x) => { ret infer_ret(self, n, x.expr); }, .StatementBreak(&x) => { ret infer_break(self, n, x.label, x.expr); }, .StatementContinue(&x) => { ret infer_continue(self, n, x.label); }, .StatementDefer(&x) => { if x.body != 0 { infer(self, x.body); } ret 0; }, .StatementComptime(&x) => { if x.body != 0 { infer(self, x.body); } ret 0; }, .StatementDecl(&x) => { if x.decl != 0 { infer(self, x.decl); } ret 0; }, .StatementExpr(&x) => { if x.expr != 0 { infer(self, x.expr); } ret 0; }, .TopLevelBlock(&x) => { if x.block != 0 { infer(self, x.block); } ret 0; }, _ => { ret 0; }, } ret 0; } fn kind_is_body_ident(n: *Node): bool { match n.body { .ExprIdent(&x) => { ret true; }, _ => { ret false; } } ret false; } fn pop_label(self: *Sema) { var out = ByteView.empty(); Vec.pop(&self.label_stack, &out as *u8); } fn infer_binary(self: *Sema, n: *Node, op: Punct, left: usize, right: usize): usize { if punct_is(op, .Assign) || is_assign_arith(op) { var lt = infer(self, left); if lt == 0 { ret 0; } coerce(self, right, lt); n.resolved_lang_tid = lt; ret lt; } if punct_is(op, .OrOr) || punct_is(op, .AndAnd) { var bt = make_bool(self); coerce(self, left, bt); coerce(self, right, bt); n.resolved_lang_tid = bt; ret bt; } if is_cmp_op(op) { var lt = infer(self, left); var rt = infer(self, right); if lt == 0 || rt == 0 { ret 0; } var common = if lt == rt { lt } else { LangType.join(&self.types, lt, rt) }; if common == 0 { diag_ty2(self, n.span, .TypeMismatch, "comparison mismatch: ", lt, rt); ret 0; } coerce(self, left, common); coerce(self, right, common); var b = make_bool(self); n.resolved_lang_tid = b; ret b; } if punct_is(op, .Range) { infer(self, left); infer(self, right); var i64t = LangType.make_int(&self.types, 64, true); var r = LangType.make_range(&self.types, i64t); n.resolved_lang_tid = r; ret r; } if is_shift_op(op) { var lt = infer(self, left); if lt == 0 { ret 0; } if !kind_is(self, lt, .Int) { diag(self, n.span, .TypeMismatch, "shift requires integer operands"); ret 0; } infer(self, right); n.resolved_lang_tid = lt; ret lt; } var lt2 = infer(self, left); var rt2 = infer(self, right); if lt2 == 0 || rt2 == 0 { ret 0; } if kind_is(self, lt2, .Ptr) && kind_is(self, rt2, .Int) && is_addsub(op) { if int_bits(self, rt2) == 0 { var it = make_isize(self); coerce(self, right, it); } n.resolved_lang_tid = lt2; ret lt2; } var common2 = if lt2 == rt2 { lt2 } else { LangType.join(&self.types, lt2, rt2) }; if common2 == 0 { diag_ty2(self, n.span, .TypeMismatch, "operand types cannot be joined: ", lt2, rt2); ret 0; } coerce(self, left, common2); coerce(self, right, common2); n.resolved_lang_tid = common2; ret common2; } fn infer_call(self: *Sema, n: *Node, callee_ref: usize, args: Vec): usize { var callee = node(self, callee_ref); if callee != null && kind_is_body_ident(callee) { var cname = ByteView.empty(); match callee.body { .ExprIdent(e) => { cname = e.name; }, _ => {} } var in = IntrinOps.lookup_call(cname); if !intrin_is(in, .None) { var t = intrin_call(self, n, in, args); n.resolved_lang_tid = t; ret t; } } var callee_tid = infer(self, callee_ref); var fn_lang = lang_of(self, callee_tid); if fn_lang == null || !kind_is(self, callee_tid, .Fn) { diag(self, n.span, .NotAFunction, "callee is not a function"); ret 0; } var params = ref_vec(); var call_ret: usize = 0; match fn_lang.extra { .Fn(f) => { params = f.params; call_ret = f.ret_type; }, _ => {}, } var has_varargs = false; var fixed = params.len; if fixed > 0 { var last = list_at(params, fixed - 1); if kind_is(self, last, .Varargs) { has_varargs = true; fixed = fixed - 1; } } var nargs = args.len; if has_varargs { if nargs < fixed { diag(self, n.span, .ArgCountMismatch, "too few arguments"); } } else if nargs != fixed { diag(self, n.span, .ArgCountMismatch, "argument count mismatch"); } var i: usize = 0; while i < nargs && i < fixed { var pt = list_at(params, i); if coerce(self, list_at(args, i), pt) == 0 { ret 0; } i = i + 1; } if has_varargs { var j = fixed; while j < nargs { if infer(self, list_at(args, j)) == 0 { ret 0; } j = j + 1; } } n.resolved_lang_tid = call_ret; ret call_ret; } fn infer_field(self: *Sema, n: *Node, obj_ref: usize, field_name: ByteView): usize { var obj_tid = infer(self, obj_ref); var obj = lang_of(self, obj_tid); if obj == null { ret 0; } var objn = node(self, obj_ref); if objn != null && kind_is_body_ident(objn) { var od = def_node(self, objn.resolved_def_id); if od != null && @enum2int(od.kind) == @enum2int(DefKind.Agg) && kind_is(self, obj_tid, .Enum) { var eir = ir_of(self, obj_tid); var vidx: usize = 0; if eir != 0 && TypeTable.agg_member_idx(&self.types.ir, eir, field_name, &vidx) { n.resolved_lang_tid = obj_tid; ret obj_tid; } } } var in = IntrinOps.lookup_field(obj.kind, field_name); if !intrin_is(in, .None) { var t = intrin_field(self, n, in); n.resolved_lang_tid = t; ret t; } var agg_lang = obj_tid; if kind_is(self, obj_tid, .Ptr) { agg_lang = elem_of_lang(obj.*); } var agg = lang_of(self, agg_lang); if agg == null { diag(self, n.span, .NoField, "field access on invalid type"); ret 0; } var agg_ir = ir_of(self, agg_lang); if agg_ir != 0 { var idx: usize = 0; if TypeTable.agg_member_idx(&self.types.ir, agg_ir, field_name, &idx) { var fir = agg_variant_ir(self, agg_ir, idx); var ft = lang_from_ir(self, fir); if ft == 0 { diag(self, n.span, .NoField, "cannot resolve field type"); ret 0; } n.resolved_lang_tid = ft; ret ft; } } var did = lang_find_method(self, agg_lang, field_name); if did != 0 { n.resolved_def_id = did; var t = eval_decl(self, did); n.resolved_lang_tid = t; ret t; } diag_name(self, n.span, .NoField, "no field `", field_name); ret 0; } fn infer_index(self: *Sema, n: *Node, obj_ref: usize, index_ref: usize): usize { var obj_tid = infer(self, obj_ref); infer(self, index_ref); var obj = lang_of(self, obj_tid); if obj == null { ret 0; } var elem: usize = 0; if kind_is(self, obj_tid, .Array) || kind_is(self, obj_tid, .Slice) || kind_is(self, obj_tid, .Range) || kind_is(self, obj_tid, .Ptr) { elem = elem_of_lang(obj.*); } else if kind_is(self, obj_tid, .Tuple) { var idx_node = node(self, index_ref); if idx_node == null || !is_expr_int_lit(idx_node) { diag(self, n.span, .TupleIndexOob, "tuple index must be integer literal"); ret 0; } var iv: i64 = -1; match idx_node.body { .ExprIntLit(lit) => { BigInt.to_i64(&iv, lit.value); }, _ => {}, } var elems = ref_vec(); match obj.extra { .Tuple(v) => { elems = v; }, _ => {} } if iv < 0 || (iv as usize) >= elems.len { diag(self, n.span, .TupleIndexOob, "tuple index out of range"); ret 0; } elem = list_at(elems, iv as usize); } else { diag(self, n.span, .IndexBaseInvalid, "index base must be array/slice/ptr/range/tuple"); ret 0; } n.resolved_lang_tid = elem; ret elem; } fn infer_slice(self: *Sema, n: *Node, obj_ref: usize, begin_ref: usize, end_ref: usize): usize { var obj_tid = infer(self, obj_ref); if begin_ref != 0 { infer(self, begin_ref); } if end_ref != 0 { infer(self, end_ref); } var obj = lang_of(self, obj_tid); if obj == null { ret 0; } if !(kind_is(self, obj_tid, .Array) || kind_is(self, obj_tid, .Ptr) || kind_is(self, obj_tid, .Slice)) { diag(self, n.span, .IndexBaseInvalid, "slice base must be array/ptr/slice"); ret 0; } var elem = elem_of_lang(obj.*); var s = LangType.make_slice(&self.types, elem); n.resolved_lang_tid = s; ret s; } fn infer_cast(self: *Sema, n: *Node, expr_ref: usize, type_ref: usize): usize { var dst = infer(self, type_ref); if dst == 0 { ret 0; } var src = infer(self, expr_ref); if src == 0 { ret 0; } if (kind_is(self, src, .Int) && int_bits(self, src) == 0) || (kind_is(self, src, .Float) && float_bits(self, src) == 0) { var src_node = node(self, expr_ref); if src_node != null && (is_expr_int_lit(src_node) || is_expr_float_lit(src_node)) { src_node.resolved_lang_tid = dst; } else if coerce(self, expr_ref, dst) == 0 { ret 0; } n.resolved_lang_tid = dst; ret dst; } var ft = ir_of(self, src); var tt = ir_of(self, dst); if ft == 0 || tt == 0 { diag(self, n.span, .CastInvalid, "as: unresolved source or target type"); ret 0; } var in = IntrinOps.lookup_cast(&self.types.ir, ft, tt); if intrin_is(in, .None) { diag(self, n.span, .CastInvalid, "as: invalid conversion"); ret 0; } n.resolved_lang_tid = dst; ret dst; } fn is_expr_float_lit(n: *Node): bool { match n.body { .ExprFloatLit(&x) => { ret true; }, _ => { ret false; } } ret false; } fn infer_array(self: *Sema, n: *Node, elem_type_ref: usize, has_len: bool, len_decl: usize, is_underscore_len: bool, elems: Vec): usize { var elem_t: usize = 0; if elem_type_ref != 0 { elem_t = infer(self, elem_type_ref); } var nelems = elems.len; var i: usize = 0; while i < nelems { var et = infer(self, list_at(elems, i)); if et == 0 { ret 0; } if elem_t == 0 { elem_t = et; } else { var c = if elem_t == et { elem_t } else { LangType.join(&self.types, elem_t, et) }; if c != 0 { elem_t = c; } } i = i + 1; } if elem_t == 0 { elem_t = make_infer(self); } var len = nelems; if !is_underscore_len && has_len { len = len_decl; } var arr = LangType.make_array(&self.types, elem_t, len); n.resolved_lang_tid = arr; ret arr; } fn infer_aggregate(self: *Sema, n: *Node, type_name: ByteView, fields: Vec): usize { if type_name.len == 0 { var inf = make_infer(self); n.resolved_lang_tid = inf; ret inf; } var sym = Symbol { .name = ByteView.empty(), .kind = .Error, .node = 0 }; if !Scope.find(&self.scope, type_name, &sym) { diag_name(self, n.span, .UndefinedSymbol, "unknown type `", type_name); ret 0; } var d = def_node(self, sym.node); if d == null || d.lang_id == 0 { diag_name(self, n.span, .UndefinedSymbol, "type not resolved `", type_name); ret 0; } var agg = d.lang_id; if !(kind_is(self, agg, .Struct) || kind_is(self, agg, .Union) || kind_is(self, agg, .Enum)) { diag_name(self, n.span, .TypeMismatch, "not a struct/union/enum `", type_name); ret 0; } if kind_is(self, agg, .Enum) && fields.len != 1 { diag(self, n.span, .TypeMismatch, "enum literal must have one variant"); ret 0; } var agg_ir = ir_of(self, agg); var field_irs = agg_variants_copy(self, agg_ir); var i: usize = 0; while i < fields.len { var item = node(self, list_at(fields, i)); if item != null && kind_is_body_struct_init(item) { var iname = ByteView.empty(); var iexpr: usize = 0; match item.body { .StructInitItem(si) => { iname = si.name; iexpr = si.expr; }, _ => {} } var idx: usize = 0; if !TypeTable.agg_member_idx(&self.types.ir, agg_ir, iname, &idx) { diag_name(self, item.span, .NoField, "no field `", iname); i = i + 1; continue; } if idx < field_irs.len { var ft = lang_from_ir(self, list_at(field_irs, idx)); if ft != 0 && iexpr != 0 { coerce(self, iexpr, ft); } } } i = i + 1; } n.resolved_lang_tid = agg; ret agg; } fn infer_for(self: *Sema, n: *Node, label_ref: usize, iter_exprs: Vec, pattern_ref: usize, body_ref: usize, else_ref: usize): usize { push_view(&self.label_stack, label_name(self, label_ref)); push_scope(self); var nsrc = iter_exprs.len; var pat = node(self, pattern_ref); var is_list = pat != null && kind_is_body_pattern_list(pat); if is_list { var atoms = ref_vec(); match pat.body { .PatternList(pl) => { atoms = pl.items; }, _ => {} } if atoms.len != nsrc { diag(self, pat.span, .TypeMismatch, "for: pattern count != source count"); } var i: usize = 0; while i < nsrc && i < atoms.len { var ct = infer(self, list_at(iter_exprs, i)); bind_pattern(self, list_at(atoms, i), for_elem_type(self, ct)); i = i + 1; } } else { if nsrc > 1 { diag(self, n.span, .TypeMismatch, "for: multiple sources require pattern list"); } var i: usize = 0; while i < nsrc { var ct = infer(self, list_at(iter_exprs, i)); if i == 0 { bind_pattern(self, pattern_ref, for_elem_type(self, ct)); } i = i + 1; } } if body_ref != 0 { infer(self, body_ref); } if else_ref != 0 { infer(self, else_ref); } pop_scope(self); pop_label(self); ret 0; } fn kind_is_body_pattern_list(n: *Node): bool { match n.body { .PatternList(&x) => { ret true; }, _ => { ret false; } } ret false; } fn infer_fn_decl(self: *Sema, n: *Node, params_ref: Vec, ret_ref: usize, body_ref: usize): usize { if n.resolved_def_id == 0 { ret 0; } var d = def_node(self, n.resolved_def_id); if d == null { ret 0; } var owner_def_id = d.owner_def; var fn_tid = d.lang_id; if fn_tid == 0 { fn_tid = eval_decl(self, n.resolved_def_id); } var fn_ln = lang_of(self, fn_tid); if fn_ln == null || !kind_is(self, fn_tid, .Fn) { ret 0; } var ret_tid: usize = 0; match fn_ln.extra { .Fn(f) => { ret_tid = f.ret_type; }, _ => {} } if body_ref == 0 { ret fn_tid; } var saved_agg = self.current_aggregate_lang; if owner_def_id != 0 { var owner = def_node(self, owner_def_id); if owner != null && owner.lang_id != 0 { self.current_aggregate_lang = owner.lang_id; } } push_scope(self); var i: usize = 0; while i < params_ref.len { var pref = list_at(params_ref, i); var pd = node(self, pref); if pd != null && is_param_decl(pd) { var pname = ByteView.empty(); var ptype: usize = 0; match pd.body { .ParamDecl(p) => { pname = p.name; ptype = p.type_expr; }, _ => {} } var psym = Symbol { .name = pname, .kind = .Var, .node = 0 }; psym.node = LangType.alloc_def(&self.types, .Var, pname, pd.span, self.ast as *any, pref); var p_tid = infer(self, ptype); var pdef = def_node(self, psym.node); if pdef != null { pdef.lang_id = p_tid; pdef.state = if p_tid != 0 { .Resolved } else { .Error }; } pd.resolved_def_id = psym.node; if !Scope.insert(&self.scope, self.scope.current_scope, psym) { diag_name(self, pd.span, .DuplicateSymbol, "duplicate symbol `", pname); } } i = i + 1; } var old_ret = self.current_fn_ret_tid; self.current_fn_ret_tid = ret_tid; infer(self, body_ref); self.current_fn_ret_tid = old_ret; pop_scope(self); self.current_aggregate_lang = saved_agg; ret fn_tid; } fn infer_type_decl(self: *Sema, n: *Node, pattern_ref: usize, type_ref: usize): usize { var pat = node(self, pattern_ref); if pat == null || !is_pattern_ident(pat) { ret 0; } if type_ref == 0 { ret 0; } var def: *DefNode = null; if n.resolved_def_id != 0 { def = def_node(self, n.resolved_def_id); } if def == null { ret 0; } var lang = def.lang_id; if lang == 0 { lang = eval_decl(self, n.resolved_def_id); } if lang == 0 { ret 0; } var te = node(self, type_ref); if te != null && is_type_aggregate(te) { var members = ref_vec(); match te.body { .TypeAggregate(a) => { members = a.members; }, _ => {} } var i: usize = 0; while i < members.len { var mref = list_at(members, i); var mn = node(self, mref); if mn != null && kind_is_body_fn_decl(mn) { infer(self, mref); } i = i + 1; } } ret lang; } fn kind_is_body_fn_decl(n: *Node): bool { match n.body { .FnDecl(&x) => { ret true; }, _ => { ret false; } } ret false; } fn infer_ret(self: *Sema, n: *Node, expr_ref: usize): usize { if expr_ref != 0 { if self.current_fn_ret_tid != 0 { coerce(self, expr_ref, self.current_fn_ret_tid); } else { infer(self, expr_ref); } } else if self.current_fn_ret_tid != 0 { var u = make_unit(self); if self.current_fn_ret_tid != u { diag(self, n.span, .MustReturnValue, "function must return a value"); } } ret 0; } fn infer_break(self: *Sema, n: *Node, label_ref: usize, expr_ref: usize): usize { var lbl = label_name(self, label_ref); if self.label_stack.len == 0 { diag(self, n.span, .BreakOutsideLoop, "`break` outside of loop"); ret 0; } if lbl.len != 0 && !label_in_stack(self, lbl) { diag_name(self, n.span, .UndefinedLabel, "undefined label `$", lbl); } if expr_ref != 0 { if self.current_loop_break_tid != 0 { coerce(self, expr_ref, self.current_loop_break_tid); } else { infer(self, expr_ref); } } ret 0; } fn infer_continue(self: *Sema, n: *Node, label_ref: usize): usize { var lbl = label_name(self, label_ref); if self.label_stack.len == 0 { diag(self, n.span, .ContinueOutsideLoop, "`continue` outside of loop"); ret 0; } if lbl.len != 0 && !label_in_stack(self, lbl) { diag_name(self, n.span, .UndefinedLabel, "undefined label `$", lbl); } ret 0; } fn label_in_stack(self: *Sema, lbl: ByteView): bool { var i = self.label_stack.len; while i > 0 { var cur: ByteView = ByteView.empty(); match Vec.get(&self.label_stack, i - 1) { .Ok(ptr) => { cur = (ptr as *ByteView).*; }, .Err(e) => {}, } if cur.len != 0 && ByteView.eq(cur, lbl) { ret true; } i = i - 1; } ret false; } type SymPrint = struct { key: ByteView, name: ByteView, node: usize, } fn sym_at(v: *Vec, i: usize): *SymPrint { match Vec.get(v, i) { .Ok(ptr) => { ret ptr as *SymPrint; }, .Err(error) => { ret null; }, } ret null; } fn out_str(buf: *ByteBuffer, s: []u8): bool { match ByteBuffer.append(buf, s) { .Ok => { ret true; }, .Err(error) => { ret false; }, } ret false; } fn out_u(buf: *ByteBuffer, v: usize): bool { match append_usize(buf, v, 10, false) { .Ok => { ret true; }, .Err(error) => { ret false; }, } ret false; } fn dump(self: *Sema, out: *ByteBuffer): bool { if !out_str(out, "Scopes:\n") { ret false; } var i: usize = 1; while i < self.scope.scopes.len { var sn = Scope.node(&self.scope, i); if sn == null { i = i + 1; continue; } if !out_str(out, "scope[") { ret false; } if !out_u(out, i) { ret false; } if !out_str(out, "] parent=") { ret false; } if !out_u(out, sn.parent) { ret false; } if !out_str(out, "\n") { ret false; } // 收集后按符号名排序输出(顺序与哈希布局解耦)。 var entries = Vec.blank(@sizeof(SymPrint), @alignof(SymPrint)); var sc = Map.slot_count(sn.symbols); var j: usize = 0; while j < sc { if Map.slot_occupied(Map.slot_state(sn.symbols, j)) { var kp = Map.key_at(&sn.symbols, j); var vp = Map.val_at(&sn.symbols, j); var sym = (vp as *Symbol).*; var sp = SymPrint { .key = (kp as *ByteView).*, .name = sym.name, .node = sym.node, }; Vec.push(&entries, &sp as *u8); } j = j + 1; } // 插入排序(作用域内符号很少) var a: usize = 1; while a < entries.len { var cur = sym_at(&entries, a).*; var b = a; while b > 0 { var prev = sym_at(&entries, b - 1).*; if ByteView.cmp(prev.key, cur.key) <= 0 { break; } var pv = prev; Vec.set(&entries, b, &pv as *u8); b = b - 1; } var cv = cur; Vec.set(&entries, b, &cv as *u8); a = a + 1; } var e: usize = 0; while e < entries.len { var sp2 = sym_at(&entries, e).*; if !out_str(out, " ") { ret false; } if !out_str(out, ByteView.as_slice(sp2.key)) { ret false; } if !out_str(out, " -> def`") { ret false; } if !out_str(out, ByteView.as_slice(sp2.name)) { ret false; } if !out_str(out, "`#") { ret false; } if !out_u(out, sp2.node) { ret false; } if !out_str(out, "\n") { ret false; } e = e + 1; } Vec.drop(&entries); i = i + 1; } if !out_str(out, "LangTypeTable:\n") { ret false; } i = 1; while i < self.types.lang_table.len { if !out_str(out, " lang_id#") { ret false; } if !out_u(out, i) { ret false; } if !out_str(out, " ") { ret false; } if !LangType.dump(&self.types, i, out) { ret false; } if !out_str(out, "\n") { ret false; } i = i + 1; } if !out_str(out, "TypeTable:\n") { ret false; } i = 1; while i < self.types.ir.table.len { if !out_str(out, " type_id#") { ret false; } if !out_u(out, i) { ret false; } if !out_str(out, " ") { ret false; } if !TypeTable.pure_dump(&self.types.ir, i, out) { ret false; } if !out_str(out, "\n") { ret false; } i = i + 1; } if !out_str(out, "DefTable:\n") { ret false; } i = 1; while i < self.types.defs.table.len { if !out_str(out, " def#") { ret false; } if !out_u(out, i) { ret false; } if !out_str(out, " ") { ret false; } if !DefTable.dump(&self.types.defs, i, out) { ret false; } if !out_str(out, "\n") { ret false; } i = i + 1; } ret Attr.dump(&self.attr, out); } // 走完整前端:lex -> parse -> sema;返回 0 表示无(新)诊断。 fn sema_source(text: []u8): i32 { var sources = set_new(); match set_add(&sources, ByteView.from_slice(""), ByteView.from_slice(text)) { .Ok(idx) => {}, .Err(error) => { ret 99; }, } var diag = Diag.new(&sources); var text_view = ByteView.from_slice(text); var tokens = Vec.blank(@sizeof(Token), @alignof(Token)); match Lexer.lex(text_view, &diag, 0) { .Ok(v) => { tokens = v; }, .Err(error) => { Vec.drop(&tokens); Diag.drop(&diag); set_drop(&sources); ret 99; }, } var ast: Ast = undefined; match Ast.new(&sources) { .Ok(v) => { ast = v; }, .Err(error) => { Vec.drop(&tokens); Diag.drop(&diag); set_drop(&sources); ret 99; }, } var p = Parser.new(&ast, tokens, &diag); Parser.parse(&p); if Diag.has_error(diag) || p.failed { Ast.drop(&ast); Vec.drop(&tokens); Diag.drop(&diag); set_drop(&sources); ret 1; } var sema: Sema = undefined; match new(&ast, &diag) { .Ok(v) => { sema = v; }, .Err(error) => { Ast.drop(&ast); Vec.drop(&tokens); Diag.drop(&diag); set_drop(&sources); ret 99; }, } run(&sema); var bad = Diag.has_error(diag); drop(&sema); Ast.drop(&ast); Vec.drop(&tokens); Diag.drop(&diag); set_drop(&sources); if bad { ret 2; } ret 0; } fn main() { @assert(sema_source("fn main(): i32 { ret 0; }") == 0, "sema simple"); @assert(sema_source("fn main(): i32 { var x: i32 = 1; ret x; }") == 0, "sema var"); @assert(sema_source("fn main(): i32 { ret 0; }\nfn bad(): i32 { ret nope; }") != 0, "sema undefined"); }