1use std::sync::Arc;
51
52use crate::ast::{Expr, FnBody, FnDef, MatchArm, Pattern, Spanned, Stmt, TopLevel};
53use crate::ir::calls::{expr_to_dotted_name, is_builtin_namespace};
54use crate::ir::hir::{
55 BuiltinCtor, BuiltinIntrinsic, ResolvedCallee, ResolvedCtor, ResolvedExpr, ResolvedFnBody,
56 ResolvedFnDef, ResolvedMatchArm, ResolvedPattern, ResolvedStmt, ResolvedStrPart,
57 ResolvedTopLevel,
58};
59use crate::ir::identity::{FnId, FnKey, TypeId, TypeKey};
60use crate::ir::symbol_table::SymbolTable;
61
62pub struct ResolveCtx<'a> {
70 pub symbols: &'a SymbolTable,
71 pub current_module: Option<String>,
72}
73
74impl<'a> ResolveCtx<'a> {
75 pub fn new(symbols: &'a SymbolTable) -> Self {
76 Self {
77 symbols,
78 current_module: None,
79 }
80 }
81
82 pub fn resolve_fn_id(&self, name: &str) -> Option<FnId> {
91 if let Some((prefix, n)) = name.rsplit_once('.') {
92 if let Some(id) = self.symbols.fn_id_of(&FnKey::in_module(prefix, n)) {
93 return Some(id);
94 }
95 if self.current_module.as_deref() == Some(prefix)
96 && let Some(id) = self.symbols.fn_id_of(&FnKey::entry(n))
97 {
98 return Some(id);
99 }
100 }
101 if let Some(prefix) = self.current_module.as_deref()
102 && let Some(id) = self.symbols.fn_id_of(&FnKey::in_module(prefix, name))
103 {
104 return Some(id);
105 }
106 self.symbols.fn_id_of(&FnKey::entry(name))
107 }
108
109 pub fn resolve_type_id(&self, name: &str) -> Option<TypeId> {
125 if let Some((prefix, n)) = name.rsplit_once('.') {
126 if let Some(id) = self.symbols.type_id_of(&TypeKey::in_module(prefix, n)) {
127 return Some(id);
128 }
129 if self.current_module.as_deref() == Some(prefix)
130 && let Some(id) = self.symbols.type_id_of(&TypeKey::entry(n))
131 {
132 return Some(id);
133 }
134 }
135 if let Some(prefix) = self.current_module.as_deref()
136 && let Some(id) = self.symbols.type_id_of(&TypeKey::in_module(prefix, name))
137 {
138 return Some(id);
139 }
140 if let Some(id) = self.symbols.type_id_of(&TypeKey::entry(name)) {
141 return Some(id);
142 }
143 self.symbols.type_id_by_bare_name(name)
150 }
151
152 pub fn resolve_user_ctor(
155 &self,
156 dotted: &str,
157 ) -> Option<(crate::ir::identity::CtorId, TypeId, String)> {
158 let (type_name, variant) = dotted.rsplit_once('.')?;
159 let type_id = self.resolve_type_id(type_name)?;
160 let ctor_id = self.symbols.ctor_id_of(type_id, variant)?;
161 Some((ctor_id, type_id, variant.to_string()))
162 }
163}
164
165pub fn resolve_program(symbols: &SymbolTable, items: &[TopLevel]) -> Vec<ResolvedTopLevel> {
170 let module_name = items.iter().find_map(|i| match i {
171 TopLevel::Module(m) => Some(m.name.clone()),
172 _ => None,
173 });
174 let mut ctx = ResolveCtx::new(symbols);
175 ctx.current_module = module_name;
176 items.iter().map(|i| resolve_top_level(&ctx, i)).collect()
177}
178
179pub fn resolve_fn_def_external(ctx: &ResolveCtx<'_>, fd: &FnDef) -> Option<ResolvedFnDef> {
185 resolve_fn_def(ctx, fd)
186}
187
188pub fn resolve_top_level(ctx: &ResolveCtx<'_>, item: &TopLevel) -> ResolvedTopLevel {
189 match item {
190 TopLevel::Module(m) => ResolvedTopLevel::Module(m.clone()),
191 TopLevel::FnDef(fd) => match resolve_fn_def(ctx, fd) {
192 Some(rfd) => ResolvedTopLevel::FnDef(rfd),
193 None => ResolvedTopLevel::Passthrough(item.clone()),
201 },
202 TopLevel::Verify(_) | TopLevel::Decision(_) | TopLevel::Stmt(_) | TopLevel::TypeDef(_) => {
207 ResolvedTopLevel::Passthrough(item.clone())
208 }
209 }
210}
211
212fn resolve_fn_def(ctx: &ResolveCtx<'_>, fd: &FnDef) -> Option<ResolvedFnDef> {
213 let fn_id = ctx.resolve_fn_id(&fd.name)?;
214 let params: Vec<(String, crate::ast::Type)> = fd
215 .params
216 .iter()
217 .map(|(name, ann)| {
218 let ty = match crate::types::parse_type_str_strict(ann) {
219 Ok(t) => canonicalise_type(ctx, t),
220 Err(_) => crate::ast::Type::Invalid,
221 };
222 (name.clone(), ty)
223 })
224 .collect();
225 let return_type = match crate::types::parse_type_str_strict(&fd.return_type) {
226 Ok(t) => canonicalise_type(ctx, t),
227 Err(_) => crate::ast::Type::Invalid,
228 };
229 let body = resolve_fn_body(ctx, &fd.body);
230 Some(ResolvedFnDef {
231 fn_id,
232 name: fd.name.clone(),
233 line: fd.line,
234 params,
235 return_type,
236 effects: fd.effects.clone(),
237 desc: fd.desc.clone(),
238 body: Arc::new(body),
239 resolution: fd.resolution.clone(),
240 })
241}
242
243fn resolve_fn_body(ctx: &ResolveCtx<'_>, body: &FnBody) -> ResolvedFnBody {
244 match body {
245 FnBody::Block(stmts) => {
246 ResolvedFnBody::Block(stmts.iter().map(|s| resolve_stmt(ctx, s)).collect())
247 }
248 }
249}
250
251pub fn resolve_stmt_external(ctx: &ResolveCtx<'_>, stmt: &Stmt) -> ResolvedStmt {
258 resolve_stmt(ctx, stmt)
259}
260
261fn resolve_stmt(ctx: &ResolveCtx<'_>, stmt: &Stmt) -> ResolvedStmt {
262 match stmt {
263 Stmt::Binding(name, ann, expr) => {
264 let ty_ann = ann
265 .as_deref()
266 .map(|src| match crate::types::parse_type_str_strict(src) {
267 Ok(t) => canonicalise_type(ctx, t),
268 Err(_) => crate::ast::Type::Invalid,
269 });
270 ResolvedStmt::Binding {
271 name: name.clone(),
272 ty_ann,
273 value: resolve_spanned(ctx, expr),
274 }
275 }
276 Stmt::Expr(expr) => ResolvedStmt::Expr(resolve_spanned(ctx, expr)),
277 }
278}
279
280fn resolve_spanned(ctx: &ResolveCtx<'_>, expr: &Spanned<Expr>) -> Spanned<ResolvedExpr> {
281 let resolved = resolve_expr(ctx, expr);
282 let out = Spanned::new(resolved, expr.line);
283 if let Some(t) = expr.ty.get() {
284 let _ = out.ty.set(t.clone());
285 }
286 out
287}
288
289fn resolve_expr(ctx: &ResolveCtx<'_>, expr: &Spanned<Expr>) -> ResolvedExpr {
290 match &expr.node {
291 Expr::Literal(l) => ResolvedExpr::Literal(l.clone()),
292 Expr::Ident(name) => ResolvedExpr::Ident(name.clone()),
293 Expr::Resolved {
294 slot,
295 name,
296 last_use,
297 } => ResolvedExpr::Resolved {
298 slot: *slot,
299 name: name.clone(),
300 last_use: *last_use,
301 },
302 Expr::Attr(obj, field) => {
303 if let Expr::Ident(ns) = &obj.node {
314 let qualified = format!("{ns}.{field}");
315 let ctor = classify_ctor(ctx, &qualified);
316 let nullary = matches!(
317 ctor,
318 ResolvedCtor::Builtin(BuiltinCtor::OptionNone) | ResolvedCtor::User { .. }
319 );
320 if nullary {
321 return ResolvedExpr::Ctor(ctor, vec![]);
322 }
323 }
324 ResolvedExpr::Attr(Box::new(resolve_spanned(ctx, obj)), field.clone())
325 }
326 Expr::FnCall(callee, args) => {
327 let resolved_args: Vec<Spanned<ResolvedExpr>> =
328 args.iter().map(|a| resolve_spanned(ctx, a)).collect();
329 if let Some(dotted) = expr_to_dotted_name(&callee.node) {
336 if let Some(builtin) = parse_builtin_ctor(&dotted) {
337 return ResolvedExpr::Ctor(ResolvedCtor::Builtin(builtin), resolved_args);
338 }
339 if let Some((ctor_id, type_id, variant_name)) = ctx.resolve_user_ctor(&dotted) {
340 return ResolvedExpr::Ctor(
341 ResolvedCtor::User {
342 ctor_id,
343 type_id,
344 name: variant_name,
345 },
346 resolved_args,
347 );
348 }
349 }
350 let resolved_callee = classify_callee(ctx, callee);
351 ResolvedExpr::Call(resolved_callee, resolved_args)
352 }
353 Expr::BinOp(op, l, r) => ResolvedExpr::BinOp(
354 *op,
355 Box::new(resolve_spanned(ctx, l)),
356 Box::new(resolve_spanned(ctx, r)),
357 ),
358 Expr::Neg(inner) => ResolvedExpr::Neg(Box::new(resolve_spanned(ctx, inner))),
359 Expr::Match { subject, arms } => ResolvedExpr::Match {
360 subject: Box::new(resolve_spanned(ctx, subject)),
361 arms: arms.iter().map(|a| resolve_match_arm(ctx, a)).collect(),
362 },
363 Expr::Constructor(name, arg) => {
364 let ctor = classify_ctor(ctx, name);
365 let args = match arg {
366 Some(a) => vec![resolve_spanned(ctx, a)],
367 None => vec![],
368 };
369 ResolvedExpr::Ctor(ctor, args)
370 }
371 Expr::ErrorProp(inner) => ResolvedExpr::ErrorProp(Box::new(resolve_spanned(ctx, inner))),
372 Expr::InterpolatedStr(parts) => ResolvedExpr::InterpolatedStr(
373 parts
374 .iter()
375 .map(|p| match p {
376 crate::ast::StrPart::Literal(s) => ResolvedStrPart::Literal(s.clone()),
377 crate::ast::StrPart::Parsed(inner) => {
378 ResolvedStrPart::Parsed(Box::new(resolve_spanned(ctx, inner)))
379 }
380 })
381 .collect(),
382 ),
383 Expr::List(items) => {
384 ResolvedExpr::List(items.iter().map(|i| resolve_spanned(ctx, i)).collect())
385 }
386 Expr::Tuple(items) => {
387 ResolvedExpr::Tuple(items.iter().map(|i| resolve_spanned(ctx, i)).collect())
388 }
389 Expr::MapLiteral(pairs) => ResolvedExpr::MapLiteral(
390 pairs
391 .iter()
392 .map(|(k, v)| (resolve_spanned(ctx, k), resolve_spanned(ctx, v)))
393 .collect(),
394 ),
395 Expr::RecordCreate { type_name, fields } => ResolvedExpr::RecordCreate {
396 type_id: ctx.resolve_type_id(type_name),
397 type_name: type_name.clone(),
398 fields: fields
399 .iter()
400 .map(|(n, e)| (n.clone(), resolve_spanned(ctx, e)))
401 .collect(),
402 },
403 Expr::RecordUpdate {
404 type_name,
405 base,
406 updates,
407 } => ResolvedExpr::RecordUpdate {
408 type_id: ctx.resolve_type_id(type_name),
409 type_name: type_name.clone(),
410 base: Box::new(resolve_spanned(ctx, base)),
411 updates: updates
412 .iter()
413 .map(|(n, e)| (n.clone(), resolve_spanned(ctx, e)))
414 .collect(),
415 },
416 Expr::TailCall(data) => {
417 let target = ctx.resolve_fn_id(&data.target);
418 let args = data.args.iter().map(|a| resolve_spanned(ctx, a)).collect();
419 match target {
420 Some(fn_id) => ResolvedExpr::TailCall {
421 target: fn_id,
422 args,
423 },
424 None => ResolvedExpr::Call(
431 ResolvedCallee::Unresolved {
432 callee: Box::new(Spanned::new(
433 ResolvedExpr::Ident(data.target.clone()),
434 expr.line,
435 )),
436 },
437 args,
438 ),
439 }
440 }
441 Expr::IndependentProduct(items, unwrap) => ResolvedExpr::IndependentProduct(
442 items.iter().map(|i| resolve_spanned(ctx, i)).collect(),
443 *unwrap,
444 ),
445 }
446}
447
448fn resolve_match_arm(ctx: &ResolveCtx<'_>, arm: &MatchArm) -> ResolvedMatchArm {
449 let binding_slots = std::sync::OnceLock::new();
450 if let Some(slots) = arm.binding_slots.get() {
451 let _ = binding_slots.set(slots.clone());
452 }
453 ResolvedMatchArm {
454 pattern: resolve_pattern(ctx, &arm.pattern),
455 body: Box::new(resolve_spanned(ctx, &arm.body)),
456 binding_slots,
457 }
458}
459
460fn resolve_pattern(ctx: &ResolveCtx<'_>, pat: &Pattern) -> ResolvedPattern {
461 match pat {
462 Pattern::Wildcard => ResolvedPattern::Wildcard,
463 Pattern::Literal(l) => ResolvedPattern::Literal(l.clone()),
464 Pattern::Ident(name) => ResolvedPattern::Ident(name.clone()),
465 Pattern::EmptyList => ResolvedPattern::EmptyList,
466 Pattern::Cons(head, tail) => ResolvedPattern::Cons(head.clone(), tail.clone()),
467 Pattern::Tuple(items) => {
468 ResolvedPattern::Tuple(items.iter().map(|p| resolve_pattern(ctx, p)).collect())
469 }
470 Pattern::Constructor(name, bindings) => {
471 ResolvedPattern::Ctor(classify_ctor(ctx, name), bindings.clone())
472 }
473 }
474}
475
476fn classify_callee(ctx: &ResolveCtx<'_>, callee: &Spanned<Expr>) -> ResolvedCallee {
485 match &callee.node {
486 Expr::Resolved {
487 slot,
488 name,
489 last_use,
490 } => ResolvedCallee::LocalSlot {
491 slot: *slot,
492 name: name.clone(),
493 last_use: *last_use,
494 },
495 Expr::Ident(name) => {
496 if let Some(intrinsic) = BuiltinIntrinsic::from_name(name) {
497 return ResolvedCallee::Intrinsic(intrinsic);
498 }
499 if let Some((head, _)) = name.split_once('.')
508 && is_builtin_namespace(head)
509 {
510 return ResolvedCallee::Builtin(name.clone());
511 }
512 match ctx.resolve_fn_id(name) {
513 Some(id) => ResolvedCallee::Fn(id),
514 None => ResolvedCallee::Unresolved {
515 callee: Box::new(resolve_spanned(ctx, callee)),
516 },
517 }
518 }
519 Expr::Attr(_, _) => {
520 let Some(dotted) = expr_to_dotted_name(&callee.node) else {
521 return ResolvedCallee::Unresolved {
522 callee: Box::new(resolve_spanned(ctx, callee)),
523 };
524 };
525 if let Some(id) = ctx.resolve_fn_id(&dotted) {
528 return ResolvedCallee::Fn(id);
529 }
530 if let Some((head, _rest)) = dotted.split_once('.')
533 && is_builtin_namespace(head)
534 {
535 return ResolvedCallee::Builtin(dotted);
536 }
537 ResolvedCallee::Unresolved {
538 callee: Box::new(resolve_spanned(ctx, callee)),
539 }
540 }
541 _ => ResolvedCallee::Unresolved {
542 callee: Box::new(resolve_spanned(ctx, callee)),
543 },
544 }
545}
546
547fn classify_ctor(ctx: &ResolveCtx<'_>, name: &str) -> ResolvedCtor {
553 if let Some(builtin) = parse_builtin_ctor(name) {
554 return ResolvedCtor::Builtin(builtin);
555 }
556 if let Some((ctor_id, type_id, variant_name)) = ctx.resolve_user_ctor(name) {
557 return ResolvedCtor::User {
558 ctor_id,
559 type_id,
560 name: variant_name,
561 };
562 }
563 ResolvedCtor::Unresolved {
564 name: name.to_string(),
565 }
566}
567
568fn parse_builtin_ctor(name: &str) -> Option<BuiltinCtor> {
569 match name {
570 "Result.Ok" => Some(BuiltinCtor::ResultOk),
571 "Result.Err" => Some(BuiltinCtor::ResultErr),
572 "Option.Some" => Some(BuiltinCtor::OptionSome),
573 "Option.None" | "None" => Some(BuiltinCtor::OptionNone),
577 _ => None,
578 }
579}
580
581fn canonicalise_type(ctx: &ResolveCtx<'_>, ty: crate::ast::Type) -> crate::ast::Type {
587 use crate::ast::Type;
588 match ty {
589 Type::Named {
592 id: Some(existing),
593 name,
594 } => Type::Named {
595 id: Some(existing),
596 name,
597 },
598 Type::Named { id: None, name } => match ctx.resolve_type_id(&name) {
599 Some(id) => Type::Named { id: Some(id), name },
600 None => Type::Named { id: None, name },
601 },
602 Type::List(inner) => Type::List(Box::new(canonicalise_type(ctx, *inner))),
603 Type::Vector(inner) => Type::Vector(Box::new(canonicalise_type(ctx, *inner))),
604 Type::Option(inner) => Type::Option(Box::new(canonicalise_type(ctx, *inner))),
605 Type::Result(ok, err) => Type::Result(
606 Box::new(canonicalise_type(ctx, *ok)),
607 Box::new(canonicalise_type(ctx, *err)),
608 ),
609 Type::Map(k, v) => Type::Map(
610 Box::new(canonicalise_type(ctx, *k)),
611 Box::new(canonicalise_type(ctx, *v)),
612 ),
613 Type::Tuple(items) => Type::Tuple(
614 items
615 .into_iter()
616 .map(|t| canonicalise_type(ctx, t))
617 .collect(),
618 ),
619 Type::Fn(params, ret, effects) => Type::Fn(
620 params
621 .into_iter()
622 .map(|t| canonicalise_type(ctx, t))
623 .collect(),
624 Box::new(canonicalise_type(ctx, *ret)),
625 effects,
626 ),
627 other => other,
628 }
629}
630
631#[cfg(test)]
632mod tests {
633 use super::*;
634 use crate::ast::{BinOp, Literal};
635 use crate::source::parse_source;
636 use crate::tco;
637
638 fn build(src: &str) -> (SymbolTable, Vec<TopLevel>) {
639 let mut items = parse_source(src).expect("parse failed");
640 tco::transform_program(&mut items);
641 let symbols = SymbolTable::build(&items, &[]);
642 (symbols, items)
643 }
644
645 fn first_fn(resolved: &[ResolvedTopLevel]) -> &ResolvedFnDef {
646 resolved
647 .iter()
648 .find_map(|t| match t {
649 ResolvedTopLevel::FnDef(f) => Some(f),
650 _ => None,
651 })
652 .expect("expected at least one resolved fn def")
653 }
654
655 #[test]
656 fn resolves_user_fn_call_to_fn_id() {
657 let (symbols, items) = build(
658 r#"
659fn main() -> Int
660 helper(1)
661
662fn helper(n: Int) -> Int
663 n + 1
664"#,
665 );
666 let resolved = resolve_program(&symbols, &items);
667 let main_fn = resolved
668 .iter()
669 .find_map(|t| match t {
670 ResolvedTopLevel::FnDef(f) if f.name == "main" => Some(f),
671 _ => None,
672 })
673 .expect("main");
674 let stmts = main_fn.body.stmts();
675 let ResolvedStmt::Expr(call) = &stmts[0] else {
676 panic!("expected tail Expr stmt, got {:?}", stmts[0]);
677 };
678 match &call.node {
679 ResolvedExpr::Call(ResolvedCallee::Fn(_), args) => {
680 assert_eq!(args.len(), 1);
681 }
682 other => panic!("expected Call(Fn, _), got {:?}", other),
683 }
684 }
685
686 #[test]
687 fn resolves_builtin_namespace_method_call() {
688 let (symbols, items) = build(
689 r#"
690fn add() -> Int
691 Int.abs(-3)
692"#,
693 );
694 let resolved = resolve_program(&symbols, &items);
695 let f = first_fn(&resolved);
696 let ResolvedStmt::Expr(call) = &f.body.stmts()[0] else {
697 panic!("expected tail Expr");
698 };
699 match &call.node {
700 ResolvedExpr::Call(ResolvedCallee::Builtin(name), _) => {
701 assert_eq!(name, "Int.abs");
702 }
703 other => panic!("expected Call(Builtin, _), got {:?}", other),
704 }
705 }
706
707 #[test]
708 fn resolves_user_ctor_to_ctor_id_and_owning_type() {
709 let (symbols, items) = build(
710 r#"
711type Shape
712 Circle(Float)
713 Square(Float)
714
715fn make() -> Shape
716 Shape.Circle(1.0)
717"#,
718 );
719 let resolved = resolve_program(&symbols, &items);
720 let f = first_fn(&resolved);
721 let ResolvedStmt::Expr(call) = &f.body.stmts()[0] else {
722 panic!("expected tail Expr");
723 };
724 match &call.node {
725 ResolvedExpr::Ctor(ResolvedCtor::User { type_id, name, .. }, args) => {
726 assert_eq!(name, "Circle");
727 assert!(*type_id != crate::ir::identity::TypeId(u32::MAX));
728 assert_eq!(args.len(), 1);
729 }
730 other => panic!("expected Ctor(User, _), got {:?}", other),
731 }
732 }
733
734 #[test]
735 fn resolves_builtin_ctor_result_ok() {
736 let (symbols, items) = build(
737 r#"
738fn make() -> Result<Int, String>
739 Result.Ok(42)
740"#,
741 );
742 let resolved = resolve_program(&symbols, &items);
743 let f = first_fn(&resolved);
744 let ResolvedStmt::Expr(expr) = &f.body.stmts()[0] else {
745 panic!("expected tail Expr");
746 };
747 match &expr.node {
748 ResolvedExpr::Ctor(ResolvedCtor::Builtin(BuiltinCtor::ResultOk), args) => {
749 assert_eq!(args.len(), 1);
750 }
751 other => panic!("expected Builtin(ResultOk), got {:?}", other),
752 }
753 }
754
755 #[test]
756 fn resolves_record_create_to_type_id() {
757 let (symbols, items) = build(
758 r#"
759record Point
760 x: Int
761 y: Int
762
763fn origin() -> Point
764 Point(x = 0, y = 0)
765"#,
766 );
767 let resolved = resolve_program(&symbols, &items);
768 let f = first_fn(&resolved);
769 let ResolvedStmt::Expr(expr) = &f.body.stmts()[0] else {
770 panic!("expected tail Expr");
771 };
772 match &expr.node {
773 ResolvedExpr::RecordCreate {
774 type_id, type_name, ..
775 } => {
776 assert!(type_id.is_some(), "Point should resolve to a TypeId");
777 assert_eq!(type_name, "Point");
778 }
779 other => panic!("expected RecordCreate, got {:?}", other),
780 }
781 }
782
783 #[test]
784 fn resolves_tail_call_target_to_fn_id() {
785 let (symbols, items) = build(
786 r#"
787fn count(n: Int, acc: Int) -> Int
788 match n
789 0 -> acc
790 _ -> count(n - 1, acc + 1)
791"#,
792 );
793 let resolved = resolve_program(&symbols, &items);
794 let f = first_fn(&resolved);
795 let ResolvedStmt::Expr(top) = &f.body.stmts()[0] else {
797 panic!("expected tail Expr");
798 };
799 let ResolvedExpr::Match { arms, .. } = &top.node else {
800 panic!("expected match");
801 };
802 let recursive_body = &arms[1].body.node;
804 match recursive_body {
805 ResolvedExpr::TailCall { target, args } => {
806 assert_eq!(args.len(), 2);
807 let _ = target;
810 }
811 other => panic!("expected TailCall, got {:?}", other),
812 }
813 }
814
815 #[test]
816 fn resolves_binding_annotation_to_canonicalised_type() {
817 let (symbols, items) = build(
818 r#"
819fn pair() -> Int
820 x: Int = 5
821 x
822"#,
823 );
824 let resolved = resolve_program(&symbols, &items);
825 let f = first_fn(&resolved);
826 let stmts = f.body.stmts();
827 let ResolvedStmt::Binding { name, ty_ann, .. } = &stmts[0] else {
828 panic!("expected Binding stmt, got {:?}", stmts[0]);
829 };
830 assert_eq!(name, "x");
831 assert_eq!(ty_ann.as_ref(), Some(&crate::ast::Type::Int));
832 }
833
834 #[test]
835 fn binop_passes_through_structurally() {
836 let (symbols, items) = build(
837 r#"
838fn add() -> Int
839 1 + 2
840"#,
841 );
842 let resolved = resolve_program(&symbols, &items);
843 let f = first_fn(&resolved);
844 let ResolvedStmt::Expr(expr) = &f.body.stmts()[0] else {
845 panic!("tail expected");
846 };
847 match &expr.node {
848 ResolvedExpr::BinOp(BinOp::Add, l, r) => {
849 assert!(matches!(l.node, ResolvedExpr::Literal(Literal::Int(1))));
850 assert!(matches!(r.node, ResolvedExpr::Literal(Literal::Int(2))));
851 }
852 other => panic!("expected BinOp(Add, _, _), got {:?}", other),
853 }
854 }
855
856 #[test]
857 fn passthrough_for_verify_decision_typedef() {
858 let (symbols, items) = build(
859 r#"
860type Tag
861 On
862 Off
863
864verify alwaysTrue
865 1 => 1
866
867fn alwaysTrue() -> Int
868 1
869"#,
870 );
871 let resolved = resolve_program(&symbols, &items);
872 let mut saw_passthrough = false;
874 let mut saw_fn = false;
875 for item in &resolved {
876 match item {
877 ResolvedTopLevel::Passthrough(_) => saw_passthrough = true,
878 ResolvedTopLevel::FnDef(_) => saw_fn = true,
879 _ => {}
880 }
881 }
882 assert!(saw_passthrough, "expected at least one passthrough item");
883 assert!(saw_fn, "expected the FnDef to be promoted");
884 }
885
886 #[test]
887 fn ty_stamps_preserved_on_resolved_spans() {
888 use crate::ast::{Expr as AstExpr, Spanned as AstSpanned};
893 let lit = AstSpanned::new(AstExpr::Literal(Literal::Int(7)), 1);
894 let _ = lit.ty.set(crate::ast::Type::Int);
895 let symbols = SymbolTable::default();
898 let ctx = ResolveCtx::new(&symbols);
899 let resolved = resolve_spanned(&ctx, &lit);
900 assert_eq!(resolved.ty.get(), Some(&crate::ast::Type::Int));
901 }
902}