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 ResolvedExpr::Attr(Box::new(resolve_spanned(ctx, obj)), field.clone())
304 }
305 Expr::FnCall(callee, args) => {
306 let resolved_args: Vec<Spanned<ResolvedExpr>> =
307 args.iter().map(|a| resolve_spanned(ctx, a)).collect();
308 if let Some(dotted) = expr_to_dotted_name(&callee.node) {
315 if let Some(builtin) = parse_builtin_ctor(&dotted) {
316 return ResolvedExpr::Ctor(ResolvedCtor::Builtin(builtin), resolved_args);
317 }
318 if let Some((ctor_id, type_id, variant_name)) = ctx.resolve_user_ctor(&dotted) {
319 return ResolvedExpr::Ctor(
320 ResolvedCtor::User {
321 ctor_id,
322 type_id,
323 name: variant_name,
324 },
325 resolved_args,
326 );
327 }
328 }
329 let resolved_callee = classify_callee(ctx, callee);
330 ResolvedExpr::Call(resolved_callee, resolved_args)
331 }
332 Expr::BinOp(op, l, r) => ResolvedExpr::BinOp(
333 *op,
334 Box::new(resolve_spanned(ctx, l)),
335 Box::new(resolve_spanned(ctx, r)),
336 ),
337 Expr::Neg(inner) => ResolvedExpr::Neg(Box::new(resolve_spanned(ctx, inner))),
338 Expr::Match { subject, arms } => ResolvedExpr::Match {
339 subject: Box::new(resolve_spanned(ctx, subject)),
340 arms: arms.iter().map(|a| resolve_match_arm(ctx, a)).collect(),
341 },
342 Expr::Constructor(name, arg) => {
343 let ctor = classify_ctor(ctx, name);
344 let args = match arg {
345 Some(a) => vec![resolve_spanned(ctx, a)],
346 None => vec![],
347 };
348 ResolvedExpr::Ctor(ctor, args)
349 }
350 Expr::ErrorProp(inner) => ResolvedExpr::ErrorProp(Box::new(resolve_spanned(ctx, inner))),
351 Expr::InterpolatedStr(parts) => ResolvedExpr::InterpolatedStr(
352 parts
353 .iter()
354 .map(|p| match p {
355 crate::ast::StrPart::Literal(s) => ResolvedStrPart::Literal(s.clone()),
356 crate::ast::StrPart::Parsed(inner) => {
357 ResolvedStrPart::Parsed(Box::new(resolve_spanned(ctx, inner)))
358 }
359 })
360 .collect(),
361 ),
362 Expr::List(items) => {
363 ResolvedExpr::List(items.iter().map(|i| resolve_spanned(ctx, i)).collect())
364 }
365 Expr::Tuple(items) => {
366 ResolvedExpr::Tuple(items.iter().map(|i| resolve_spanned(ctx, i)).collect())
367 }
368 Expr::MapLiteral(pairs) => ResolvedExpr::MapLiteral(
369 pairs
370 .iter()
371 .map(|(k, v)| (resolve_spanned(ctx, k), resolve_spanned(ctx, v)))
372 .collect(),
373 ),
374 Expr::RecordCreate { type_name, fields } => ResolvedExpr::RecordCreate {
375 type_id: ctx.resolve_type_id(type_name),
376 type_name: type_name.clone(),
377 fields: fields
378 .iter()
379 .map(|(n, e)| (n.clone(), resolve_spanned(ctx, e)))
380 .collect(),
381 },
382 Expr::RecordUpdate {
383 type_name,
384 base,
385 updates,
386 } => ResolvedExpr::RecordUpdate {
387 type_id: ctx.resolve_type_id(type_name),
388 type_name: type_name.clone(),
389 base: Box::new(resolve_spanned(ctx, base)),
390 updates: updates
391 .iter()
392 .map(|(n, e)| (n.clone(), resolve_spanned(ctx, e)))
393 .collect(),
394 },
395 Expr::TailCall(data) => {
396 let target = ctx.resolve_fn_id(&data.target);
397 let args = data.args.iter().map(|a| resolve_spanned(ctx, a)).collect();
398 match target {
399 Some(fn_id) => ResolvedExpr::TailCall {
400 target: fn_id,
401 args,
402 },
403 None => ResolvedExpr::Call(
410 ResolvedCallee::Unresolved {
411 callee: Box::new(Spanned::new(
412 ResolvedExpr::Ident(data.target.clone()),
413 expr.line,
414 )),
415 },
416 args,
417 ),
418 }
419 }
420 Expr::IndependentProduct(items, unwrap) => ResolvedExpr::IndependentProduct(
421 items.iter().map(|i| resolve_spanned(ctx, i)).collect(),
422 *unwrap,
423 ),
424 }
425}
426
427fn resolve_match_arm(ctx: &ResolveCtx<'_>, arm: &MatchArm) -> ResolvedMatchArm {
428 let binding_slots = std::sync::OnceLock::new();
429 if let Some(slots) = arm.binding_slots.get() {
430 let _ = binding_slots.set(slots.clone());
431 }
432 ResolvedMatchArm {
433 pattern: resolve_pattern(ctx, &arm.pattern),
434 body: Box::new(resolve_spanned(ctx, &arm.body)),
435 binding_slots,
436 }
437}
438
439fn resolve_pattern(ctx: &ResolveCtx<'_>, pat: &Pattern) -> ResolvedPattern {
440 match pat {
441 Pattern::Wildcard => ResolvedPattern::Wildcard,
442 Pattern::Literal(l) => ResolvedPattern::Literal(l.clone()),
443 Pattern::Ident(name) => ResolvedPattern::Ident(name.clone()),
444 Pattern::EmptyList => ResolvedPattern::EmptyList,
445 Pattern::Cons(head, tail) => ResolvedPattern::Cons(head.clone(), tail.clone()),
446 Pattern::Tuple(items) => {
447 ResolvedPattern::Tuple(items.iter().map(|p| resolve_pattern(ctx, p)).collect())
448 }
449 Pattern::Constructor(name, bindings) => {
450 ResolvedPattern::Ctor(classify_ctor(ctx, name), bindings.clone())
451 }
452 }
453}
454
455fn classify_callee(ctx: &ResolveCtx<'_>, callee: &Spanned<Expr>) -> ResolvedCallee {
464 match &callee.node {
465 Expr::Resolved {
466 slot,
467 name,
468 last_use,
469 } => ResolvedCallee::LocalSlot {
470 slot: *slot,
471 name: name.clone(),
472 last_use: *last_use,
473 },
474 Expr::Ident(name) => {
475 if let Some(intrinsic) = BuiltinIntrinsic::from_name(name) {
476 return ResolvedCallee::Intrinsic(intrinsic);
477 }
478 if let Some((head, _)) = name.split_once('.')
487 && is_builtin_namespace(head)
488 {
489 return ResolvedCallee::Builtin(name.clone());
490 }
491 match ctx.resolve_fn_id(name) {
492 Some(id) => ResolvedCallee::Fn(id),
493 None => ResolvedCallee::Unresolved {
494 callee: Box::new(resolve_spanned(ctx, callee)),
495 },
496 }
497 }
498 Expr::Attr(_, _) => {
499 let Some(dotted) = expr_to_dotted_name(&callee.node) else {
500 return ResolvedCallee::Unresolved {
501 callee: Box::new(resolve_spanned(ctx, callee)),
502 };
503 };
504 if let Some(id) = ctx.resolve_fn_id(&dotted) {
507 return ResolvedCallee::Fn(id);
508 }
509 if let Some((head, _rest)) = dotted.split_once('.')
512 && is_builtin_namespace(head)
513 {
514 return ResolvedCallee::Builtin(dotted);
515 }
516 ResolvedCallee::Unresolved {
517 callee: Box::new(resolve_spanned(ctx, callee)),
518 }
519 }
520 _ => ResolvedCallee::Unresolved {
521 callee: Box::new(resolve_spanned(ctx, callee)),
522 },
523 }
524}
525
526fn classify_ctor(ctx: &ResolveCtx<'_>, name: &str) -> ResolvedCtor {
532 if let Some(builtin) = parse_builtin_ctor(name) {
533 return ResolvedCtor::Builtin(builtin);
534 }
535 if let Some((ctor_id, type_id, variant_name)) = ctx.resolve_user_ctor(name) {
536 return ResolvedCtor::User {
537 ctor_id,
538 type_id,
539 name: variant_name,
540 };
541 }
542 ResolvedCtor::Unresolved {
543 name: name.to_string(),
544 }
545}
546
547fn parse_builtin_ctor(name: &str) -> Option<BuiltinCtor> {
548 match name {
549 "Result.Ok" => Some(BuiltinCtor::ResultOk),
550 "Result.Err" => Some(BuiltinCtor::ResultErr),
551 "Option.Some" => Some(BuiltinCtor::OptionSome),
552 "Option.None" | "None" => Some(BuiltinCtor::OptionNone),
556 _ => None,
557 }
558}
559
560fn canonicalise_type(ctx: &ResolveCtx<'_>, ty: crate::ast::Type) -> crate::ast::Type {
566 use crate::ast::Type;
567 match ty {
568 Type::Named {
571 id: Some(existing),
572 name,
573 } => Type::Named {
574 id: Some(existing),
575 name,
576 },
577 Type::Named { id: None, name } => match ctx.resolve_type_id(&name) {
578 Some(id) => Type::Named { id: Some(id), name },
579 None => Type::Named { id: None, name },
580 },
581 Type::List(inner) => Type::List(Box::new(canonicalise_type(ctx, *inner))),
582 Type::Vector(inner) => Type::Vector(Box::new(canonicalise_type(ctx, *inner))),
583 Type::Option(inner) => Type::Option(Box::new(canonicalise_type(ctx, *inner))),
584 Type::Result(ok, err) => Type::Result(
585 Box::new(canonicalise_type(ctx, *ok)),
586 Box::new(canonicalise_type(ctx, *err)),
587 ),
588 Type::Map(k, v) => Type::Map(
589 Box::new(canonicalise_type(ctx, *k)),
590 Box::new(canonicalise_type(ctx, *v)),
591 ),
592 Type::Tuple(items) => Type::Tuple(
593 items
594 .into_iter()
595 .map(|t| canonicalise_type(ctx, t))
596 .collect(),
597 ),
598 Type::Fn(params, ret, effects) => Type::Fn(
599 params
600 .into_iter()
601 .map(|t| canonicalise_type(ctx, t))
602 .collect(),
603 Box::new(canonicalise_type(ctx, *ret)),
604 effects,
605 ),
606 other => other,
607 }
608}
609
610#[cfg(test)]
611mod tests {
612 use super::*;
613 use crate::ast::{BinOp, Literal};
614 use crate::source::parse_source;
615 use crate::tco;
616
617 fn build(src: &str) -> (SymbolTable, Vec<TopLevel>) {
618 let mut items = parse_source(src).expect("parse failed");
619 tco::transform_program(&mut items);
620 let symbols = SymbolTable::build(&items, &[]);
621 (symbols, items)
622 }
623
624 fn first_fn(resolved: &[ResolvedTopLevel]) -> &ResolvedFnDef {
625 resolved
626 .iter()
627 .find_map(|t| match t {
628 ResolvedTopLevel::FnDef(f) => Some(f),
629 _ => None,
630 })
631 .expect("expected at least one resolved fn def")
632 }
633
634 #[test]
635 fn resolves_user_fn_call_to_fn_id() {
636 let (symbols, items) = build(
637 r#"
638fn main() -> Int
639 helper(1)
640
641fn helper(n: Int) -> Int
642 n + 1
643"#,
644 );
645 let resolved = resolve_program(&symbols, &items);
646 let main_fn = resolved
647 .iter()
648 .find_map(|t| match t {
649 ResolvedTopLevel::FnDef(f) if f.name == "main" => Some(f),
650 _ => None,
651 })
652 .expect("main");
653 let stmts = main_fn.body.stmts();
654 let ResolvedStmt::Expr(call) = &stmts[0] else {
655 panic!("expected tail Expr stmt, got {:?}", stmts[0]);
656 };
657 match &call.node {
658 ResolvedExpr::Call(ResolvedCallee::Fn(_), args) => {
659 assert_eq!(args.len(), 1);
660 }
661 other => panic!("expected Call(Fn, _), got {:?}", other),
662 }
663 }
664
665 #[test]
666 fn resolves_builtin_namespace_method_call() {
667 let (symbols, items) = build(
668 r#"
669fn add() -> Int
670 Int.abs(-3)
671"#,
672 );
673 let resolved = resolve_program(&symbols, &items);
674 let f = first_fn(&resolved);
675 let ResolvedStmt::Expr(call) = &f.body.stmts()[0] else {
676 panic!("expected tail Expr");
677 };
678 match &call.node {
679 ResolvedExpr::Call(ResolvedCallee::Builtin(name), _) => {
680 assert_eq!(name, "Int.abs");
681 }
682 other => panic!("expected Call(Builtin, _), got {:?}", other),
683 }
684 }
685
686 #[test]
687 fn resolves_user_ctor_to_ctor_id_and_owning_type() {
688 let (symbols, items) = build(
689 r#"
690type Shape
691 Circle(Float)
692 Square(Float)
693
694fn make() -> Shape
695 Shape.Circle(1.0)
696"#,
697 );
698 let resolved = resolve_program(&symbols, &items);
699 let f = first_fn(&resolved);
700 let ResolvedStmt::Expr(call) = &f.body.stmts()[0] else {
701 panic!("expected tail Expr");
702 };
703 match &call.node {
704 ResolvedExpr::Ctor(ResolvedCtor::User { type_id, name, .. }, args) => {
705 assert_eq!(name, "Circle");
706 assert!(*type_id != crate::ir::identity::TypeId(u32::MAX));
707 assert_eq!(args.len(), 1);
708 }
709 other => panic!("expected Ctor(User, _), got {:?}", other),
710 }
711 }
712
713 #[test]
714 fn resolves_builtin_ctor_result_ok() {
715 let (symbols, items) = build(
716 r#"
717fn make() -> Result<Int, String>
718 Result.Ok(42)
719"#,
720 );
721 let resolved = resolve_program(&symbols, &items);
722 let f = first_fn(&resolved);
723 let ResolvedStmt::Expr(expr) = &f.body.stmts()[0] else {
724 panic!("expected tail Expr");
725 };
726 match &expr.node {
727 ResolvedExpr::Ctor(ResolvedCtor::Builtin(BuiltinCtor::ResultOk), args) => {
728 assert_eq!(args.len(), 1);
729 }
730 other => panic!("expected Builtin(ResultOk), got {:?}", other),
731 }
732 }
733
734 #[test]
735 fn resolves_record_create_to_type_id() {
736 let (symbols, items) = build(
737 r#"
738record Point
739 x: Int
740 y: Int
741
742fn origin() -> Point
743 Point(x = 0, y = 0)
744"#,
745 );
746 let resolved = resolve_program(&symbols, &items);
747 let f = first_fn(&resolved);
748 let ResolvedStmt::Expr(expr) = &f.body.stmts()[0] else {
749 panic!("expected tail Expr");
750 };
751 match &expr.node {
752 ResolvedExpr::RecordCreate {
753 type_id, type_name, ..
754 } => {
755 assert!(type_id.is_some(), "Point should resolve to a TypeId");
756 assert_eq!(type_name, "Point");
757 }
758 other => panic!("expected RecordCreate, got {:?}", other),
759 }
760 }
761
762 #[test]
763 fn resolves_tail_call_target_to_fn_id() {
764 let (symbols, items) = build(
765 r#"
766fn count(n: Int, acc: Int) -> Int
767 match n
768 0 -> acc
769 _ -> count(n - 1, acc + 1)
770"#,
771 );
772 let resolved = resolve_program(&symbols, &items);
773 let f = first_fn(&resolved);
774 let ResolvedStmt::Expr(top) = &f.body.stmts()[0] else {
776 panic!("expected tail Expr");
777 };
778 let ResolvedExpr::Match { arms, .. } = &top.node else {
779 panic!("expected match");
780 };
781 let recursive_body = &arms[1].body.node;
783 match recursive_body {
784 ResolvedExpr::TailCall { target, args } => {
785 assert_eq!(args.len(), 2);
786 let _ = target;
789 }
790 other => panic!("expected TailCall, got {:?}", other),
791 }
792 }
793
794 #[test]
795 fn resolves_binding_annotation_to_canonicalised_type() {
796 let (symbols, items) = build(
797 r#"
798fn pair() -> Int
799 x: Int = 5
800 x
801"#,
802 );
803 let resolved = resolve_program(&symbols, &items);
804 let f = first_fn(&resolved);
805 let stmts = f.body.stmts();
806 let ResolvedStmt::Binding { name, ty_ann, .. } = &stmts[0] else {
807 panic!("expected Binding stmt, got {:?}", stmts[0]);
808 };
809 assert_eq!(name, "x");
810 assert_eq!(ty_ann.as_ref(), Some(&crate::ast::Type::Int));
811 }
812
813 #[test]
814 fn binop_passes_through_structurally() {
815 let (symbols, items) = build(
816 r#"
817fn add() -> Int
818 1 + 2
819"#,
820 );
821 let resolved = resolve_program(&symbols, &items);
822 let f = first_fn(&resolved);
823 let ResolvedStmt::Expr(expr) = &f.body.stmts()[0] else {
824 panic!("tail expected");
825 };
826 match &expr.node {
827 ResolvedExpr::BinOp(BinOp::Add, l, r) => {
828 assert!(matches!(l.node, ResolvedExpr::Literal(Literal::Int(1))));
829 assert!(matches!(r.node, ResolvedExpr::Literal(Literal::Int(2))));
830 }
831 other => panic!("expected BinOp(Add, _, _), got {:?}", other),
832 }
833 }
834
835 #[test]
836 fn passthrough_for_verify_decision_typedef() {
837 let (symbols, items) = build(
838 r#"
839type Tag
840 On
841 Off
842
843verify alwaysTrue
844 1 => 1
845
846fn alwaysTrue() -> Int
847 1
848"#,
849 );
850 let resolved = resolve_program(&symbols, &items);
851 let mut saw_passthrough = false;
853 let mut saw_fn = false;
854 for item in &resolved {
855 match item {
856 ResolvedTopLevel::Passthrough(_) => saw_passthrough = true,
857 ResolvedTopLevel::FnDef(_) => saw_fn = true,
858 _ => {}
859 }
860 }
861 assert!(saw_passthrough, "expected at least one passthrough item");
862 assert!(saw_fn, "expected the FnDef to be promoted");
863 }
864
865 #[test]
866 fn ty_stamps_preserved_on_resolved_spans() {
867 use crate::ast::{Expr as AstExpr, Spanned as AstSpanned};
872 let lit = AstSpanned::new(AstExpr::Literal(Literal::Int(7)), 1);
873 let _ = lit.ty.set(crate::ast::Type::Int);
874 let symbols = SymbolTable::default();
877 let ctx = ResolveCtx::new(&symbols);
878 let resolved = resolve_spanned(&ctx, &lit);
879 assert_eq!(resolved.ty.get(), Some(&crate::ast::Type::Int));
880 }
881}