Files
spl/stage2/sema.spl
T
2026-10-08 22:46:31 +08:00

3088 lines
103 KiB
Plaintext

// 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(&params, infer(self, tref));
} else {
push_u(&params, LangType.make_varargs(&self.types));
}
} else {
push_u(&params, 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("<sema-test>"), 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");
}