Skip to main content

bock_types/
traits.rs

1//! Trait resolution — `ImplTable` construction, impl/method dispatch,
2//! coherence checking, associated-type resolution, and supertrait obligations.
3//!
4//! # Overview
5//!
6//! The [`ImplTable`] is the central data structure. It is built from an AIR
7//! module node by [`ImplTable::build_from`], which walks all top-level
8//! [`NodeKind::ImplBlock`] items and registers them. Two free functions then
9//! provide the main resolution queries:
10//!
11//! - [`resolve_impl`] — find the [`ImplId`] for a `(trait, type)` pair.
12//! - [`resolve_method`] — dispatch a `.method()` call on a receiver type.
13//!
14//! Coherence checking (exact-type overlap detection) runs during construction.
15//! Generic-parameter impls are registered but exempted from the coherence check.
16//!
17//! # Supertrait obligations
18//!
19//! Call [`check_supertrait_obligations`] after [`resolve_impl`] to verify that
20//! all transitively required supertraits are also satisfied.
21
22use std::collections::{HashMap, HashSet, VecDeque};
23
24use bock_air::{AIRNode, NodeKind};
25use bock_ast::TypePath;
26use bock_errors::{DiagnosticBag, DiagnosticCode};
27
28use crate::{GenericType, NamedType, PrimitiveType, Type};
29
30// ─── Diagnostic codes ─────────────────────────────────────────────────────────
31
32const E_COHERENCE_OVERLAP: DiagnosticCode = DiagnosticCode {
33    prefix: 'E',
34    number: 4010,
35};
36
37// ─── Core types ───────────────────────────────────────────────────────────────
38
39/// Unique identifier for a registered impl block.
40pub type ImplId = u32;
41
42/// A reference to a named trait, identified by its fully-qualified name.
43///
44/// Examples: `"Equatable"`, `"Std.Io.Writable"`.
45#[derive(Debug, Clone, PartialEq, Eq, Hash)]
46pub struct TraitRef {
47    /// Fully-qualified name of the trait.
48    pub name: String,
49}
50
51impl TraitRef {
52    /// Create a `TraitRef` from any string-like value.
53    #[must_use]
54    pub fn new(name: impl Into<String>) -> Self {
55        Self { name: name.into() }
56    }
57
58    fn from_path(path: &TypePath) -> Self {
59        let name = path
60            .segments
61            .iter()
62            .map(|s| s.name.as_str())
63            .collect::<Vec<_>>()
64            .join(".");
65        Self { name }
66    }
67}
68
69/// The result of a successful method dispatch via [`resolve_method`].
70#[derive(Debug, Clone)]
71pub struct ResolvedMethod {
72    /// The impl block that provides this method.
73    pub impl_id: ImplId,
74    /// The trait this method comes from, or `None` for inherent impls.
75    pub trait_ref: Option<TraitRef>,
76    /// The resolved method name.
77    pub method: String,
78}
79
80// ─── ImplTable ────────────────────────────────────────────────────────────────
81
82/// A record of a single impl block registered in the [`ImplTable`].
83#[derive(Debug, Clone)]
84pub struct ImplEntry {
85    /// Unique id allocated for this impl block.
86    pub id: ImplId,
87    /// Trait being implemented, or `None` for an inherent impl.
88    pub trait_ref: Option<TraitRef>,
89    /// Canonical string key for the target type (see [`type_key`]).
90    pub type_key: String,
91    /// Names of the methods provided by this impl.
92    pub methods: Vec<String>,
93    /// `true` when the impl has at least one generic type parameter.
94    ///
95    /// Generic impls are registered but skipped during coherence checking.
96    pub is_generic: bool,
97}
98
99/// Maps `(TraitRef, Type)` pairs to impl blocks and supports method dispatch.
100///
101/// # Construction
102///
103/// ```ignore
104/// let table = ImplTable::build_from(&air_module_node);
105/// // inspect table.diags for coherence errors
106/// ```
107///
108/// # Querying
109///
110/// ```ignore
111/// let impl_id = resolve_impl(&TraitRef::new("Equatable"), &ty, &table)?;
112/// let method  = resolve_method(&receiver_ty, "equals", &table)?;
113/// ```
114pub struct ImplTable {
115    /// All registered impl entries indexed by [`ImplId`].
116    entries: HashMap<ImplId, ImplEntry>,
117    /// Trait impl index: `(trait_name, type_key) → ImplId` (concrete impls only).
118    trait_impl_index: HashMap<(String, String), ImplId>,
119    /// Inherent impl index: `type_key → ImplId`.
120    inherent_impl_index: HashMap<String, ImplId>,
121    /// Supertrait graph: `trait_name → list of direct supertrait names`.
122    supertraits: HashMap<String, Vec<String>>,
123    /// Associated type bindings: `(ImplId, assoc_type_name) → Type`.
124    assoc_types: HashMap<(ImplId, String), Type>,
125    /// Monotonically increasing id counter.
126    next_id: u32,
127    /// Diagnostics emitted during construction (coherence errors use `E4010`).
128    pub diags: DiagnosticBag,
129}
130
131impl ImplTable {
132    /// Create a new, empty `ImplTable`.
133    #[must_use]
134    pub fn new() -> Self {
135        Self {
136            entries: HashMap::new(),
137            trait_impl_index: HashMap::new(),
138            inherent_impl_index: HashMap::new(),
139            supertraits: HashMap::new(),
140            assoc_types: HashMap::new(),
141            next_id: 0,
142            diags: DiagnosticBag::new(),
143        }
144    }
145
146    /// Build an `ImplTable` by walking the top-level items of an AIR module node.
147    ///
148    /// The node must have `NodeKind::Module`; other node kinds produce an empty
149    /// table. Coherence checking (exact-type `impl` overlap detection) runs
150    /// during construction and emits `E4010` diagnostics for any duplicate
151    /// `(Trait, Type)` pair.
152    #[must_use]
153    pub fn build_from(module: &AIRNode) -> Self {
154        let mut table = Self::new();
155        if let NodeKind::Module { items, .. } = &module.kind {
156            for item in items {
157                table.visit_item(item);
158            }
159        }
160        table
161    }
162
163    fn visit_item(&mut self, node: &AIRNode) {
164        match &node.kind {
165            NodeKind::ImplBlock {
166                trait_path,
167                target,
168                methods,
169                generic_params,
170                ..
171            } => {
172                let trait_ref = trait_path.as_ref().map(TraitRef::from_path);
173                let type_key = type_key_from_node(target);
174                let is_generic = !generic_params.is_empty();
175
176                // Coherence: detect exact-type duplicates (skip generic impls).
177                if !is_generic {
178                    if let Some(tr) = &trait_ref {
179                        let index_key = (tr.name.clone(), type_key.clone());
180                        if self.trait_impl_index.contains_key(&index_key) {
181                            self.diags.error(
182                                E_COHERENCE_OVERLAP,
183                                format!(
184                                    "conflicting implementations of trait `{}` for type `{}`",
185                                    tr.name, type_key,
186                                ),
187                                node.span,
188                            );
189                            return;
190                        }
191                    }
192                }
193
194                let id = self.alloc_id();
195
196                // Collect method names, and register any associated type aliases.
197                let mut method_names = Vec::new();
198                for m in methods {
199                    match &m.kind {
200                        NodeKind::FnDecl { name, .. } => {
201                            method_names.push(name.name.clone());
202                        }
203                        NodeKind::TypeAlias { name, ty, .. } => {
204                            // Associated type binding: `type Assoc = ConcreteType`.
205                            let resolved = type_from_node(ty);
206                            self.assoc_types.insert((id, name.name.clone()), resolved);
207                        }
208                        _ => {}
209                    }
210                }
211
212                // Register in the appropriate index.
213                if let Some(tr) = &trait_ref {
214                    if !is_generic {
215                        self.trait_impl_index
216                            .insert((tr.name.clone(), type_key.clone()), id);
217                    }
218                } else {
219                    // Inherent impl — last registration wins for the type key.
220                    self.inherent_impl_index.insert(type_key.clone(), id);
221                }
222
223                self.entries.insert(
224                    id,
225                    ImplEntry {
226                        id,
227                        trait_ref,
228                        type_key,
229                        methods: method_names,
230                        is_generic,
231                    },
232                );
233            }
234
235            NodeKind::TraitDecl {
236                name,
237                generic_params,
238                ..
239            } => {
240                // Extract supertrait bounds: any bound on a `Self` generic param
241                // is treated as a supertrait requirement.
242                for param in generic_params {
243                    if param.name.name == "Self" {
244                        for bound in &param.bounds {
245                            let supertrait = trait_name_from_path(bound);
246                            self.register_supertrait(name.name.clone(), supertrait);
247                        }
248                    }
249                }
250            }
251
252            _ => {}
253        }
254    }
255
256    fn alloc_id(&mut self) -> ImplId {
257        let id = self.next_id;
258        self.next_id += 1;
259        id
260    }
261
262    /// Register a direct supertrait relationship: every impl of `sub_trait`
263    /// must also impl `super_trait`.
264    pub fn register_supertrait(
265        &mut self,
266        sub_trait: impl Into<String>,
267        super_trait: impl Into<String>,
268    ) {
269        self.supertraits
270            .entry(sub_trait.into())
271            .or_default()
272            .push(super_trait.into());
273    }
274
275    /// Programmatically register a trait impl for a concrete type.
276    ///
277    /// This is a convenience method for tests and downstream passes that need
278    /// to populate the table without building from AIR nodes.
279    pub fn register_trait_impl(&mut self, trait_name: impl Into<String>, ty: &Type) -> ImplId {
280        let id = self.alloc_id();
281        let trait_name = trait_name.into();
282        let key = type_key(ty);
283        self.entries.insert(
284            id,
285            ImplEntry {
286                id,
287                trait_ref: Some(TraitRef::new(&trait_name)),
288                type_key: key.clone(),
289                methods: vec![],
290                is_generic: false,
291            },
292        );
293        self.trait_impl_index.insert((trait_name, key), id);
294        id
295    }
296
297    /// Register an associated type binding for a given impl block.
298    ///
299    /// Overrides any existing binding for `(impl_id, name)`.
300    pub fn register_assoc_type(&mut self, impl_id: ImplId, name: impl Into<String>, ty: Type) {
301        self.assoc_types.insert((impl_id, name.into()), ty);
302    }
303
304    /// Look up an associated type binding by impl id and name.
305    ///
306    /// Returns `None` if no binding has been registered.
307    #[must_use]
308    pub fn resolve_assoc_type(&self, impl_id: ImplId, name: &str) -> Option<&Type> {
309        self.assoc_types.get(&(impl_id, name.to_owned()))
310    }
311
312    /// Return all supertraits of `trait_name`, collected transitively via BFS.
313    ///
314    /// The result list is in BFS order (direct supertraits before their
315    /// supertraits) with no duplicates. The original `trait_name` is **not**
316    /// included.
317    #[must_use]
318    pub fn all_supertraits(&self, trait_name: &str) -> Vec<String> {
319        let mut result = Vec::new();
320        let mut visited: HashSet<String> = HashSet::new();
321        let mut queue: VecDeque<String> = VecDeque::new();
322
323        if let Some(direct) = self.supertraits.get(trait_name) {
324            for st in direct {
325                if visited.insert(st.clone()) {
326                    queue.push_back(st.clone());
327                }
328            }
329        }
330
331        while let Some(name) = queue.pop_front() {
332            result.push(name.clone());
333            if let Some(supers) = self.supertraits.get(&name) {
334                for st in supers {
335                    if visited.insert(st.clone()) {
336                        queue.push_back(st.clone());
337                    }
338                }
339            }
340        }
341
342        result
343    }
344
345    /// Get the impl entry for an id.
346    #[must_use]
347    pub fn get_entry(&self, id: ImplId) -> Option<&ImplEntry> {
348        self.entries.get(&id)
349    }
350
351    /// Iterate over all registered entries.
352    pub fn entries(&self) -> impl Iterator<Item = &ImplEntry> {
353        self.entries.values()
354    }
355
356    // ── Internal lookup helpers ────────────────────────────────────────────────
357
358    fn find_trait_impl(&self, trait_name: &str, type_key: &str) -> Option<ImplId> {
359        self.trait_impl_index
360            .get(&(trait_name.to_owned(), type_key.to_owned()))
361            .copied()
362    }
363
364    fn find_inherent_impl(&self, type_key: &str) -> Option<ImplId> {
365        self.inherent_impl_index.get(type_key).copied()
366    }
367}
368
369impl Default for ImplTable {
370    fn default() -> Self {
371        Self::new()
372    }
373}
374
375// ─── Public resolution functions ──────────────────────────────────────────────
376
377/// Find the impl block that satisfies `trait_ref` for `ty` in `impls`.
378///
379/// Performs an exact-type lookup: the type key derived from `ty` must match
380/// the key stored when the impl was registered. Returns `None` if no impl
381/// exists for the `(trait, type)` pair.
382///
383/// To verify that supertrait obligations are also satisfied, call
384/// [`check_supertrait_obligations`] on the result.
385#[must_use]
386pub fn resolve_impl(trait_ref: &TraitRef, ty: &Type, impls: &ImplTable) -> Option<ImplId> {
387    let key = type_key(ty);
388    impls.find_trait_impl(&trait_ref.name, &key)
389}
390
391/// Check that all transitively required supertraits of `trait_ref` are
392/// satisfied by `ty` in `impls`.
393///
394/// Returns `true` if every supertrait reachable from `trait_ref` via the
395/// supertrait graph has a registered impl for `ty`.
396#[must_use]
397pub fn check_supertrait_obligations(trait_ref: &TraitRef, ty: &Type, impls: &ImplTable) -> bool {
398    let key = type_key(ty);
399    for supertrait in impls.all_supertraits(&trait_ref.name) {
400        if impls.find_trait_impl(&supertrait, &key).is_none() {
401            return false;
402        }
403    }
404    true
405}
406
407/// Dispatch a method call on `receiver` by searching `impls`.
408///
409/// Search order:
410/// 1. The inherent impl registered for `receiver`'s type key (if any).
411/// 2. All trait impls whose target type key matches `receiver`.
412///
413/// Returns the first match found, or `None` if no impl provides `method`.
414#[must_use]
415pub fn resolve_method(receiver: &Type, method: &str, impls: &ImplTable) -> Option<ResolvedMethod> {
416    let key = type_key(receiver);
417
418    // 1. Inherent impl.
419    if let Some(impl_id) = impls.find_inherent_impl(&key) {
420        if let Some(entry) = impls.get_entry(impl_id) {
421            if entry.methods.iter().any(|m| m == method) {
422                return Some(ResolvedMethod {
423                    impl_id,
424                    trait_ref: None,
425                    method: method.to_owned(),
426                });
427            }
428        }
429    }
430
431    // 2. Trait impls — iterate all entries whose type key matches.
432    for entry in impls.entries() {
433        if entry.type_key == key
434            && entry.trait_ref.is_some()
435            && entry.methods.iter().any(|m| m == method)
436        {
437            return Some(ResolvedMethod {
438                impl_id: entry.id,
439                trait_ref: entry.trait_ref.clone(),
440                method: method.to_owned(),
441            });
442        }
443    }
444
445    None
446}
447
448// ─── Key helpers ──────────────────────────────────────────────────────────────
449
450/// Produce a canonical string key for a [`Type`].
451///
452/// The key is used as the second component of the `(trait, type)` lookup in
453/// the [`ImplTable`]. It is deterministic and human-readable but is **not**
454/// intended as a user-facing display format.
455#[must_use]
456pub fn type_key(ty: &Type) -> String {
457    match ty {
458        Type::Primitive(p) => format!("{p:?}"),
459        Type::Named(n) => n.name.clone(),
460        Type::Generic(g) => {
461            let args = g.args.iter().map(type_key).collect::<Vec<_>>().join(", ");
462            format!("{}[{}]", g.constructor, args)
463        }
464        Type::Tuple(elems) => {
465            let elems = elems.iter().map(type_key).collect::<Vec<_>>().join(", ");
466            format!("({})", elems)
467        }
468        Type::Function(f) => {
469            let params = f.params.iter().map(type_key).collect::<Vec<_>>().join(", ");
470            format!("Fn({}) -> {}", params, type_key(&f.ret))
471        }
472        Type::Optional(inner) => format!("{}?", type_key(inner)),
473        Type::Result(ok, err) => format!("Result[{}, {}]", type_key(ok), type_key(err)),
474        Type::TypeVar(id) => format!("?{id}"),
475        Type::Refined(base, _) => type_key(base),
476        Type::Flexible(_) => "Flexible".to_string(),
477        Type::Error => "Error".to_string(),
478    }
479}
480
481/// Extract a canonical trait name string from a [`TypePath`].
482fn trait_name_from_path(path: &TypePath) -> String {
483    path.segments
484        .iter()
485        .map(|s| s.name.as_str())
486        .collect::<Vec<_>>()
487        .join(".")
488}
489
490/// Produce a canonical type key string from an AIR type-expression node.
491///
492/// Used during [`ImplTable::build_from`] to key the target of an impl block.
493fn type_key_from_node(node: &AIRNode) -> String {
494    match &node.kind {
495        NodeKind::TypeNamed { path, args } => {
496            let name = path
497                .segments
498                .iter()
499                .map(|s| s.name.as_str())
500                .collect::<Vec<_>>()
501                .join(".");
502            if args.is_empty() {
503                name
504            } else {
505                let arg_keys: Vec<_> = args.iter().map(type_key_from_node).collect();
506                format!("{}[{}]", name, arg_keys.join(", "))
507            }
508        }
509        NodeKind::TypeTuple { elems } => {
510            let elem_keys: Vec<_> = elems.iter().map(type_key_from_node).collect();
511            format!("({})", elem_keys.join(", "))
512        }
513        NodeKind::TypeOptional { inner } => format!("{}?", type_key_from_node(inner)),
514        NodeKind::TypeFunction { params, ret, .. } => {
515            let param_keys: Vec<_> = params.iter().map(type_key_from_node).collect();
516            format!(
517                "Fn({}) -> {}",
518                param_keys.join(", "),
519                type_key_from_node(ret)
520            )
521        }
522        NodeKind::TypeSelf => "Self".to_string(),
523        _ => "Unknown".to_string(),
524    }
525}
526
527/// Best-effort conversion of a type-expression AIR node to a [`Type`].
528///
529/// Used when extracting associated type bindings from impl body items.
530/// Unrecognised nodes produce [`Type::Error`].
531fn type_from_node(node: &AIRNode) -> Type {
532    match &node.kind {
533        NodeKind::TypeNamed { path, args } => {
534            let name = path
535                .segments
536                .iter()
537                .map(|s| s.name.as_str())
538                .collect::<Vec<_>>()
539                .join(".");
540            if args.is_empty() {
541                match name.as_str() {
542                    "Int" => Type::Primitive(PrimitiveType::Int),
543                    "Float" => Type::Primitive(PrimitiveType::Float),
544                    "Bool" => Type::Primitive(PrimitiveType::Bool),
545                    "String" => Type::Primitive(PrimitiveType::String),
546                    "Char" => Type::Primitive(PrimitiveType::Char),
547                    "Void" => Type::Primitive(PrimitiveType::Void),
548                    "Never" => Type::Primitive(PrimitiveType::Never),
549                    _ => Type::Named(NamedType { name }),
550                }
551            } else {
552                let type_args: Vec<_> = args.iter().map(type_from_node).collect();
553                Type::Generic(GenericType {
554                    constructor: name,
555                    args: type_args,
556                })
557            }
558        }
559        NodeKind::TypeOptional { inner } => Type::Optional(Box::new(type_from_node(inner))),
560        NodeKind::TypeTuple { elems } => Type::Tuple(elems.iter().map(type_from_node).collect()),
561        NodeKind::TypeSelf => Type::Named(NamedType {
562            name: "Self".to_owned(),
563        }),
564        _ => Type::Error,
565    }
566}
567
568// ─── Tests ────────────────────────────────────────────────────────────────────
569
570#[cfg(test)]
571mod tests {
572    use super::*;
573    use crate::{NamedType, PrimitiveType, Type};
574
575    // ── Helpers ───────────────────────────────────────────────────────────────
576
577    fn named(name: &str) -> Type {
578        Type::Named(NamedType {
579            name: name.to_owned(),
580        })
581    }
582
583    fn int() -> Type {
584        Type::Primitive(PrimitiveType::Int)
585    }
586
587    fn bool_ty() -> Type {
588        Type::Primitive(PrimitiveType::Bool)
589    }
590
591    fn dummy_span() -> bock_errors::Span {
592        use bock_errors::{FileId, Span};
593        Span {
594            file: FileId(0),
595            start: 0,
596            end: 0,
597        }
598    }
599
600    fn make_air_node(kind: NodeKind) -> AIRNode {
601        AIRNode::new(0, dummy_span(), kind)
602    }
603
604    fn make_module(items: Vec<AIRNode>) -> AIRNode {
605        make_air_node(NodeKind::Module {
606            path: None,
607            annotations: vec![],
608            imports: vec![],
609            items,
610        })
611    }
612
613    fn make_type_named(name: &str) -> AIRNode {
614        use bock_ast::{Ident, TypePath};
615        let ident = Ident {
616            name: name.to_owned(),
617            span: dummy_span(),
618        };
619        make_air_node(NodeKind::TypeNamed {
620            path: TypePath {
621                segments: vec![ident],
622                span: dummy_span(),
623            },
624            args: vec![],
625        })
626    }
627
628    fn make_fn_decl(name: &str) -> AIRNode {
629        use bock_ast::{Ident, Visibility};
630        let body = make_air_node(NodeKind::Block {
631            stmts: vec![],
632            tail: None,
633        });
634        make_air_node(NodeKind::FnDecl {
635            annotations: vec![],
636            visibility: Visibility::Private,
637            is_async: false,
638            name: Ident {
639                name: name.to_owned(),
640                span: dummy_span(),
641            },
642            generic_params: vec![],
643            params: vec![],
644            return_type: None,
645            effect_clause: vec![],
646            where_clause: vec![],
647            body: Box::new(body),
648        })
649    }
650
651    fn make_impl_block(
652        trait_name: Option<&str>,
653        target_name: &str,
654        methods: Vec<AIRNode>,
655    ) -> AIRNode {
656        use bock_ast::{Ident, TypePath};
657        let trait_path = trait_name.map(|n| TypePath {
658            segments: vec![Ident {
659                name: n.to_owned(),
660                span: dummy_span(),
661            }],
662            span: dummy_span(),
663        });
664        make_air_node(NodeKind::ImplBlock {
665            annotations: vec![],
666            generic_params: vec![],
667            trait_path,
668            target: Box::new(make_type_named(target_name)),
669            where_clause: vec![],
670            methods,
671        })
672    }
673
674    // ── type_key ──────────────────────────────────────────────────────────────
675
676    #[test]
677    fn type_key_primitive() {
678        assert_eq!(type_key(&int()), "Int");
679        assert_eq!(type_key(&bool_ty()), "Bool");
680    }
681
682    #[test]
683    fn type_key_named() {
684        assert_eq!(type_key(&named("User")), "User");
685    }
686
687    #[test]
688    fn type_key_generic() {
689        use crate::GenericType;
690        let ty = Type::Generic(GenericType {
691            constructor: "List".to_owned(),
692            args: vec![int()],
693        });
694        assert_eq!(type_key(&ty), "List[Int]");
695    }
696
697    #[test]
698    fn type_key_optional() {
699        assert_eq!(type_key(&Type::Optional(Box::new(int()))), "Int?");
700    }
701
702    #[test]
703    fn type_key_result() {
704        assert_eq!(
705            type_key(&Type::Result(Box::new(int()), Box::new(named("Err")))),
706            "Result[Int, Err]"
707        );
708    }
709
710    // ── ImplTable construction ─────────────────────────────────────────────────
711
712    #[test]
713    fn build_empty_module() {
714        let module = make_module(vec![]);
715        let table = ImplTable::build_from(&module);
716        assert!(!table.diags.has_errors());
717        assert_eq!(table.entries.len(), 0);
718    }
719
720    #[test]
721    fn build_registers_trait_impl() {
722        let eq_method = make_fn_decl("equals");
723        let impl_block = make_impl_block(Some("Equatable"), "User", vec![eq_method]);
724        let module = make_module(vec![impl_block]);
725        let table = ImplTable::build_from(&module);
726        assert!(!table.diags.has_errors());
727        let id = resolve_impl(&TraitRef::new("Equatable"), &named("User"), &table);
728        assert!(id.is_some());
729    }
730
731    #[test]
732    fn build_registers_inherent_impl() {
733        let method = make_fn_decl("greet");
734        let impl_block = make_impl_block(None, "User", vec![method]);
735        let module = make_module(vec![impl_block]);
736        let table = ImplTable::build_from(&module);
737        let result = resolve_method(&named("User"), "greet", &table);
738        assert!(result.is_some());
739        let r = result.unwrap();
740        assert!(r.trait_ref.is_none());
741        assert_eq!(r.method, "greet");
742    }
743
744    // ── resolve_impl ──────────────────────────────────────────────────────────
745
746    #[test]
747    fn resolve_impl_found() {
748        let mut table = ImplTable::new();
749        let id = table.alloc_id();
750        table.entries.insert(
751            id,
752            ImplEntry {
753                id,
754                trait_ref: Some(TraitRef::new("Printable")),
755                type_key: "Int".to_owned(),
756                methods: vec!["print".to_owned()],
757                is_generic: false,
758            },
759        );
760        table
761            .trait_impl_index
762            .insert(("Printable".to_owned(), "Int".to_owned()), id);
763
764        assert_eq!(
765            resolve_impl(&TraitRef::new("Printable"), &int(), &table),
766            Some(id)
767        );
768    }
769
770    #[test]
771    fn resolve_impl_not_found() {
772        let table = ImplTable::new();
773        assert_eq!(
774            resolve_impl(&TraitRef::new("Printable"), &int(), &table),
775            None
776        );
777    }
778
779    #[test]
780    fn resolve_impl_wrong_type() {
781        let mut table = ImplTable::new();
782        let id = table.alloc_id();
783        table.entries.insert(
784            id,
785            ImplEntry {
786                id,
787                trait_ref: Some(TraitRef::new("Printable")),
788                type_key: "Int".to_owned(),
789                methods: vec!["print".to_owned()],
790                is_generic: false,
791            },
792        );
793        table
794            .trait_impl_index
795            .insert(("Printable".to_owned(), "Int".to_owned()), id);
796
797        // Bool does not implement Printable.
798        assert_eq!(
799            resolve_impl(&TraitRef::new("Printable"), &bool_ty(), &table),
800            None
801        );
802    }
803
804    // ── resolve_method ────────────────────────────────────────────────────────
805
806    #[test]
807    fn resolve_method_inherent() {
808        let method = make_fn_decl("to_string");
809        let impl_block = make_impl_block(None, "User", vec![method]);
810        let module = make_module(vec![impl_block]);
811        let table = ImplTable::build_from(&module);
812
813        let r = resolve_method(&named("User"), "to_string", &table);
814        assert!(r.is_some());
815        let r = r.unwrap();
816        assert!(r.trait_ref.is_none());
817        assert_eq!(r.method, "to_string");
818    }
819
820    #[test]
821    fn resolve_method_from_trait_impl() {
822        let method = make_fn_decl("serialize");
823        let impl_block = make_impl_block(Some("Serializable"), "User", vec![method]);
824        let module = make_module(vec![impl_block]);
825        let table = ImplTable::build_from(&module);
826
827        let r = resolve_method(&named("User"), "serialize", &table);
828        assert!(r.is_some());
829        let r = r.unwrap();
830        assert_eq!(
831            r.trait_ref.as_ref().map(|t| t.name.as_str()),
832            Some("Serializable")
833        );
834        assert_eq!(r.method, "serialize");
835    }
836
837    #[test]
838    fn resolve_method_not_found() {
839        let table = ImplTable::new();
840        assert!(resolve_method(&int(), "foo", &table).is_none());
841    }
842
843    #[test]
844    fn resolve_method_inherent_takes_priority_over_trait() {
845        let inherent_method = make_fn_decl("display");
846        let trait_method = make_fn_decl("display");
847        let inherent_impl = make_impl_block(None, "User", vec![inherent_method]);
848        let trait_impl = make_impl_block(Some("Display"), "User", vec![trait_method]);
849        let module = make_module(vec![inherent_impl, trait_impl]);
850        let table = ImplTable::build_from(&module);
851
852        let r = resolve_method(&named("User"), "display", &table).unwrap();
853        // Inherent impl has priority — no trait_ref.
854        assert!(r.trait_ref.is_none());
855    }
856
857    // ── Coherence ─────────────────────────────────────────────────────────────
858
859    #[test]
860    fn coherence_detects_exact_overlap() {
861        let impl1 = make_impl_block(Some("Equatable"), "Point", vec![make_fn_decl("equals")]);
862        let impl2 = make_impl_block(Some("Equatable"), "Point", vec![make_fn_decl("equals")]);
863        let module = make_module(vec![impl1, impl2]);
864        let table = ImplTable::build_from(&module);
865
866        assert!(table.diags.has_errors());
867        assert_eq!(table.diags.error_count(), 1);
868    }
869
870    #[test]
871    fn coherence_allows_different_types() {
872        let impl1 = make_impl_block(Some("Equatable"), "Int", vec![make_fn_decl("equals")]);
873        let impl2 = make_impl_block(Some("Equatable"), "Bool", vec![make_fn_decl("equals")]);
874        let module = make_module(vec![impl1, impl2]);
875        let table = ImplTable::build_from(&module);
876
877        assert!(!table.diags.has_errors());
878    }
879
880    #[test]
881    fn coherence_allows_different_traits() {
882        let impl1 = make_impl_block(Some("Equatable"), "Point", vec![make_fn_decl("equals")]);
883        let impl2 = make_impl_block(Some("Comparable"), "Point", vec![make_fn_decl("compare")]);
884        let module = make_module(vec![impl1, impl2]);
885        let table = ImplTable::build_from(&module);
886
887        assert!(!table.diags.has_errors());
888    }
889
890    #[test]
891    fn coherence_skips_generic_impls() {
892        use bock_ast::{GenericParam, Ident, TypePath};
893
894        let generic_param = GenericParam {
895            id: 0,
896            span: dummy_span(),
897            name: Ident {
898                name: "T".to_owned(),
899                span: dummy_span(),
900            },
901            bounds: vec![],
902        };
903        let impl1 = make_air_node(NodeKind::ImplBlock {
904            annotations: vec![],
905            generic_params: vec![generic_param.clone()],
906            trait_path: Some(TypePath {
907                segments: vec![Ident {
908                    name: "Printable".to_owned(),
909                    span: dummy_span(),
910                }],
911                span: dummy_span(),
912            }),
913            target: Box::new(make_type_named("T")),
914            where_clause: vec![],
915            methods: vec![],
916        });
917        let impl2 = make_air_node(NodeKind::ImplBlock {
918            annotations: vec![],
919            generic_params: vec![generic_param],
920            trait_path: Some(TypePath {
921                segments: vec![Ident {
922                    name: "Printable".to_owned(),
923                    span: dummy_span(),
924                }],
925                span: dummy_span(),
926            }),
927            target: Box::new(make_type_named("T")),
928            where_clause: vec![],
929            methods: vec![],
930        });
931        let module = make_module(vec![impl1, impl2]);
932        let table = ImplTable::build_from(&module);
933
934        // Generic impls are exempt from exact-type coherence.
935        assert!(!table.diags.has_errors());
936    }
937
938    // ── Supertrait obligations ─────────────────────────────────────────────────
939
940    #[test]
941    fn supertrait_registration_and_lookup() {
942        let mut table = ImplTable::new();
943        table.register_supertrait("Hashable", "Equatable");
944        let supers = table.all_supertraits("Hashable");
945        assert_eq!(supers, vec!["Equatable"]);
946    }
947
948    #[test]
949    fn supertrait_transitive() {
950        let mut table = ImplTable::new();
951        table.register_supertrait("C", "B");
952        table.register_supertrait("B", "A");
953        let supers = table.all_supertraits("C");
954        assert_eq!(supers, vec!["B", "A"]);
955    }
956
957    #[test]
958    fn check_supertrait_obligations_satisfied() {
959        let mut table = ImplTable::new();
960        table.register_supertrait("Hashable", "Equatable");
961
962        // Register impls for both Equatable and Hashable on User.
963        let eq_method = make_fn_decl("equals");
964        let hash_method = make_fn_decl("hash");
965        let eq_impl = make_impl_block(Some("Equatable"), "User", vec![eq_method]);
966        let hash_impl = make_impl_block(Some("Hashable"), "User", vec![hash_method]);
967        let module = make_module(vec![eq_impl, hash_impl]);
968        let table_built = ImplTable::build_from(&module);
969
970        // Manually merge supertrait registration into built table.
971        let mut full_table = table_built;
972        full_table.register_supertrait("Hashable", "Equatable");
973
974        assert!(check_supertrait_obligations(
975            &TraitRef::new("Hashable"),
976            &named("User"),
977            &full_table
978        ));
979    }
980
981    #[test]
982    fn check_supertrait_obligations_missing() {
983        let mut table = ImplTable::new();
984        table.register_supertrait("Hashable", "Equatable");
985
986        // Only Hashable impl, missing Equatable.
987        let hash_method = make_fn_decl("hash");
988        let hash_impl = make_impl_block(Some("Hashable"), "User", vec![hash_method]);
989        let module = make_module(vec![hash_impl]);
990        let table_built = ImplTable::build_from(&module);
991        let mut full_table = table_built;
992        full_table.register_supertrait("Hashable", "Equatable");
993
994        // Equatable supertrait is not satisfied.
995        assert!(!check_supertrait_obligations(
996            &TraitRef::new("Hashable"),
997            &named("User"),
998            &full_table
999        ));
1000    }
1001
1002    // ── Associated types ───────────────────────────────────────────────────────
1003
1004    #[test]
1005    fn assoc_type_manual_registration() {
1006        let mut table = ImplTable::new();
1007        let impl_id = table.alloc_id();
1008        table.register_assoc_type(impl_id, "Item", int());
1009
1010        assert_eq!(table.resolve_assoc_type(impl_id, "Item"), Some(&int()));
1011        assert_eq!(table.resolve_assoc_type(impl_id, "Missing"), None);
1012    }
1013
1014    #[test]
1015    fn assoc_type_not_found_for_other_impl() {
1016        let mut table = ImplTable::new();
1017        let id1 = table.alloc_id();
1018        let id2 = table.alloc_id();
1019        table.register_assoc_type(id1, "Item", int());
1020
1021        assert_eq!(table.resolve_assoc_type(id2, "Item"), None);
1022    }
1023
1024    // ── Method dispatch on generic types ──────────────────────────────────────
1025
1026    #[test]
1027    fn resolve_method_on_generic_type() {
1028        let method = make_fn_decl("push");
1029        let impl_block = make_impl_block(None, "List", vec![method]);
1030        let module = make_module(vec![impl_block]);
1031        let table = ImplTable::build_from(&module);
1032
1033        // The inherent impl is registered under the key "List" (no type args in
1034        // the target node). A receiver of List[Int] won't match by key since its
1035        // key is "List[Int]", but a plain Named("List") will.
1036        let receiver = Type::Named(NamedType {
1037            name: "List".to_owned(),
1038        });
1039        let r = resolve_method(&receiver, "push", &table);
1040        assert!(r.is_some());
1041    }
1042}