1use ecow::EcoString;
2use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
3use std::collections::BTreeMap;
4
5use syntax::ast::Visibility;
6use syntax::program::MethodSignatures;
7use syntax::types::{CompoundKind, Symbol, Type};
8
9use crate::store::Store;
10
11#[derive(Clone, Debug)]
12pub enum MemberKind {
13 Field {
14 ty: Type,
15 visibility: Visibility,
16 },
17 Method {
20 ty: Type,
21 },
22}
23
24#[derive(Clone, Debug)]
25pub struct ResolvedMember {
26 pub name: EcoString,
27 pub depth: usize,
28 pub embed_path: Vec<EcoString>,
29 pub declaring_type: Symbol,
30 pub indirect: bool,
31 pub kind: MemberKind,
32}
33
34#[derive(Clone, Debug)]
35pub enum Resolution {
36 Found(ResolvedMember),
37 Ambiguous { sources: Vec<Symbol> },
38 NotFound,
39}
40
41pub fn has_direct_embed(store: &Store, ty: &Type) -> bool {
42 let Type::Nominal { id, .. } = store.deep_resolve_alias(&ty.strip_refs()) else {
43 return false;
44 };
45 store
46 .fields_of(id.as_str())
47 .is_some_and(|fields| fields.iter().any(|f| f.embedded))
48}
49
50pub fn resolve_selector(store: &Store, outer: &Type, name: &str) -> Resolution {
51 let entries = walk(store, outer);
52 resolve_in_entries(store, &entries, outer, name)
53}
54
55pub fn promoted_method_set(store: &Store, outer: &Type) -> MethodSignatures {
56 let entries = walk(store, outer);
57
58 let mut names: HashSet<EcoString> = HashSet::default();
59 for entry in &entries {
60 collect_member_names(store, &entry.ty, &mut names);
61 }
62
63 let mut result = MethodSignatures::default();
64 for name in names {
65 if let Resolution::Found(member) = resolve_in_entries(store, &entries, outer, &name)
66 && let MemberKind::Method { ty } = member.kind
67 {
68 result.insert(name, ty);
69 }
70 }
71 result
72}
73
74#[derive(Clone)]
75struct Entry {
76 ty: Type,
77 depth: usize,
78 indirect: bool,
80 multiples: bool,
82 embed_path: Vec<EcoString>,
83}
84
85fn walk(store: &Store, outer: &Type) -> Vec<Entry> {
86 let mut visited: Vec<Entry> = Vec::new();
87 let mut seen: HashSet<String> = HashSet::default();
88
89 let Some(root) = nominal_entry(store, outer.clone(), 0, false, false, Vec::new()) else {
90 return visited;
91 };
92 let mut current = vec![root];
93 let mut depth = 0;
94
95 while !current.is_empty() {
96 let mut next: Vec<Entry> = Vec::new();
97
98 for entry in ¤t {
99 if !seen.insert(type_key(&entry.ty)) {
101 continue;
102 }
103 visited.push(entry.clone());
104
105 let Some(id) = entry.ty.get_qualified_id() else {
107 continue;
108 };
109 if store.get_interface(id).is_some() {
110 continue;
111 }
112 let Some(fields) = store.fields_of(id) else {
113 continue;
114 };
115 for field in fields {
116 if !field.embedded {
117 continue;
118 }
119 let resolved_field = store.deep_resolve_alias(&field.ty);
121 let (target, is_pointer) = deref_once(&resolved_field);
122 let mut path = entry.embed_path.clone();
123 path.push(field.name.clone());
124 if let Some(child) = nominal_entry(
125 store,
126 target,
127 depth + 1,
128 entry.indirect || is_pointer,
129 entry.multiples,
130 path,
131 ) {
132 next.push(child);
133 }
134 }
135 }
136
137 current = consolidate(next);
138 depth += 1;
139 }
140
141 visited
142}
143
144fn resolve_in_entries(store: &Store, entries: &[Entry], outer: &Type, name: &str) -> Resolution {
147 let mut by_depth: BTreeMap<usize, Vec<(&Entry, Candidate)>> = BTreeMap::new();
148 for entry in entries {
149 if let Some(candidate) = entry_candidate(store, &entry.ty, name) {
150 by_depth
151 .entry(entry.depth)
152 .or_default()
153 .push((entry, candidate));
154 }
155 }
156
157 let Some((_, candidates)) = by_depth.into_iter().next() else {
158 return Resolution::NotFound;
159 };
160
161 if let [(entry, candidate)] = candidates.as_slice()
162 && !entry.multiples
163 {
164 return Resolution::Found(build_member(outer, name, entry, candidate));
165 }
166
167 let mut sources: Vec<Symbol> = candidates
168 .iter()
169 .map(|(_, c)| c.declaring_type.clone())
170 .collect();
171 sources.sort();
172 sources.dedup();
173 Resolution::Ambiguous { sources }
174}
175
176struct Candidate {
177 declaring_type: Symbol,
178 detail: CandidateDetail,
179}
180
181enum CandidateDetail {
182 Field { ty: Type, visibility: Visibility },
183 Method { ty: Type },
184}
185
186fn entry_candidate(store: &Store, ty: &Type, name: &str) -> Option<Candidate> {
189 let id = ty.get_qualified_id()?;
190
191 if store.get_interface(id).is_some() {
192 let methods = store.get_all_methods(ty, &Default::default());
193 let method_ty = methods.get(name)?.clone();
194 return Some(Candidate {
195 declaring_type: Symbol::from_raw(id),
196 detail: CandidateDetail::Method { ty: method_ty },
197 });
198 }
199
200 if let Some(method_ty) = store.get_own_methods(id).and_then(|m| m.get(name)) {
201 return Some(Candidate {
202 declaring_type: Symbol::from_raw(id),
203 detail: CandidateDetail::Method {
204 ty: method_ty.clone(),
205 },
206 });
207 }
208
209 if let Some(field) = store
210 .fields_of(id)
211 .and_then(|fields| fields.iter().find(|f| f.name == name))
212 {
213 return Some(Candidate {
214 declaring_type: Symbol::from_raw(id),
215 detail: CandidateDetail::Field {
216 ty: field.ty.clone(),
217 visibility: field.visibility,
218 },
219 });
220 }
221
222 None
223}
224
225fn build_member(outer: &Type, name: &str, entry: &Entry, candidate: &Candidate) -> ResolvedMember {
226 let kind = match &candidate.detail {
227 CandidateDetail::Field { ty, visibility } => MemberKind::Field {
228 ty: ty.clone(),
229 visibility: *visibility,
230 },
231 CandidateDetail::Method { ty } => {
232 let method_ty = if entry.depth == 0 {
237 ty.clone()
238 } else if !entry.indirect && method_has_pointer_receiver(ty) {
239 ty.with_replaced_first_param(&ref_of(outer))
240 } else {
241 ty.with_replaced_first_param(outer)
242 };
243 MemberKind::Method { ty: method_ty }
244 }
245 };
246
247 ResolvedMember {
248 name: name.into(),
249 depth: entry.depth,
250 embed_path: entry.embed_path.clone(),
251 declaring_type: candidate.declaring_type.clone(),
252 indirect: entry.indirect,
253 kind,
254 }
255}
256
257fn collect_member_names(store: &Store, ty: &Type, names: &mut HashSet<EcoString>) {
259 let Some(id) = ty.get_qualified_id() else {
260 return;
261 };
262 if store.get_interface(id).is_some() {
263 for key in store.get_all_methods(ty, &Default::default()).keys() {
264 names.insert(key.clone());
265 }
266 return;
267 }
268 if let Some(methods) = store.get_own_methods(id) {
269 for key in methods.keys() {
270 names.insert(key.clone());
271 }
272 }
273 if let Some(fields) = store.fields_of(id) {
274 for field in fields {
275 names.insert(field.name.clone());
276 }
277 }
278}
279
280fn nominal_entry(
282 store: &Store,
283 target: Type,
284 depth: usize,
285 indirect: bool,
286 multiples: bool,
287 embed_path: Vec<EcoString>,
288) -> Option<Entry> {
289 let resolved = store.deep_resolve_alias(&target);
290 if !matches!(resolved, Type::Nominal { .. }) {
291 return None;
292 }
293 Some(Entry {
294 ty: resolved,
295 depth,
296 indirect,
297 multiples,
298 embed_path,
299 })
300}
301
302fn consolidate(list: Vec<Entry>) -> Vec<Entry> {
305 let mut result: Vec<Entry> = Vec::with_capacity(list.len());
306 let mut index_of: HashMap<String, usize> = HashMap::default();
307 for entry in list {
308 let key = type_key(&entry.ty);
309 if let Some(&i) = index_of.get(&key) {
310 result[i].multiples = true;
311 } else {
312 index_of.insert(key, result.len());
313 result.push(entry);
314 }
315 }
316 result
317}
318
319fn type_key(ty: &Type) -> String {
321 match ty {
322 Type::Nominal { id, params, .. } if params.is_empty() => id.as_str().to_string(),
323 Type::Nominal { id, params, .. } => {
324 let args: Vec<String> = params.iter().map(type_key).collect();
325 format!("{}<{}>", id, args.join(","))
326 }
327 other => other.to_string(),
328 }
329}
330
331fn deref_once(ty: &Type) -> (Type, bool) {
333 if ty.is_ref() {
334 (ty.inner().unwrap_or(Type::Error), true)
335 } else {
336 (ty.clone(), false)
337 }
338}
339
340fn method_has_pointer_receiver(method_ty: &Type) -> bool {
341 let body = match method_ty {
342 Type::Forall { body, .. } => body.as_ref(),
343 other => other,
344 };
345 matches!(body, Type::Function(f) if f.params.first().is_some_and(Type::is_ref))
346}
347
348fn ref_of(ty: &Type) -> Type {
349 Type::Compound {
350 kind: CompoundKind::Ref,
351 args: vec![ty.clone()],
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358 use syntax::ast::{Annotation, Span, StructFieldDefinition, StructKind};
359 use syntax::program::Visibility as ProgVis;
360 use syntax::program::{Attributes, Definition, DefinitionBody, Interface};
361
362 const MODULE: &str = "m";
363
364 fn nominal(name: &str) -> Type {
365 Type::Nominal {
366 id: Symbol::from_parts(MODULE, name),
367 params: vec![],
368 underlying_ty: None,
369 }
370 }
371
372 fn value_method(owner: &str) -> Type {
373 Type::function(
374 vec![nominal(owner)],
375 vec![false],
376 vec![],
377 Box::new(Type::string()),
378 )
379 }
380
381 fn pointer_method(owner: &str) -> Type {
382 Type::function(
383 vec![ref_of(&nominal(owner))],
384 vec![false],
385 vec![],
386 Box::new(Type::string()),
387 )
388 }
389
390 fn interface_method() -> Type {
392 Type::function(vec![], vec![], vec![], Box::new(Type::string()))
393 }
394
395 fn field(name: &str, ty: Type, embedded: bool) -> StructFieldDefinition {
396 StructFieldDefinition {
397 doc: None,
398 attributes: vec![],
399 name: name.into(),
400 name_span: Span::dummy(),
401 annotation: Annotation::Unknown,
402 visibility: Visibility::Public,
403 ty,
404 embedded,
405 }
406 }
407
408 struct Builder {
409 store: Store,
410 }
411
412 impl Builder {
413 fn new() -> Self {
414 let mut store = Store::new();
415 store.add_module(MODULE);
416 Builder { store }
417 }
418
419 fn insert(&mut self, name: &str, body: DefinitionBody) -> &mut Self {
420 let def = Definition {
421 visibility: ProgVis::Public,
422 ty: nominal(name),
423 name: Some(name.into()),
424 name_span: None,
425 doc: None,
426 body,
427 };
428 self.store
429 .get_module_mut(MODULE)
430 .unwrap()
431 .definitions
432 .insert(Symbol::from_parts(MODULE, name), def);
433 self
434 }
435
436 fn struct_(
437 &mut self,
438 name: &str,
439 fields: Vec<StructFieldDefinition>,
440 methods: Vec<(&str, Type)>,
441 ) -> &mut Self {
442 let mut method_map = MethodSignatures::default();
443 for (n, t) in methods {
444 method_map.insert(n.into(), t);
445 }
446 self.insert(
447 name,
448 DefinitionBody::Struct {
449 generics: vec![],
450 fields,
451 kind: StructKind::Record,
452 methods: method_map,
453 constructor: None,
454 attributes: Attributes::default(),
455 },
456 )
457 }
458
459 fn interface(&mut self, name: &str, methods: Vec<&str>, parents: Vec<&str>) -> &mut Self {
460 let mut method_map = MethodSignatures::default();
461 for n in methods {
462 method_map.insert(n.into(), interface_method());
463 }
464 self.insert(
465 name,
466 DefinitionBody::Interface {
467 definition: Interface {
468 name: name.into(),
469 generics: vec![],
470 parents: parents.into_iter().map(nominal).collect(),
471 methods: method_map,
472 },
473 },
474 )
475 }
476 }
477
478 fn vembed(target: &str) -> StructFieldDefinition {
479 field(target, nominal(target), true)
480 }
481
482 fn pembed(target: &str) -> StructFieldDefinition {
483 field(target, ref_of(&nominal(target)), true)
484 }
485
486 fn found(resolution: Resolution) -> ResolvedMember {
487 match resolution {
488 Resolution::Found(member) => member,
489 other => panic!("expected Found, got {other:?}"),
490 }
491 }
492
493 fn is_pointer_receiver(member: &ResolvedMember) -> bool {
494 match &member.kind {
495 MemberKind::Method { ty } => ty.get_function_params().unwrap()[0].is_ref(),
496 other => panic!("expected a method, got {other:?}"),
497 }
498 }
499
500 #[test]
501 fn direct_method_at_depth_zero() {
502 let mut b = Builder::new();
503 b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
504 let member = found(resolve_selector(&b.store, &nominal("N0"), "m"));
505 assert_eq!(member.depth, 0);
506 assert!(!is_pointer_receiver(&member));
507 }
508
509 #[test]
510 fn value_embed_promotes_value_method() {
511 let mut b = Builder::new();
512 b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
513 b.struct_("N1", vec![vembed("N0")], vec![]);
514 let member = found(resolve_selector(&b.store, &nominal("N1"), "m"));
515 assert_eq!(member.depth, 1);
516 assert_eq!(member.embed_path, vec![EcoString::from("N0")]);
517 assert!(!member.indirect);
518 assert!(!is_pointer_receiver(&member));
519 }
520
521 #[test]
522 fn value_embed_of_pointer_method_is_pointer_only() {
523 let mut b = Builder::new();
524 b.struct_("N0", vec![], vec![("pm", pointer_method("N0"))]);
525 b.struct_("N1", vec![vembed("N0")], vec![]);
526 let member = found(resolve_selector(&b.store, &nominal("N1"), "pm"));
527 assert!(!member.indirect);
528 assert!(is_pointer_receiver(&member));
529 }
530
531 #[test]
532 fn pointer_embed_puts_pointer_method_in_value_set() {
533 let mut b = Builder::new();
534 b.struct_("N0", vec![], vec![("pm", pointer_method("N0"))]);
535 b.struct_("N1", vec![pembed("N0")], vec![]);
536 let member = found(resolve_selector(&b.store, &nominal("N1"), "pm"));
537 assert!(member.indirect);
538 assert!(!is_pointer_receiver(&member));
539 }
540
541 #[test]
542 fn pointer_embed_of_value_method_is_value() {
543 let mut b = Builder::new();
544 b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
545 b.struct_("N1", vec![pembed("N0")], vec![]);
546 let member = found(resolve_selector(&b.store, &nominal("N1"), "m"));
547 assert!(member.indirect);
548 assert!(!is_pointer_receiver(&member));
549 }
550
551 #[test]
552 fn diamond_is_ambiguous() {
553 let mut b = Builder::new();
554 b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
555 b.struct_("N1", vec![vembed("N0")], vec![]);
556 b.struct_("N2", vec![vembed("N0")], vec![]);
557 b.struct_("N3", vec![vembed("N1"), vembed("N2")], vec![]);
558 assert!(matches!(
559 resolve_selector(&b.store, &nominal("N3"), "m"),
560 Resolution::Ambiguous { .. }
561 ));
562 }
563
564 #[test]
565 fn shallower_path_shadows_deeper_diamond() {
566 let mut b = Builder::new();
567 b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
568 b.struct_("N1", vec![vembed("N0")], vec![]);
569 b.struct_("N3", vec![vembed("N0"), vembed("N1")], vec![]);
570 let member = found(resolve_selector(&b.store, &nominal("N3"), "m"));
571 assert_eq!(member.depth, 1);
572 }
573
574 #[test]
575 fn own_member_shadows_promoted() {
576 let mut b = Builder::new();
577 b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
578 b.struct_("N1", vec![vembed("N0")], vec![("m", value_method("N1"))]);
579 let member = found(resolve_selector(&b.store, &nominal("N1"), "m"));
580 assert_eq!(member.depth, 0);
581 assert_eq!(member.declaring_type.as_str(), "m.N1");
582 }
583
584 #[test]
585 fn field_promotes() {
586 let mut b = Builder::new();
587 b.struct_("N0", vec![field("f", Type::int(), false)], vec![]);
588 b.struct_("N1", vec![vembed("N0")], vec![]);
589 let member = found(resolve_selector(&b.store, &nominal("N1"), "f"));
590 assert_eq!(member.depth, 1);
591 assert!(matches!(member.kind, MemberKind::Field { .. }));
592 }
593
594 #[test]
595 fn field_and_method_collide_across_embeds() {
596 let mut b = Builder::new();
597 b.struct_("A", vec![field("x", Type::int(), false)], vec![]);
598 b.struct_("B", vec![], vec![("x", value_method("B"))]);
599 b.struct_("N2", vec![vembed("A"), vembed("B")], vec![]);
600 assert!(matches!(
601 resolve_selector(&b.store, &nominal("N2"), "x"),
602 Resolution::Ambiguous { .. }
603 ));
604 }
605
606 #[test]
607 fn pointer_cycle_terminates_and_resolves() {
608 let mut b = Builder::new();
609 b.struct_("N0", vec![pembed("N1")], vec![("a", value_method("N0"))]);
610 b.struct_("N1", vec![vembed("N0")], vec![("bb", value_method("N1"))]);
611 assert_eq!(
612 found(resolve_selector(&b.store, &nominal("N0"), "a")).depth,
613 0
614 );
615 assert_eq!(
616 found(resolve_selector(&b.store, &nominal("N0"), "bb")).depth,
617 1
618 );
619 assert_eq!(
620 found(resolve_selector(&b.store, &nominal("N1"), "a")).depth,
621 1
622 );
623 assert!(matches!(
624 resolve_selector(&b.store, &nominal("N0"), "absent"),
625 Resolution::NotFound
626 ));
627 }
628
629 #[test]
630 fn embedded_interface_promotes_value_callable() {
631 let mut b = Builder::new();
632 b.interface("I", vec!["speak"], vec![]);
633 b.struct_("N2", vec![vembed("I")], vec![]);
634 let member = found(resolve_selector(&b.store, &nominal("N2"), "speak"));
635 assert_eq!(member.depth, 1);
636 assert!(!is_pointer_receiver(&member));
637 }
638
639 #[test]
640 fn struct_embedding_interface_and_struct_with_same_method_is_ambiguous() {
641 let mut b = Builder::new();
642 b.interface("I", vec!["speak"], vec![]);
643 b.struct_("S", vec![], vec![("speak", value_method("S"))]);
644 b.struct_("N2", vec![vembed("I"), vembed("S")], vec![]);
645 assert!(matches!(
646 resolve_selector(&b.store, &nominal("N2"), "speak"),
647 Resolution::Ambiguous { .. }
648 ));
649 assert!(!promoted_method_set(&b.store, &nominal("N2")).contains_key("speak"));
650 }
651
652 #[test]
653 fn method_set_includes_promoted_excludes_ambiguous() {
654 let mut b = Builder::new();
655 b.struct_(
656 "N0",
657 vec![],
658 vec![("m", value_method("N0")), ("pm", pointer_method("N0"))],
659 );
660 b.struct_("N1", vec![vembed("N0")], vec![("o", value_method("N1"))]);
661 let set = promoted_method_set(&b.store, &nominal("N1"));
662 assert!(set.contains_key("o"));
663 assert!(set.contains_key("m"));
664 assert!(set.contains_key("pm"));
665 assert!(!set.get("m").unwrap().get_function_params().unwrap()[0].is_ref());
666 assert!(set.get("pm").unwrap().get_function_params().unwrap()[0].is_ref());
667 }
668
669 #[test]
670 fn method_set_drops_ambiguous_diamond_member() {
671 let mut b = Builder::new();
672 b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
673 b.struct_("N1", vec![vembed("N0")], vec![]);
674 b.struct_("N2", vec![vembed("N0")], vec![]);
675 b.struct_("N3", vec![vembed("N1"), vembed("N2")], vec![]);
676 assert!(!promoted_method_set(&b.store, &nominal("N3")).contains_key("m"));
677 }
678
679 #[test]
680 fn has_direct_embed_detects_embeds() {
681 let mut b = Builder::new();
682 b.struct_("N0", vec![field("f", Type::int(), false)], vec![]);
683 b.struct_("N1", vec![vembed("N0")], vec![]);
684 assert!(!has_direct_embed(&b.store, &nominal("N0")));
685 assert!(has_direct_embed(&b.store, &nominal("N1")));
686 assert!(has_direct_embed(&b.store, &ref_of(&nominal("N1"))));
687 }
688}