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::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    /// `ty` already carries the effective receiver: value embeds keep the
18    /// declared receiver, promoted methods are re-pointed at the embedder.
19    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    /// A pointer edge was crossed on the path to this subobject.
79    indirect: bool,
80    /// Reached by more than one path at this depth, so its members collide.
81    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 &current {
99            // Seen at a shallower depth: shadows here, and breaks cycles.
100            if !seen.insert(type_key(&entry.ty)) {
101                continue;
102            }
103            visited.push(entry.clone());
104
105            // Interfaces contribute their method set but have no fields to descend.
106            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                // Resolve aliases first so an alias to `Ref<T>` peels as a pointer edge.
120                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
144/// Resolve `name` to its shallowest candidate; a lone non-`multiples` hit wins,
145/// anything else is ambiguous.
146fn 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
186/// The field or method a type declares under `name`. A method shadows a
187/// same-named field, as gc checks attached methods first.
188fn 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            // Only promoted methods are re-pointed; a depth-0 receiver is already
233            // the outer type, and rewriting it would break generics. A promoted
234            // method stays pointer-only when its receiver is a pointer and no
235            // pointer edge was crossed.
236            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
257/// Every field and method name a type exposes.
258fn 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
280/// Build an entry for `target` if it resolves (through aliases) to a nominal type.
281fn 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
302/// gc's `consolidateMultiples`: dedup by type, flagging any reached by more than
303/// one path so its members resolve as ambiguous.
304fn 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
319/// Type identity for `seen`/`multiples`: qualified id plus any type arguments.
320fn 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
331/// Strip one `Ref`, reporting whether it was present (a pointer edge).
332fn 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    /// An interface method as stored after registration: receiver already stripped.
391    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}