Skip to main content

lisette_semantics/
promotion.rs

1use ecow::EcoString;
2use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
3use std::collections::BTreeMap;
4
5use syntax::ast::{Generic, Visibility};
6use syntax::program::{DefinitionBody, MethodSignatures};
7use syntax::types::{CompoundKind, Symbol, Type, build_substitution_map, substitute};
8
9use crate::call_classification::is_ufcs_method_type;
10use crate::store::Store;
11
12#[derive(Clone, Debug)]
13pub enum MemberKind {
14    Field {
15        ty: Type,
16        visibility: Visibility,
17    },
18    /// `ty` already carries the effective receiver: value embeds keep the
19    /// declared receiver, promoted methods are re-pointed at the embedder.
20    Method {
21        ty: Type,
22    },
23}
24
25#[derive(Clone, Debug)]
26pub struct ResolvedMember {
27    pub name: EcoString,
28    pub depth: usize,
29    pub embed_path: Vec<EcoString>,
30    pub declaring_type: Symbol,
31    pub indirect: bool,
32    pub kind: MemberKind,
33}
34
35#[derive(Clone, Debug)]
36pub enum Resolution {
37    Found(ResolvedMember),
38    Ambiguous { sources: Vec<Symbol> },
39    NotFound,
40}
41
42pub fn has_direct_embed(store: &Store, ty: &Type) -> bool {
43    let Type::Nominal { id, .. } = store.deep_resolve_alias(&ty.strip_refs()) else {
44        return false;
45    };
46    store
47        .fields_of(id.as_str())
48        .is_some_and(|fields| fields.iter().any(|f| f.embedded))
49}
50
51pub fn resolve_selector(store: &Store, outer: &Type, name: &str) -> Resolution {
52    let entries = walk(store, outer);
53    resolve_in_entries(store, &entries, outer, name)
54}
55
56pub fn promoted_method_set(store: &Store, outer: &Type) -> MethodSignatures {
57    let entries = walk(store, outer);
58
59    let mut names: HashSet<EcoString> = HashSet::default();
60    for entry in &entries {
61        collect_member_names(store, &entry.ty, &mut names);
62    }
63
64    let mut result = MethodSignatures::default();
65    for name in names {
66        if let Resolution::Found(member) = resolve_in_entries(store, &entries, outer, &name)
67            && let MemberKind::Method { ty } = member.kind
68        {
69            result.insert(name, ty);
70        }
71    }
72    result
73}
74
75#[derive(Clone)]
76struct Entry {
77    ty: Type,
78    depth: usize,
79    /// A pointer edge was crossed on the path to this subobject.
80    indirect: bool,
81    /// Reached by more than one path at this depth, so its members collide.
82    multiples: bool,
83    embed_path: Vec<EcoString>,
84}
85
86fn walk(store: &Store, outer: &Type) -> Vec<Entry> {
87    let mut visited: Vec<Entry> = Vec::new();
88    let mut seen: HashSet<String> = HashSet::default();
89
90    let Some(root) = nominal_entry(store, outer.clone(), 0, false, false, Vec::new()) else {
91        return visited;
92    };
93    let mut current = vec![root];
94    let mut depth = 0;
95
96    while !current.is_empty() {
97        let mut next: Vec<Entry> = Vec::new();
98
99        for entry in &current {
100            // Seen at a shallower depth: shadows here, and breaks cycles.
101            if !seen.insert(type_key(&entry.ty)) {
102                continue;
103            }
104            visited.push(entry.clone());
105
106            // Interfaces contribute their method set but have no fields to descend.
107            let Some(id) = entry.ty.get_qualified_id() else {
108                continue;
109            };
110            if store.get_interface(id).is_some() {
111                continue;
112            }
113            let Some(fields) = store.fields_of(id) else {
114                continue;
115            };
116            for field in fields {
117                if !field.embedded {
118                    continue;
119                }
120                let field_ty = instantiate_field(store, &entry.ty, &field.ty);
121                let resolved_field = store.deep_resolve_alias(&field_ty);
122                let (target, is_pointer) = deref_once(&resolved_field);
123                let mut path = entry.embed_path.clone();
124                path.push(field.name.clone());
125                if let Some(child) = nominal_entry(
126                    store,
127                    target,
128                    depth + 1,
129                    entry.indirect || is_pointer,
130                    entry.multiples,
131                    path,
132                ) {
133                    next.push(child);
134                }
135            }
136        }
137
138        current = consolidate(next);
139        depth += 1;
140    }
141
142    visited
143}
144
145/// Resolve `name` to its shallowest candidate; a lone non-`multiples` hit wins,
146/// anything else is ambiguous.
147fn resolve_in_entries(store: &Store, entries: &[Entry], outer: &Type, name: &str) -> Resolution {
148    let mut by_depth: BTreeMap<usize, Vec<(&Entry, Candidate)>> = BTreeMap::new();
149    for entry in entries {
150        if let Some(candidate) = entry_candidate(store, &entry.ty, name) {
151            by_depth
152                .entry(entry.depth)
153                .or_default()
154                .push((entry, candidate));
155        }
156    }
157
158    let Some((_, candidates)) = by_depth.into_iter().next() else {
159        return Resolution::NotFound;
160    };
161
162    if let [(entry, candidate)] = candidates.as_slice()
163        && !entry.multiples
164    {
165        return Resolution::Found(build_member(outer, name, entry, candidate));
166    }
167
168    let mut sources: Vec<Symbol> = candidates
169        .iter()
170        .map(|(_, c)| c.declaring_type.clone())
171        .collect();
172    sources.sort();
173    sources.dedup();
174    Resolution::Ambiguous { sources }
175}
176
177struct Candidate {
178    declaring_type: Symbol,
179    detail: CandidateDetail,
180}
181
182enum CandidateDetail {
183    Field { ty: Type, visibility: Visibility },
184    Method { ty: Type },
185}
186
187/// The field or method a type declares under `name`. A method shadows a
188/// same-named field, as gc checks attached methods first.
189fn entry_candidate(store: &Store, ty: &Type, name: &str) -> Option<Candidate> {
190    let id = ty.get_qualified_id()?;
191
192    if store.get_interface(id).is_some() {
193        let methods = store.get_all_methods(ty, &Default::default());
194        let method_ty = methods.get(name)?.clone();
195        return Some(Candidate {
196            declaring_type: Symbol::from_raw(id),
197            detail: CandidateDetail::Method { ty: method_ty },
198        });
199    }
200
201    if let Some(method_ty) = store.get_own_methods(id).and_then(|m| m.get(name)) {
202        return Some(Candidate {
203            declaring_type: Symbol::from_raw(id),
204            detail: CandidateDetail::Method {
205                ty: instantiate_method(store, ty, method_ty)?,
206            },
207        });
208    }
209
210    if let Some(field) = store
211        .fields_of(id)
212        .and_then(|fields| fields.iter().find(|f| f.name == name))
213    {
214        return Some(Candidate {
215            declaring_type: Symbol::from_raw(id),
216            detail: CandidateDetail::Field {
217                ty: instantiate_field(store, ty, &field.ty),
218                visibility: field.visibility,
219            },
220        });
221    }
222
223    None
224}
225
226fn build_member(outer: &Type, name: &str, entry: &Entry, candidate: &Candidate) -> ResolvedMember {
227    let kind = match &candidate.detail {
228        CandidateDetail::Field { ty, visibility } => MemberKind::Field {
229            ty: ty.clone(),
230            visibility: *visibility,
231        },
232        CandidateDetail::Method { ty } => {
233            // Only promoted methods are re-pointed; a depth-0 receiver is already
234            // the outer type, and rewriting it would break generics. A promoted
235            // method stays pointer-only when its receiver is a pointer and no
236            // pointer edge was crossed.
237            let method_ty = if entry.depth == 0 {
238                ty.clone()
239            } else if !entry.indirect && method_has_pointer_receiver(ty) {
240                ty.with_replaced_first_param(&ref_of(outer))
241            } else {
242                ty.with_replaced_first_param(outer)
243            };
244            MemberKind::Method { ty: method_ty }
245        }
246    };
247
248    ResolvedMember {
249        name: name.into(),
250        depth: entry.depth,
251        embed_path: entry.embed_path.clone(),
252        declaring_type: candidate.declaring_type.clone(),
253        indirect: entry.indirect,
254        kind,
255    }
256}
257
258/// Every field and method name a type exposes.
259fn collect_member_names(store: &Store, ty: &Type, names: &mut HashSet<EcoString>) {
260    let Some(id) = ty.get_qualified_id() else {
261        return;
262    };
263    if store.get_interface(id).is_some() {
264        for key in store.get_all_methods(ty, &Default::default()).keys() {
265            names.insert(key.clone());
266        }
267        return;
268    }
269    if let Some(methods) = store.get_own_methods(id) {
270        for key in methods.keys() {
271            names.insert(key.clone());
272        }
273    }
274    if let Some(fields) = store.fields_of(id) {
275        for field in fields {
276            names.insert(field.name.clone());
277        }
278    }
279}
280
281/// Build an entry for `target` if it resolves (through aliases) to a nominal type.
282fn nominal_entry(
283    store: &Store,
284    target: Type,
285    depth: usize,
286    indirect: bool,
287    multiples: bool,
288    embed_path: Vec<EcoString>,
289) -> Option<Entry> {
290    let resolved = store.deep_resolve_alias(&target);
291    if !matches!(resolved, Type::Nominal { .. }) {
292        return None;
293    }
294    Some(Entry {
295        ty: resolved,
296        depth,
297        indirect,
298        multiples,
299        embed_path,
300    })
301}
302
303/// gc's `consolidateMultiples`: dedup by type, flagging any reached by more than
304/// one path so its members resolve as ambiguous.
305fn consolidate(list: Vec<Entry>) -> Vec<Entry> {
306    let mut result: Vec<Entry> = Vec::with_capacity(list.len());
307    let mut index_of: HashMap<String, usize> = HashMap::default();
308    for entry in list {
309        let key = type_key(&entry.ty);
310        if let Some(&i) = index_of.get(&key) {
311            result[i].multiples = true;
312        } else {
313            index_of.insert(key, result.len());
314            result.push(entry);
315        }
316    }
317    result
318}
319
320/// Type identity for `seen`/`multiples`: qualified id plus any type arguments.
321fn type_key(ty: &Type) -> String {
322    match ty {
323        Type::Nominal { id, params, .. } if params.is_empty() => id.as_str().to_string(),
324        Type::Nominal { id, params, .. } => {
325            let args: Vec<String> = params.iter().map(type_key).collect();
326            format!("{}<{}>", id, args.join(","))
327        }
328        other => other.to_string(),
329    }
330}
331
332/// Strip one `Ref`, reporting whether it was present (a pointer edge).
333fn deref_once(ty: &Type) -> (Type, bool) {
334    if ty.is_ref() {
335        (ty.inner().unwrap_or(Type::Error), true)
336    } else {
337        (ty.clone(), false)
338    }
339}
340
341fn method_has_pointer_receiver(method_ty: &Type) -> bool {
342    let body = match method_ty {
343        Type::Forall { body, .. } => body.as_ref(),
344        other => other,
345    };
346    matches!(body, Type::Function(f) if f.params.first().is_some_and(Type::is_ref))
347}
348
349fn ref_of(ty: &Type) -> Type {
350    Type::Compound {
351        kind: CompoundKind::Ref,
352        args: vec![ty.clone()],
353    }
354}
355
356fn declaring_generics<'a>(store: &'a Store, id: &str) -> &'a [Generic] {
357    match store.get_definition(id).map(|d| &d.body) {
358        Some(
359            DefinitionBody::Struct { generics, .. }
360            | DefinitionBody::Enum { generics, .. }
361            | DefinitionBody::TypeAlias { generics, .. },
362        ) => generics,
363        Some(DefinitionBody::Interface { definition }) => &definition.generics,
364        _ => &[],
365    }
366}
367
368fn instantiate_field(store: &Store, container: &Type, member_ty: &Type) -> Type {
369    let Some(id) = container.get_qualified_id() else {
370        return member_ty.clone();
371    };
372    let args = container.get_type_params().unwrap_or_default();
373    if args.is_empty() {
374        return member_ty.clone();
375    }
376    substitute(
377        member_ty,
378        &build_substitution_map(declaring_generics(store, id), args),
379    )
380}
381
382fn instantiate_method(store: &Store, container: &Type, method_ty: &Type) -> Option<Type> {
383    let Some(id) = container.get_qualified_id() else {
384        return Some(method_ty.clone());
385    };
386    let arity = declaring_generics(store, id).len();
387    if is_ufcs_method_type(method_ty, arity) {
388        return None;
389    }
390    let args = container.get_type_params().unwrap_or_default();
391    if args.is_empty() || arity == 0 {
392        return Some(method_ty.clone());
393    }
394    let Type::Forall { vars, body } = method_ty else {
395        return Some(method_ty.clone());
396    };
397    let map: HashMap<EcoString, Type> = vars.iter().cloned().zip(args.iter().cloned()).collect();
398    Some(substitute(body, &map))
399}
400
401#[cfg(test)]
402mod tests {
403    use super::*;
404    use syntax::ast::{Annotation, Span, StructFieldDefinition, StructKind};
405    use syntax::program::Visibility as ProgVis;
406    use syntax::program::{Attributes, Definition, DefinitionBody, Interface};
407
408    const MODULE: &str = "m";
409
410    fn nominal(name: &str) -> Type {
411        Type::Nominal {
412            id: Symbol::from_parts(MODULE, name),
413            params: vec![],
414            underlying_ty: None,
415        }
416    }
417
418    fn value_method(owner: &str) -> Type {
419        Type::function(
420            vec![nominal(owner)],
421            vec![false],
422            vec![],
423            Box::new(Type::string()),
424        )
425    }
426
427    fn pointer_method(owner: &str) -> Type {
428        Type::function(
429            vec![ref_of(&nominal(owner))],
430            vec![false],
431            vec![],
432            Box::new(Type::string()),
433        )
434    }
435
436    /// An interface method as stored after registration: receiver already stripped.
437    fn interface_method() -> Type {
438        Type::function(vec![], vec![], vec![], Box::new(Type::string()))
439    }
440
441    fn field(name: &str, ty: Type, embedded: bool) -> StructFieldDefinition {
442        StructFieldDefinition {
443            doc: None,
444            attributes: vec![],
445            name: name.into(),
446            name_span: Span::dummy(),
447            annotation: Annotation::Unknown,
448            visibility: Visibility::Public,
449            ty,
450            embedded,
451        }
452    }
453
454    struct Builder {
455        store: Store,
456    }
457
458    impl Builder {
459        fn new() -> Self {
460            let mut store = Store::new();
461            store.add_module(MODULE);
462            Builder { store }
463        }
464
465        fn insert(&mut self, name: &str, body: DefinitionBody) -> &mut Self {
466            let def = Definition {
467                visibility: ProgVis::Public,
468                ty: nominal(name),
469                name: Some(name.into()),
470                name_span: None,
471                doc: None,
472                body,
473            };
474            self.store
475                .get_module_mut(MODULE)
476                .unwrap()
477                .definitions
478                .insert(Symbol::from_parts(MODULE, name), def);
479            self
480        }
481
482        fn struct_(
483            &mut self,
484            name: &str,
485            fields: Vec<StructFieldDefinition>,
486            methods: Vec<(&str, Type)>,
487        ) -> &mut Self {
488            let mut method_map = MethodSignatures::default();
489            for (n, t) in methods {
490                method_map.insert(n.into(), t);
491            }
492            self.insert(
493                name,
494                DefinitionBody::Struct {
495                    generics: vec![],
496                    fields,
497                    kind: StructKind::Record,
498                    methods: method_map,
499                    constructor: None,
500                    attributes: Attributes::default(),
501                },
502            )
503        }
504
505        fn generic_struct(
506            &mut self,
507            name: &str,
508            generics: Vec<&str>,
509            fields: Vec<StructFieldDefinition>,
510            methods: Vec<(&str, Type)>,
511        ) -> &mut Self {
512            let mut method_map = MethodSignatures::default();
513            for (n, t) in methods {
514                method_map.insert(n.into(), t);
515            }
516            self.insert(
517                name,
518                DefinitionBody::Struct {
519                    generics: generics
520                        .into_iter()
521                        .map(|g| Generic {
522                            name: g.into(),
523                            bounds: vec![],
524                            span: Span::dummy(),
525                        })
526                        .collect(),
527                    fields,
528                    kind: StructKind::Record,
529                    methods: method_map,
530                    constructor: None,
531                    attributes: Attributes::default(),
532                },
533            )
534        }
535
536        fn interface(&mut self, name: &str, methods: Vec<&str>, parents: Vec<&str>) -> &mut Self {
537            let mut method_map = MethodSignatures::default();
538            for n in methods {
539                method_map.insert(n.into(), interface_method());
540            }
541            self.insert(
542                name,
543                DefinitionBody::Interface {
544                    definition: Interface {
545                        name: name.into(),
546                        generics: vec![],
547                        parents: parents.into_iter().map(nominal).collect(),
548                        methods: method_map,
549                    },
550                },
551            )
552        }
553    }
554
555    fn vembed(target: &str) -> StructFieldDefinition {
556        field(target, nominal(target), true)
557    }
558
559    fn pembed(target: &str) -> StructFieldDefinition {
560        field(target, ref_of(&nominal(target)), true)
561    }
562
563    fn param(name: &str) -> Type {
564        Type::Parameter(name.into())
565    }
566
567    fn generic_nominal(name: &str, args: Vec<Type>) -> Type {
568        Type::Nominal {
569            id: Symbol::from_parts(MODULE, name),
570            params: args,
571            underlying_ty: None,
572        }
573    }
574
575    fn generic_value_method(owner: &str, impl_var: &str, ret: Type) -> Type {
576        Type::Forall {
577            vars: vec![impl_var.into()],
578            body: Box::new(Type::function(
579                vec![generic_nominal(owner, vec![param(impl_var)])],
580                vec![false],
581                vec![],
582                Box::new(ret),
583            )),
584        }
585    }
586
587    fn found(resolution: Resolution) -> ResolvedMember {
588        match resolution {
589            Resolution::Found(member) => member,
590            other => panic!("expected Found, got {other:?}"),
591        }
592    }
593
594    fn is_pointer_receiver(member: &ResolvedMember) -> bool {
595        match &member.kind {
596            MemberKind::Method { ty } => ty.get_function_params().unwrap()[0].is_ref(),
597            other => panic!("expected a method, got {other:?}"),
598        }
599    }
600
601    #[test]
602    fn direct_method_at_depth_zero() {
603        let mut b = Builder::new();
604        b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
605        let member = found(resolve_selector(&b.store, &nominal("N0"), "m"));
606        assert_eq!(member.depth, 0);
607        assert!(!is_pointer_receiver(&member));
608    }
609
610    #[test]
611    fn value_embed_promotes_value_method() {
612        let mut b = Builder::new();
613        b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
614        b.struct_("N1", vec![vembed("N0")], vec![]);
615        let member = found(resolve_selector(&b.store, &nominal("N1"), "m"));
616        assert_eq!(member.depth, 1);
617        assert_eq!(member.embed_path, vec![EcoString::from("N0")]);
618        assert!(!member.indirect);
619        assert!(!is_pointer_receiver(&member));
620    }
621
622    #[test]
623    fn value_embed_of_pointer_method_is_pointer_only() {
624        let mut b = Builder::new();
625        b.struct_("N0", vec![], vec![("pm", pointer_method("N0"))]);
626        b.struct_("N1", vec![vembed("N0")], vec![]);
627        let member = found(resolve_selector(&b.store, &nominal("N1"), "pm"));
628        assert!(!member.indirect);
629        assert!(is_pointer_receiver(&member));
630    }
631
632    #[test]
633    fn pointer_embed_puts_pointer_method_in_value_set() {
634        let mut b = Builder::new();
635        b.struct_("N0", vec![], vec![("pm", pointer_method("N0"))]);
636        b.struct_("N1", vec![pembed("N0")], vec![]);
637        let member = found(resolve_selector(&b.store, &nominal("N1"), "pm"));
638        assert!(member.indirect);
639        assert!(!is_pointer_receiver(&member));
640    }
641
642    #[test]
643    fn pointer_embed_of_value_method_is_value() {
644        let mut b = Builder::new();
645        b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
646        b.struct_("N1", vec![pembed("N0")], vec![]);
647        let member = found(resolve_selector(&b.store, &nominal("N1"), "m"));
648        assert!(member.indirect);
649        assert!(!is_pointer_receiver(&member));
650    }
651
652    #[test]
653    fn three_value_edges_keep_pointer_method_pointer_only() {
654        let mut b = Builder::new();
655        b.struct_("N0", vec![], vec![("pm", pointer_method("N0"))]);
656        b.struct_("N1", vec![vembed("N0")], vec![]);
657        b.struct_("N2", vec![vembed("N1")], vec![]);
658        b.struct_("N3", vec![vembed("N2")], vec![]);
659        let member = found(resolve_selector(&b.store, &nominal("N3"), "pm"));
660        assert_eq!(member.depth, 3);
661        assert!(!member.indirect);
662        assert!(is_pointer_receiver(&member));
663    }
664
665    #[test]
666    fn pointer_edge_mid_three_level_path_puts_pointer_method_in_value_set() {
667        let mut b = Builder::new();
668        b.struct_("N0", vec![], vec![("pm", pointer_method("N0"))]);
669        b.struct_("N1", vec![vembed("N0")], vec![]);
670        b.struct_("N2", vec![pembed("N1")], vec![]);
671        b.struct_("N3", vec![vembed("N2")], vec![]);
672        let member = found(resolve_selector(&b.store, &nominal("N3"), "pm"));
673        assert_eq!(member.depth, 3);
674        assert!(member.indirect);
675        assert!(!is_pointer_receiver(&member));
676    }
677
678    #[test]
679    fn value_method_promotes_through_three_value_edges() {
680        let mut b = Builder::new();
681        b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
682        b.struct_("N1", vec![vembed("N0")], vec![]);
683        b.struct_("N2", vec![vembed("N1")], vec![]);
684        b.struct_("N3", vec![vembed("N2")], vec![]);
685        let member = found(resolve_selector(&b.store, &nominal("N3"), "m"));
686        assert_eq!(member.depth, 3);
687        assert!(!member.indirect);
688        assert!(!is_pointer_receiver(&member));
689    }
690
691    #[test]
692    fn diamond_is_ambiguous() {
693        let mut b = Builder::new();
694        b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
695        b.struct_("N1", vec![vembed("N0")], vec![]);
696        b.struct_("N2", vec![vembed("N0")], vec![]);
697        b.struct_("N3", vec![vembed("N1"), vembed("N2")], vec![]);
698        assert!(matches!(
699            resolve_selector(&b.store, &nominal("N3"), "m"),
700            Resolution::Ambiguous { .. }
701        ));
702    }
703
704    #[test]
705    fn shallower_path_shadows_deeper_diamond() {
706        let mut b = Builder::new();
707        b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
708        b.struct_("N1", vec![vembed("N0")], vec![]);
709        b.struct_("N3", vec![vembed("N0"), vembed("N1")], vec![]);
710        let member = found(resolve_selector(&b.store, &nominal("N3"), "m"));
711        assert_eq!(member.depth, 1);
712    }
713
714    #[test]
715    fn own_member_shadows_promoted() {
716        let mut b = Builder::new();
717        b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
718        b.struct_("N1", vec![vembed("N0")], vec![("m", value_method("N1"))]);
719        let member = found(resolve_selector(&b.store, &nominal("N1"), "m"));
720        assert_eq!(member.depth, 0);
721        assert_eq!(member.declaring_type.as_str(), "m.N1");
722    }
723
724    #[test]
725    fn field_promotes() {
726        let mut b = Builder::new();
727        b.struct_("N0", vec![field("f", Type::int(), false)], vec![]);
728        b.struct_("N1", vec![vembed("N0")], vec![]);
729        let member = found(resolve_selector(&b.store, &nominal("N1"), "f"));
730        assert_eq!(member.depth, 1);
731        assert!(matches!(member.kind, MemberKind::Field { .. }));
732    }
733
734    #[test]
735    fn field_and_method_collide_across_embeds() {
736        let mut b = Builder::new();
737        b.struct_("A", vec![field("x", Type::int(), false)], vec![]);
738        b.struct_("B", vec![], vec![("x", value_method("B"))]);
739        b.struct_("N2", vec![vembed("A"), vembed("B")], vec![]);
740        assert!(matches!(
741            resolve_selector(&b.store, &nominal("N2"), "x"),
742            Resolution::Ambiguous { .. }
743        ));
744    }
745
746    #[test]
747    fn pointer_cycle_terminates_and_resolves() {
748        let mut b = Builder::new();
749        b.struct_("N0", vec![pembed("N1")], vec![("a", value_method("N0"))]);
750        b.struct_("N1", vec![vembed("N0")], vec![("bb", value_method("N1"))]);
751        assert_eq!(
752            found(resolve_selector(&b.store, &nominal("N0"), "a")).depth,
753            0
754        );
755        assert_eq!(
756            found(resolve_selector(&b.store, &nominal("N0"), "bb")).depth,
757            1
758        );
759        assert_eq!(
760            found(resolve_selector(&b.store, &nominal("N1"), "a")).depth,
761            1
762        );
763        assert!(matches!(
764            resolve_selector(&b.store, &nominal("N0"), "absent"),
765            Resolution::NotFound
766        ));
767    }
768
769    #[test]
770    fn embedded_interface_promotes_value_callable() {
771        let mut b = Builder::new();
772        b.interface("I", vec!["speak"], vec![]);
773        b.struct_("N2", vec![vembed("I")], vec![]);
774        let member = found(resolve_selector(&b.store, &nominal("N2"), "speak"));
775        assert_eq!(member.depth, 1);
776        assert!(!is_pointer_receiver(&member));
777    }
778
779    #[test]
780    fn struct_embedding_interface_and_struct_with_same_method_is_ambiguous() {
781        let mut b = Builder::new();
782        b.interface("I", vec!["speak"], vec![]);
783        b.struct_("S", vec![], vec![("speak", value_method("S"))]);
784        b.struct_("N2", vec![vembed("I"), vembed("S")], vec![]);
785        assert!(matches!(
786            resolve_selector(&b.store, &nominal("N2"), "speak"),
787            Resolution::Ambiguous { .. }
788        ));
789        assert!(!promoted_method_set(&b.store, &nominal("N2")).contains_key("speak"));
790    }
791
792    #[test]
793    fn method_set_includes_promoted_excludes_ambiguous() {
794        let mut b = Builder::new();
795        b.struct_(
796            "N0",
797            vec![],
798            vec![("m", value_method("N0")), ("pm", pointer_method("N0"))],
799        );
800        b.struct_("N1", vec![vembed("N0")], vec![("o", value_method("N1"))]);
801        let set = promoted_method_set(&b.store, &nominal("N1"));
802        assert!(set.contains_key("o"));
803        assert!(set.contains_key("m"));
804        assert!(set.contains_key("pm"));
805        assert!(!set.get("m").unwrap().get_function_params().unwrap()[0].is_ref());
806        assert!(set.get("pm").unwrap().get_function_params().unwrap()[0].is_ref());
807    }
808
809    #[test]
810    fn method_set_drops_ambiguous_diamond_member() {
811        let mut b = Builder::new();
812        b.struct_("N0", vec![], vec![("m", value_method("N0"))]);
813        b.struct_("N1", vec![vembed("N0")], vec![]);
814        b.struct_("N2", vec![vembed("N0")], vec![]);
815        b.struct_("N3", vec![vembed("N1"), vembed("N2")], vec![]);
816        assert!(!promoted_method_set(&b.store, &nominal("N3")).contains_key("m"));
817    }
818
819    #[test]
820    fn has_direct_embed_detects_embeds() {
821        let mut b = Builder::new();
822        b.struct_("N0", vec![field("f", Type::int(), false)], vec![]);
823        b.struct_("N1", vec![vembed("N0")], vec![]);
824        assert!(!has_direct_embed(&b.store, &nominal("N0")));
825        assert!(has_direct_embed(&b.store, &nominal("N1")));
826        assert!(has_direct_embed(&b.store, &ref_of(&nominal("N1"))));
827    }
828
829    fn method_return(member: &ResolvedMember) -> Type {
830        match &member.kind {
831            MemberKind::Method { ty } => ty.get_function_ret().unwrap().clone(),
832            other => panic!("expected a method, got {other:?}"),
833        }
834    }
835
836    #[test]
837    fn generic_embed_promotes_field_at_instantiation() {
838        let mut b = Builder::new();
839        b.generic_struct(
840            "Box",
841            vec!["T"],
842            vec![field("value", param("T"), false)],
843            vec![],
844        );
845        b.struct_(
846            "Outer",
847            vec![field(
848                "Box",
849                generic_nominal("Box", vec![Type::int()]),
850                true,
851            )],
852            vec![],
853        );
854        let member = found(resolve_selector(&b.store, &nominal("Outer"), "value"));
855        match member.kind {
856            MemberKind::Field { ty, .. } => assert_eq!(ty, Type::int()),
857            other => panic!("expected a field, got {other:?}"),
858        }
859    }
860
861    #[test]
862    fn generic_embed_promotes_method_at_instantiation() {
863        let mut b = Builder::new();
864        b.generic_struct(
865            "Box",
866            vec!["T"],
867            vec![],
868            vec![("get", generic_value_method("Box", "T", param("T")))],
869        );
870        b.struct_(
871            "Outer",
872            vec![field(
873                "Box",
874                generic_nominal("Box", vec![Type::string()]),
875                true,
876            )],
877            vec![],
878        );
879        let member = found(resolve_selector(&b.store, &nominal("Outer"), "get"));
880        assert_eq!(method_return(&member), Type::string());
881    }
882
883    #[test]
884    fn generic_embedder_flows_its_param_into_the_target() {
885        let mut b = Builder::new();
886        b.generic_struct(
887            "Box",
888            vec!["T"],
889            vec![],
890            vec![("get", generic_value_method("Box", "T", param("T")))],
891        );
892        b.generic_struct(
893            "Outer",
894            vec!["U"],
895            vec![field("Box", generic_nominal("Box", vec![param("U")]), true)],
896            vec![],
897        );
898        let member = found(resolve_selector(
899            &b.store,
900            &generic_nominal("Outer", vec![Type::int()]),
901            "get",
902        ));
903        assert_eq!(method_return(&member), Type::int());
904    }
905
906    #[test]
907    fn renamed_impl_param_is_captured() {
908        // struct param is `T`, but the method's impl var is `V` (`impl<V> Box<V>`).
909        let mut b = Builder::new();
910        b.generic_struct(
911            "Box",
912            vec!["T"],
913            vec![],
914            vec![("get", generic_value_method("Box", "V", param("V")))],
915        );
916        b.struct_(
917            "Outer",
918            vec![field(
919                "Box",
920                generic_nominal("Box", vec![Type::int()]),
921                true,
922            )],
923            vec![],
924        );
925        let member = found(resolve_selector(&b.store, &nominal("Outer"), "get"));
926        assert_eq!(method_return(&member), Type::int());
927    }
928
929    #[test]
930    fn specialized_impl_method_is_skipped() {
931        let specialized = Type::Forall {
932            vars: vec!["V".into()],
933            body: Box::new(Type::function(
934                vec![generic_nominal(
935                    "Box",
936                    vec![Type::Compound {
937                        kind: CompoundKind::Slice,
938                        args: vec![param("V")],
939                    }],
940                )],
941                vec![false],
942                vec![],
943                Box::new(param("V")),
944            )),
945        };
946        let mut b = Builder::new();
947        b.generic_struct("Box", vec!["T"], vec![], vec![("weird", specialized)]);
948        b.struct_(
949            "Outer",
950            vec![field(
951                "Box",
952                generic_nominal("Box", vec![Type::int()]),
953                true,
954            )],
955            vec![],
956        );
957        assert!(matches!(
958            resolve_selector(&b.store, &nominal("Outer"), "weird"),
959            Resolution::NotFound
960        ));
961    }
962
963    /// A concrete `impl Box<int> { fn only_int(self: Box<int>) -> int }`, stored
964    /// without a `Forall` because it binds no type variables.
965    fn concrete_int_method() -> Type {
966        Type::function(
967            vec![generic_nominal("Box", vec![Type::int()])],
968            vec![false],
969            vec![],
970            Box::new(Type::int()),
971        )
972    }
973
974    #[test]
975    fn specialized_impl_does_not_promote_onto_other_instantiation() {
976        let mut b = Builder::new();
977        b.generic_struct(
978            "Box",
979            vec!["T"],
980            vec![],
981            vec![("only_int", concrete_int_method())],
982        );
983        b.struct_(
984            "Outer",
985            vec![field(
986                "Box",
987                generic_nominal("Box", vec![Type::string()]),
988                true,
989            )],
990            vec![],
991        );
992        assert!(matches!(
993            resolve_selector(&b.store, &nominal("Outer"), "only_int"),
994            Resolution::NotFound
995        ));
996    }
997
998    #[test]
999    fn specialized_impl_method_not_promoted_onto_matching_instantiation() {
1000        let mut b = Builder::new();
1001        b.generic_struct(
1002            "Box",
1003            vec!["T"],
1004            vec![],
1005            vec![("only_int", concrete_int_method())],
1006        );
1007        b.struct_(
1008            "Outer",
1009            vec![field(
1010                "Box",
1011                generic_nominal("Box", vec![Type::int()]),
1012                true,
1013            )],
1014            vec![],
1015        );
1016        assert!(matches!(
1017            resolve_selector(&b.store, &nominal("Outer"), "only_int"),
1018            Resolution::NotFound
1019        ));
1020    }
1021}