1use std::collections::HashMap;
13use std::sync::Arc as Rc;
14
15use crate::ast::*;
16
17struct TypeInfo {
22 variants: HashMap<(String, String), Vec<String>>,
31 variant_parents: HashMap<String, Vec<String>>,
37 #[allow(dead_code)]
42 records: HashMap<String, Vec<(String, String)>>,
43}
44
45fn build_type_info(items: &[TopLevel]) -> TypeInfo {
46 let mut variants: HashMap<(String, String), Vec<String>> = HashMap::new();
47 let mut variant_parents: HashMap<String, Vec<String>> = HashMap::new();
48 let mut records: HashMap<String, Vec<(String, String)>> = HashMap::new();
49 for item in items {
50 match item {
51 TopLevel::TypeDef(TypeDef::Sum {
52 name: parent,
53 variants: vs,
54 ..
55 }) => {
56 for v in vs {
57 variants.insert((parent.clone(), v.name.clone()), v.fields.clone());
58 variant_parents
59 .entry(v.name.clone())
60 .or_default()
61 .push(parent.clone());
62 }
63 }
64 TopLevel::TypeDef(TypeDef::Product { name, fields, .. }) => {
65 records.insert(name.clone(), fields.clone());
66 }
67 _ => {}
68 }
69 }
70 TypeInfo {
71 variants,
72 variant_parents,
73 records,
74 }
75}
76
77pub fn resolve_program(items: &mut [TopLevel]) {
82 let type_info = build_type_info(items);
83 for item in items.iter_mut() {
84 if let TopLevel::FnDef(fd) = item {
85 resolve_fn(fd, &type_info);
86 }
87 }
88}
89
90fn resolve_fn(fd: &mut FnDef, type_info: &TypeInfo) {
101 let mut state = ResolverState::new(type_info);
102
103 state.scopes.push(HashMap::new());
105 for (param_name, ty_str) in &fd.params {
106 let ty = crate::types::parse_type_str_strict(ty_str).unwrap_or(Type::Invalid);
107 let slot = state.declare(param_name, ty);
108 state.last_alloc.insert(param_name.clone(), slot);
109 }
110
111 let mut body = fd.body.as_ref().clone();
115 state.walk_stmts(body.stmts_mut());
116 fd.body = Rc::new(body);
117
118 let next_slot = state.next_slot;
119 let last_alloc = state.last_alloc;
120 let slot_types = state.slot_types;
121 state.scopes.pop();
122
123 fd.resolution = Some(FnResolution {
124 local_count: next_slot,
125 local_slots: Rc::new(last_alloc),
126 local_slot_types: Rc::new(slot_types),
127 aliased_slots: Rc::new(vec![false; next_slot as usize]),
128 });
129}
130
131struct ResolverState<'a> {
135 type_info: &'a TypeInfo,
136 next_slot: u16,
137 slot_types: Vec<Type>,
138 scopes: Vec<HashMap<String, u16>>,
143 last_alloc: HashMap<String, u16>,
150}
151
152impl<'a> ResolverState<'a> {
153 fn new(type_info: &'a TypeInfo) -> Self {
154 Self {
155 type_info,
156 next_slot: 0,
157 slot_types: Vec::new(),
158 scopes: Vec::new(),
159 last_alloc: HashMap::new(),
160 }
161 }
162
163 fn alloc(&mut self, ty: Type) -> u16 {
165 let idx = self.next_slot;
166 self.next_slot += 1;
167 self.slot_types.push(ty);
168 idx
169 }
170
171 fn declare(&mut self, name: &str, ty: Type) -> u16 {
185 if name == "_" {
186 return u16::MAX;
187 }
188 let slot = self.alloc(ty);
189 if let Some(scope) = self.scopes.last_mut() {
190 scope.insert(name.to_string(), slot);
191 }
192 self.last_alloc.insert(name.to_string(), slot);
193 slot
194 }
195
196 fn lookup(&self, name: &str) -> Option<u16> {
200 for scope in self.scopes.iter().rev() {
201 if let Some(&s) = scope.get(name) {
202 return Some(s);
203 }
204 }
205 None
206 }
207
208 fn walk_stmts(&mut self, stmts: &mut [Stmt]) {
209 for stmt in stmts {
210 match stmt {
211 Stmt::Binding(name, _annot, expr) => {
212 let ty = expr.ty().cloned().unwrap_or(Type::Invalid);
219 self.declare(name, ty);
220 self.walk_expr(expr);
221 }
222 Stmt::Expr(expr) => self.walk_expr(expr),
223 }
224 }
225 }
226
227 fn walk_expr(&mut self, expr: &mut Spanned<Expr>) {
228 match &mut expr.node {
229 Expr::Ident(name) => {
230 if let Some(slot) = self.lookup(name) {
231 expr.node = Expr::Resolved {
232 slot,
233 name: name.clone(),
234 last_use: AnnotBool(false),
235 };
236 }
237 }
238 Expr::Match { subject, arms } => {
239 self.walk_expr(subject);
240 let subject_ty = subject.ty().cloned();
241 for arm in arms.iter_mut() {
242 self.scopes.push(HashMap::new());
243 let slots = self.allocate_pattern(&arm.pattern, subject_ty.as_ref());
244 let _ = arm.binding_slots.set(slots);
245 self.walk_expr(&mut arm.body);
246 self.scopes.pop();
247 }
248 }
249 Expr::FnCall(func, args) => {
250 self.walk_expr(func);
251 for arg in args {
252 self.walk_expr(arg);
253 }
254 }
255 Expr::BinOp(_, l, r) => {
256 self.walk_expr(l);
257 self.walk_expr(r);
258 }
259 Expr::Neg(inner) => self.walk_expr(inner),
260 Expr::Attr(obj, _) => self.walk_expr(obj),
261 Expr::ErrorProp(inner) => self.walk_expr(inner),
262 Expr::Constructor(_, Some(inner)) => self.walk_expr(inner),
263 Expr::Constructor(_, None) => {}
264 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
265 for it in items {
266 self.walk_expr(it);
267 }
268 }
269 Expr::MapLiteral(entries) => {
270 for (k, v) in entries {
271 self.walk_expr(k);
272 self.walk_expr(v);
273 }
274 }
275 Expr::InterpolatedStr(parts) => {
276 for part in parts {
277 if let StrPart::Parsed(e) = part {
278 self.walk_expr(e);
279 }
280 }
281 }
282 Expr::RecordCreate { fields, .. } => {
283 for (_, e) in fields {
284 self.walk_expr(e);
285 }
286 }
287 Expr::RecordUpdate { base, updates, .. } => {
288 self.walk_expr(base);
289 for (_, e) in updates {
290 self.walk_expr(e);
291 }
292 }
293 Expr::TailCall(boxed) => {
294 for a in &mut boxed.args {
295 self.walk_expr(a);
296 }
297 }
298 Expr::Literal(_) | Expr::Resolved { .. } => {}
299 }
300 }
301
302 fn allocate_pattern(&mut self, pattern: &Pattern, subject_ty: Option<&Type>) -> Vec<u16> {
308 match pattern {
309 Pattern::Ident(name) => {
310 let ty = subject_ty.cloned().unwrap_or(Type::Invalid);
311 vec![self.declare(name, ty)]
312 }
313 Pattern::Cons(head, tail) => {
314 let elem_ty = match subject_ty {
315 Some(Type::List(inner)) => (**inner).clone(),
316 _ => Type::Invalid,
317 };
318 let list_ty = Type::List(Box::new(elem_ty.clone()));
319 vec![self.declare(head, elem_ty), self.declare(tail, list_ty)]
320 }
321 Pattern::Constructor(name, bindings) => {
322 let bare = name.rsplit('.').next().unwrap_or(name);
323 let parent_hint: Option<String> = match (subject_ty, name.split_once('.')) {
324 (Some(Type::Named(parent)), _) => Some(parent.clone()),
325 (_, Some((parent, _))) => Some(parent.to_string()),
326 _ => self
327 .type_info
328 .variant_parents
329 .get(bare)
330 .and_then(|parents| {
331 if parents.len() == 1 {
332 Some(parents[0].clone())
333 } else {
334 None
335 }
336 }),
337 };
338 let field_tys: Vec<Type> = match (bare, subject_ty) {
339 ("Ok", Some(Type::Result(t, _))) => vec![(**t).clone()],
340 ("Err", Some(Type::Result(_, e))) => vec![(**e).clone()],
341 ("Some", Some(Type::Option(inner))) => vec![(**inner).clone()],
342 ("None", _) => Vec::new(),
343 _ => parent_hint
344 .and_then(|p| self.type_info.variants.get(&(p, bare.to_string())))
345 .map(|fields| {
346 fields
347 .iter()
348 .map(|s| {
349 crate::types::parse_type_str_strict(s).unwrap_or(Type::Invalid)
350 })
351 .collect()
352 })
353 .unwrap_or_else(|| vec![Type::Invalid; bindings.len()]),
354 };
355 bindings
356 .iter()
357 .enumerate()
358 .map(|(i, name)| {
359 let ty = field_tys.get(i).cloned().unwrap_or(Type::Invalid);
360 self.declare(name, ty)
361 })
362 .collect()
363 }
364 Pattern::Tuple(items) => {
365 let elem_tys: Vec<Type> = match subject_ty {
366 Some(Type::Tuple(elems)) if elems.len() == items.len() => elems.to_vec(),
367 _ => vec![Type::Invalid; items.len()],
368 };
369 let mut slots = Vec::new();
370 for (item, elem_ty) in items.iter().zip(elem_tys.iter()) {
371 slots.extend(self.allocate_pattern(item, Some(elem_ty)));
372 }
373 slots
374 }
375 Pattern::Wildcard | Pattern::Literal(_) | Pattern::EmptyList => Vec::new(),
376 }
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383
384 #[test]
385 fn resolves_param_to_slot() {
386 let mut fd = FnDef {
387 name: "add".to_string(),
388 line: 1,
389 params: vec![
390 ("a".to_string(), "Int".to_string()),
391 ("b".to_string(), "Int".to_string()),
392 ],
393 return_type: "Int".to_string(),
394 effects: vec![],
395 desc: None,
396 body: Rc::new(FnBody::from_expr(Spanned::bare(Expr::BinOp(
397 BinOp::Add,
398 Box::new(Spanned::bare(Expr::Ident("a".to_string()))),
399 Box::new(Spanned::bare(Expr::Ident("b".to_string()))),
400 )))),
401 resolution: None,
402 };
403 resolve_fn(
404 &mut fd,
405 &TypeInfo {
406 variants: HashMap::new(),
407 variant_parents: HashMap::new(),
408 records: HashMap::new(),
409 },
410 );
411 let res = fd.resolution.as_ref().unwrap();
412 assert_eq!(res.local_slots["a"], 0);
413 assert_eq!(res.local_slots["b"], 1);
414 assert_eq!(res.local_count, 2);
415
416 match fd.body.tail_expr() {
417 Some(Spanned {
418 node: Expr::BinOp(_, left, right),
419 ..
420 }) => {
421 assert_eq!(
422 left.node,
423 Expr::Resolved {
424 slot: 0,
425 name: "a".to_string(),
426 last_use: AnnotBool(false)
427 }
428 );
429 assert_eq!(
430 right.node,
431 Expr::Resolved {
432 slot: 1,
433 name: "b".to_string(),
434 last_use: AnnotBool(false)
435 }
436 );
437 }
438 other => panic!("unexpected body: {:?}", other),
439 }
440 }
441
442 #[test]
443 fn leaves_globals_as_ident() {
444 let mut fd = FnDef {
445 name: "f".to_string(),
446 line: 1,
447 params: vec![("x".to_string(), "Int".to_string())],
448 return_type: "Int".to_string(),
449 effects: vec![],
450 desc: None,
451 body: Rc::new(FnBody::from_expr(Spanned::bare(Expr::FnCall(
452 Box::new(Spanned::bare(Expr::Ident("Console".to_string()))),
453 vec![Spanned::bare(Expr::Ident("x".to_string()))],
454 )))),
455 resolution: None,
456 };
457 resolve_fn(
458 &mut fd,
459 &TypeInfo {
460 variants: HashMap::new(),
461 variant_parents: HashMap::new(),
462 records: HashMap::new(),
463 },
464 );
465 match fd.body.tail_expr() {
466 Some(Spanned {
467 node: Expr::FnCall(func, args),
468 ..
469 }) => {
470 assert_eq!(func.node, Expr::Ident("Console".to_string()));
471 assert_eq!(
472 args[0].node,
473 Expr::Resolved {
474 slot: 0,
475 name: "x".to_string(),
476 last_use: AnnotBool(false)
477 }
478 );
479 }
480 other => panic!("unexpected body: {:?}", other),
481 }
482 }
483
484 #[test]
485 fn resolves_val_in_block_body() {
486 let mut fd = FnDef {
487 name: "f".to_string(),
488 line: 1,
489 params: vec![("x".to_string(), "Int".to_string())],
490 return_type: "Int".to_string(),
491 effects: vec![],
492 desc: None,
493 body: Rc::new(FnBody::Block(vec![
494 Stmt::Binding(
495 "y".to_string(),
496 None,
497 Spanned::bare(Expr::BinOp(
498 BinOp::Add,
499 Box::new(Spanned::bare(Expr::Ident("x".to_string()))),
500 Box::new(Spanned::bare(Expr::Literal(Literal::Int(1)))),
501 )),
502 ),
503 Stmt::Expr(Spanned::bare(Expr::Ident("y".to_string()))),
504 ])),
505 resolution: None,
506 };
507 resolve_fn(
508 &mut fd,
509 &TypeInfo {
510 variants: HashMap::new(),
511 variant_parents: HashMap::new(),
512 records: HashMap::new(),
513 },
514 );
515 let res = fd.resolution.as_ref().unwrap();
516 assert_eq!(res.local_slots["x"], 0);
517 assert_eq!(res.local_slots["y"], 1);
518 assert_eq!(res.local_count, 2);
519
520 let stmts = fd.body.stmts();
521 match &stmts[0] {
523 Stmt::Binding(
524 _,
525 _,
526 Spanned {
527 node: Expr::BinOp(_, left, _),
528 ..
529 },
530 ) => {
531 assert_eq!(
532 left.node,
533 Expr::Resolved {
534 slot: 0,
535 name: "x".to_string(),
536 last_use: AnnotBool(false)
537 }
538 );
539 }
540 other => panic!("unexpected stmt: {:?}", other),
541 }
542 match &stmts[1] {
544 Stmt::Expr(Spanned {
545 node: Expr::Resolved { slot: 1, .. },
546 ..
547 }) => {}
548 other => panic!("unexpected stmt: {:?}", other),
549 }
550 }
551
552 #[test]
553 fn resolves_match_pattern_bindings() {
554 let mut fd = FnDef {
556 name: "f".to_string(),
557 line: 1,
558 params: vec![("x".to_string(), "Int".to_string())],
559 return_type: "Int".to_string(),
560 effects: vec![],
561 desc: None,
562 body: Rc::new(FnBody::from_expr(Spanned::new(
563 Expr::Match {
564 subject: Box::new(Spanned::bare(Expr::Ident("x".to_string()))),
565 arms: vec![
566 MatchArm {
567 pattern: Pattern::Constructor(
568 "Result.Ok".to_string(),
569 vec!["v".to_string()],
570 ),
571 body: Box::new(Spanned::bare(Expr::Ident("v".to_string()))),
572 binding_slots: std::sync::OnceLock::new(),
573 },
574 MatchArm {
575 pattern: Pattern::Wildcard,
576 body: Box::new(Spanned::bare(Expr::Literal(Literal::Int(0)))),
577 binding_slots: std::sync::OnceLock::new(),
578 },
579 ],
580 },
581 1,
582 ))),
583 resolution: None,
584 };
585 resolve_fn(
586 &mut fd,
587 &TypeInfo {
588 variants: HashMap::new(),
589 variant_parents: HashMap::new(),
590 records: HashMap::new(),
591 },
592 );
593 let res = fd.resolution.as_ref().unwrap();
594 assert_eq!(res.local_slots["v"], 1);
596
597 match fd.body.tail_expr() {
598 Some(Spanned {
599 node: Expr::Match { arms, .. },
600 ..
601 }) => {
602 assert_eq!(
603 arms[0].body.node,
604 Expr::Resolved {
605 slot: 1,
606 name: "v".to_string(),
607 last_use: AnnotBool(false)
608 }
609 );
610 }
611 other => panic!("unexpected body: {:?}", other),
612 }
613 }
614
615 #[test]
616 fn resolves_match_pattern_bindings_inside_binding_initializer() {
617 let mut fd = FnDef {
618 name: "f".to_string(),
619 line: 1,
620 params: vec![("x".to_string(), "Int".to_string())],
621 return_type: "Int".to_string(),
622 effects: vec![],
623 desc: None,
624 body: Rc::new(FnBody::Block(vec![
625 Stmt::Binding(
626 "result".to_string(),
627 None,
628 Spanned::bare(Expr::Match {
629 subject: Box::new(Spanned::bare(Expr::Ident("x".to_string()))),
630 arms: vec![
631 MatchArm {
632 pattern: Pattern::Constructor(
633 "Option.Some".to_string(),
634 vec!["v".to_string()],
635 ),
636 body: Box::new(Spanned::bare(Expr::Ident("v".to_string()))),
637 binding_slots: std::sync::OnceLock::new(),
638 },
639 MatchArm {
640 pattern: Pattern::Wildcard,
641 body: Box::new(Spanned::bare(Expr::Literal(Literal::Int(0)))),
642 binding_slots: std::sync::OnceLock::new(),
643 },
644 ],
645 }),
646 ),
647 Stmt::Expr(Spanned::bare(Expr::Ident("result".to_string()))),
648 ])),
649 resolution: None,
650 };
651
652 resolve_fn(
653 &mut fd,
654 &TypeInfo {
655 variants: HashMap::new(),
656 variant_parents: HashMap::new(),
657 records: HashMap::new(),
658 },
659 );
660 let res = fd.resolution.as_ref().unwrap();
661 assert_eq!(res.local_slots["x"], 0);
662 assert_eq!(res.local_slots["result"], 1);
663 assert_eq!(res.local_slots["v"], 2);
664
665 let stmts = fd.body.stmts();
666 match &stmts[0] {
667 Stmt::Binding(
668 _,
669 _,
670 Spanned {
671 node: Expr::Match { arms, .. },
672 ..
673 },
674 ) => {
675 assert_eq!(
676 arms[0].body.node,
677 Expr::Resolved {
678 slot: 2,
679 name: "v".to_string(),
680 last_use: AnnotBool(false)
681 }
682 );
683 }
684 other => panic!("unexpected stmt: {:?}", other),
685 }
686
687 match &stmts[1] {
688 Stmt::Expr(Spanned {
689 node: Expr::Resolved { slot: 1, .. },
690 ..
691 }) => {}
692 other => panic!("unexpected stmt: {:?}", other),
693 }
694 }
695}