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/// `E4011` — a user `impl` tries to implement a sealed core trait for a
38/// primitive type (orphan-rule violation). See [`SEALED_CORE_TRAITS`].
39const E_SEALED_PRIMITIVE_IMPL: DiagnosticCode = DiagnosticCode {
40    prefix: 'E',
41    number: 4011,
42};
43
44/// `E4012` — the single-method-namespace rule (DQ27) is violated: a method name
45/// is defined more than once for one type, across any combination of inherent
46/// `impl T {}`, `class T {}` body, and `impl Trait for T {}` blocks. A type has
47/// exactly one method namespace keyed by method name; a trait requirement is
48/// satisfied by a name+signature match *anywhere* in that namespace, so the
49/// same name cannot be defined twice. See spec §6.4/§6.5/§6.7.
50const E_DUPLICATE_METHOD: DiagnosticCode = DiagnosticCode {
51    prefix: 'E',
52    number: 4012,
53};
54
55// ─── Core types ───────────────────────────────────────────────────────────────
56
57/// Unique identifier for a registered impl block.
58pub type ImplId = u32;
59
60/// A reference to a named trait, identified by its fully-qualified name.
61///
62/// Examples: `"Equatable"`, `"Std.Io.Writable"`.
63#[derive(Debug, Clone, PartialEq)]
64pub struct TraitRef {
65    /// Fully-qualified name of the trait.
66    pub name: String,
67    /// Type arguments applied to a parameterized trait, e.g. `[Int]` in
68    /// `From[Int]`. Empty for non-parameterized traits.
69    pub args: Vec<Type>,
70}
71
72impl TraitRef {
73    /// Create a `TraitRef` for a non-parameterized trait from any string-like
74    /// value. The argument list is empty.
75    #[must_use]
76    pub fn new(name: impl Into<String>) -> Self {
77        Self {
78            name: name.into(),
79            args: vec![],
80        }
81    }
82
83    /// Create a `TraitRef` for a parameterized trait, e.g.
84    /// `TraitRef::parameterized("From", vec![Type::Primitive(Int)])` for
85    /// `From[Int]`.
86    #[must_use]
87    pub fn parameterized(name: impl Into<String>, args: Vec<Type>) -> Self {
88        Self {
89            name: name.into(),
90            args,
91        }
92    }
93
94    fn from_path(path: &TypePath) -> Self {
95        let name = path
96            .segments
97            .iter()
98            .map(|s| s.name.as_str())
99            .collect::<Vec<_>>()
100            .join(".");
101        Self { name, args: vec![] }
102    }
103}
104
105/// The result of a successful method dispatch via [`resolve_method`].
106#[derive(Debug, Clone)]
107pub struct ResolvedMethod {
108    /// The impl block that provides this method.
109    pub impl_id: ImplId,
110    /// The trait this method comes from, or `None` for inherent impls.
111    pub trait_ref: Option<TraitRef>,
112    /// The resolved method name.
113    pub method: String,
114}
115
116// ─── ImplTable ────────────────────────────────────────────────────────────────
117
118/// A record of a single impl block registered in the [`ImplTable`].
119#[derive(Debug, Clone)]
120pub struct ImplEntry {
121    /// Unique id allocated for this impl block.
122    pub id: ImplId,
123    /// Trait being implemented, or `None` for an inherent impl.
124    pub trait_ref: Option<TraitRef>,
125    /// Canonical string key for the target type (see [`type_key`]).
126    pub type_key: String,
127    /// Names of the methods provided by this impl.
128    pub methods: Vec<String>,
129    /// `true` when the impl has at least one generic type parameter.
130    ///
131    /// Generic impls are registered but skipped during coherence checking.
132    pub is_generic: bool,
133    /// `true` when this entry was registered by the compiler as a canonical
134    /// conformance for a primitive type (see
135    /// [`register_canonical_conformances`]). User `impl` blocks always set
136    /// this to `false`. Canonical entries are sealed: user code may not add
137    /// its own `impl` of a core trait for a primitive (`E4011`).
138    pub is_canonical: bool,
139    /// Type arguments applied to the implemented trait, e.g. `[Int]` in
140    /// `impl From[Int] for Float`. Empty for non-parameterized traits.
141    pub trait_args: Vec<Type>,
142    /// `true` when this entry was synthesized by the compiler from another
143    /// impl rather than written by the user — e.g. the blanket
144    /// `Into[U] for T` derived from an explicit `From[T] for U`. A derived
145    /// entry never wins over an explicit one and never triggers a coherence
146    /// error against an explicit impl.
147    pub is_derived: bool,
148    /// The structured target [`Type`] of the impl, when it could be resolved
149    /// from the AIR target node. Used to synthesize blanket reverse impls
150    /// (e.g. deriving `Into[U]` from `From[T] for U`). `None` for entries
151    /// built from an unrecognized target node.
152    pub target_type: Option<Type>,
153}
154
155/// Maps `(TraitRef, Type)` pairs to impl blocks and supports method dispatch.
156///
157/// # Construction
158///
159/// ```ignore
160/// let table = ImplTable::build_from(&air_module_node);
161/// // inspect table.diags for coherence errors
162/// ```
163///
164/// # Querying
165///
166/// ```ignore
167/// let impl_id = resolve_impl(&TraitRef::new("Equatable"), &ty, &table)?;
168/// let method  = resolve_method(&receiver_ty, "equals", &table)?;
169/// ```
170pub struct ImplTable {
171    /// All registered impl entries indexed by [`ImplId`].
172    entries: HashMap<ImplId, ImplEntry>,
173    /// Trait impl index: `(trait_name, type_key) → ImplId` (concrete,
174    /// non-parameterized impls only). Untouched by parameterized-trait support
175    /// so all pre-existing (Q-bridge) behavior is bit-identical.
176    trait_impl_index: HashMap<(String, String), ImplId>,
177    /// Parameterized trait impl index:
178    /// `(trait_name, trait_arg_key, target_type_key) → ImplId`. Used for traits
179    /// that carry type arguments, e.g. `From[Int] for Float`. Keyed on the
180    /// three-tuple so `From[Int] for Float` and `From[String] for Float`
181    /// coexist without a coherence collision.
182    param_trait_impl_index: HashMap<(String, String, String), ImplId>,
183    /// Inherent impl index: `type_key → ImplId`.
184    inherent_impl_index: HashMap<String, ImplId>,
185    /// Supertrait graph: `trait_name → list of direct supertrait names`.
186    supertraits: HashMap<String, Vec<String>>,
187    /// Associated type bindings: `(ImplId, assoc_type_name) → Type`.
188    assoc_types: HashMap<(ImplId, String), Type>,
189    /// Monotonically increasing id counter.
190    next_id: u32,
191    /// Diagnostics emitted during construction (coherence errors use `E4010`).
192    pub diags: DiagnosticBag,
193    /// Per-type record of every method *definition* seen while building the
194    /// table, keyed by the target type key. Each entry captures the method's
195    /// name, a structural signature key, its span, and the block it came from.
196    /// Used after the visit pass to enforce the single-method-namespace rule
197    /// (DQ27): defining a method name more than once for one type is an
198    /// `E4012` coherence error, regardless of which blocks the definitions
199    /// appear in. Drained into [`Self::diags`] by [`Self::check_method_namespace`].
200    method_defs: HashMap<String, Vec<MethodDef>>,
201}
202
203/// A single method definition seen during [`ImplTable::build_from`], retained
204/// only long enough to run the single-method-namespace coherence check.
205struct MethodDef {
206    /// The method's name (the namespace key within a type).
207    name: String,
208    /// A structural key for the method's signature (param + return shape),
209    /// used to distinguish a true duplicate (matching signature) from two
210    /// genuinely-conflicting requirements (differing signatures).
211    sig_key: String,
212    /// Span of the method's `fn` declaration, for the diagnostic.
213    span: bock_errors::Span,
214    /// Human-readable description of the block the method was declared in,
215    /// e.g. `"inherent impl"`, `"class body"`, or `"impl Component for ..."`.
216    origin: String,
217}
218
219impl ImplTable {
220    /// Create a new, empty `ImplTable`.
221    #[must_use]
222    pub fn new() -> Self {
223        Self {
224            entries: HashMap::new(),
225            trait_impl_index: HashMap::new(),
226            param_trait_impl_index: HashMap::new(),
227            inherent_impl_index: HashMap::new(),
228            supertraits: HashMap::new(),
229            assoc_types: HashMap::new(),
230            next_id: 0,
231            diags: DiagnosticBag::new(),
232            method_defs: HashMap::new(),
233        }
234    }
235
236    /// Build an `ImplTable` by walking the top-level items of an AIR module node.
237    ///
238    /// The node must have `NodeKind::Module`; other node kinds produce an empty
239    /// table. Coherence checking (exact-type `impl` overlap detection) runs
240    /// during construction and emits `E4010` diagnostics for any duplicate
241    /// `(Trait, Type)` pair.
242    #[must_use]
243    pub fn build_from(module: &AIRNode) -> Self {
244        let mut table = Self::new();
245        if let NodeKind::Module { items, .. } = &module.kind {
246            for item in items {
247                table.visit_item(item);
248            }
249        }
250        // Single-method-namespace coherence (DQ27): every method that applies
251        // to a type — inherent, class-body, or trait-impl — shares one
252        // namespace keyed by name. Defining a name twice for one type is an
253        // `E4012` error. Run after all items are visited so the check sees the
254        // type's whole namespace.
255        table.check_method_namespace();
256        // Second pass: synthesize blanket `Into[U] for T` from each explicit
257        // `From[T] for U`. Runs after all explicit impls so an explicit
258        // `Into` always wins (the synthesized entry is skipped if its slot
259        // is occupied — see `synthesize_blanket_into`).
260        table.synthesize_blanket_into();
261        table
262    }
263
264    /// Enforce the single-method-namespace rule (DQ27) across every block that
265    /// contributes methods to a type.
266    ///
267    /// A type — `record` or `class` — has exactly one method namespace keyed by
268    /// method name. Methods declared in an inherent `impl T {}`, a `class T {}`
269    /// body, or any `impl Trait for T {}` block all share that namespace. A
270    /// trait requirement is satisfied by a name+signature match *anywhere* in
271    /// the namespace (so an empty `impl Trait for T {}` is well-formed when an
272    /// inherent/class-body method already provides the required method), which
273    /// in turn means a name cannot be defined twice for one type:
274    ///
275    /// - Two definitions with **matching signatures** are a redundant
276    ///   duplicate (e.g. an inherent `render` plus a trait-impl `render`
277    ///   forwarder) — the classic react-components collision.
278    /// - Two definitions with **differing signatures** (e.g. two traits that
279    ///   both require a `foo` with incompatible signatures) are genuinely
280    ///   unsatisfiable on the v1 targets, which have one method slot per name.
281    ///
282    /// Both are reported as `E4012`. The message distinguishes the two cases so
283    /// the user knows whether to delete a redundant definition or rename.
284    fn check_method_namespace(&mut self) {
285        // Stable ordering for deterministic diagnostics across runs.
286        let mut type_keys: Vec<&String> = self.method_defs.keys().collect();
287        type_keys.sort();
288        let type_keys: Vec<String> = type_keys.into_iter().cloned().collect();
289
290        for type_key in type_keys {
291            let defs = &self.method_defs[&type_key];
292            // Group definition indices by method name, preserving declaration
293            // order so the *first* definition is treated as canonical and
294            // later ones are flagged.
295            let mut by_name: HashMap<&str, Vec<usize>> = HashMap::new();
296            for (idx, def) in defs.iter().enumerate() {
297                by_name.entry(def.name.as_str()).or_default().push(idx);
298            }
299            let mut names: Vec<&str> = by_name.keys().copied().collect();
300            names.sort();
301
302            for name in names {
303                let indices = &by_name[name];
304                if indices.len() < 2 {
305                    continue;
306                }
307                let first = &defs[indices[0]];
308                for &dup_idx in &indices[1..] {
309                    let dup = &defs[dup_idx];
310                    let same_sig = dup.sig_key == first.sig_key;
311                    let detail = if same_sig {
312                        format!(
313                            "a method named `{name}` is already defined for type `{type_key}` \
314                             in the {}; a type has one method namespace, so the same method \
315                             may not be defined twice",
316                            first.origin,
317                        )
318                    } else {
319                        format!(
320                            "method `{name}` is defined for type `{type_key}` with conflicting \
321                             signatures (in the {} and the {}); a type has one method slot per \
322                             name and cannot satisfy two requirements with incompatible signatures",
323                            first.origin, dup.origin,
324                        )
325                    };
326                    self.diags
327                        .error(E_DUPLICATE_METHOD, detail, dup.span)
328                        .note(format!(
329                            "a trait requirement is satisfied by a matching method anywhere in \
330                             the type's namespace; if `{name}` should satisfy a trait, define it \
331                             once (as an inherent/class-body method or inside the trait impl) and \
332                             leave the other block empty",
333                        ));
334                }
335            }
336        }
337    }
338
339    /// Record a method definition against its target type for the
340    /// single-method-namespace check. Called from [`Self::visit_item`] for
341    /// inherent/trait `impl` blocks and `class` bodies.
342    fn record_method_def(&mut self, type_key: &str, method: &AIRNode, origin: &str) {
343        if let NodeKind::FnDecl { name, .. } = &method.kind {
344            self.method_defs
345                .entry(type_key.to_owned())
346                .or_default()
347                .push(MethodDef {
348                    name: name.name.clone(),
349                    sig_key: method_sig_key(method),
350                    span: method.span,
351                    origin: origin.to_owned(),
352                });
353        }
354    }
355
356    /// For every explicit `impl From[T] for U`, synthesize the blanket reverse
357    /// `impl Into[U] for T`, marked `is_derived`.
358    ///
359    /// Skips synthesis when the `Into[U] for T` slot is already occupied — an
360    /// explicit `Into` impl always wins and a derived entry never triggers an
361    /// `E4010` coherence error against an explicit impl. Only `From` impls
362    /// with exactly one trait argument participate (the v1 surface);
363    /// `TryFrom` is intentionally NOT blanket-reversed.
364    fn synthesize_blanket_into(&mut self) {
365        // Collect first to avoid mutating while iterating.
366        let froms: Vec<(Type, Type)> = self
367            .entries
368            .values()
369            .filter_map(|e| {
370                let tr = e.trait_ref.as_ref()?;
371                if tr.name != "From" || tr.args.len() != 1 || e.is_generic {
372                    return None;
373                }
374                // From[T] for U  →  source T = tr.args[0], target U = e.type_key's type.
375                let source = tr.args[0].clone();
376                let target = e.target_type.clone()?;
377                Some((source, target))
378            })
379            .collect();
380
381        for (source, target) in froms {
382            // Reverse: Into[target] for source.
383            let into_arg_key = trait_arg_key(std::slice::from_ref(&target));
384            let into_target_key = type_key(&source);
385            let occupied = self.param_trait_impl_index.contains_key(&(
386                "Into".to_owned(),
387                into_arg_key,
388                into_target_key,
389            ));
390            if occupied {
391                // Explicit (or already-derived) Into wins — never clobber.
392                continue;
393            }
394            self.register_param_trait_impl("Into", std::slice::from_ref(&target), &source, true);
395        }
396    }
397
398    fn visit_item(&mut self, node: &AIRNode) {
399        match &node.kind {
400            NodeKind::ImplBlock {
401                trait_path,
402                trait_args,
403                target,
404                methods,
405                generic_params,
406                ..
407            } => {
408                // Resolve the trait's type arguments (e.g. `[Int]` in
409                // `impl From[Int] for Float`) into `Type`s, and build a
410                // parameterized `TraitRef` when present.
411                let resolved_trait_args: Vec<Type> =
412                    trait_args.iter().map(type_from_node).collect();
413                let trait_ref = trait_path.as_ref().map(|p| {
414                    let mut tr = TraitRef::from_path(p);
415                    tr.args = resolved_trait_args.clone();
416                    tr
417                });
418                let type_key = type_key_from_node(target);
419                let is_generic = !generic_params.is_empty();
420
421                // Sealing (Q1b): a *user* `impl <CoreTrait> for <Primitive>` is
422                // an orphan-rule violation — core traits have sealed,
423                // compiler-provided conformances for primitives. The newtype
424                // pattern is the escape hatch. Scoped strictly to the (core
425                // trait, primitive) quadrant; this is NOT a general orphan
426                // model. The compiler's own canonical conformances are added
427                // later via `register_trait_impl_inner`, which bypasses this
428                // check, so they are never rejected.
429                if let Some(tr) = &trait_ref {
430                    if SEALED_CORE_TRAITS.contains(&tr.name.as_str())
431                        && SEALED_PRIMITIVE_KEYS.contains(&type_key.as_str())
432                    {
433                        self.diags
434                            .error(
435                                E_SEALED_PRIMITIVE_IMPL,
436                                format!(
437                                    "cannot implement core trait `{}` for primitive type                                      `{}`: its conformance is provided by the compiler and                                      is sealed",
438                                    tr.name, type_key,
439                                ),
440                                node.span,
441                            )
442                            .note(format!(
443                                "wrap `{type_key}` in a newtype (e.g. `record My{type_key}                                  {{ value: {type_key} }}`) and implement `{}` for that instead",
444                                tr.name,
445                            ));
446                        return;
447                    }
448                }
449
450                // Coherence: detect exact-type duplicates (skip generic impls).
451                // Parameterized traits key on the (trait, trait-args, target)
452                // three-tuple, so `From[Int] for Float` and
453                // `From[String] for Float` do not collide; non-parameterized
454                // traits use the original two-tuple index (bit-identical to
455                // the Q-bridge behavior).
456                if !is_generic {
457                    if let Some(tr) = &trait_ref {
458                        if tr.args.is_empty() {
459                            let index_key = (tr.name.clone(), type_key.clone());
460                            if self.trait_impl_index.contains_key(&index_key) {
461                                self.diags.error(
462                                    E_COHERENCE_OVERLAP,
463                                    format!(
464                                        "conflicting implementations of trait `{}` for type `{}`",
465                                        tr.name, type_key,
466                                    ),
467                                    node.span,
468                                );
469                                return;
470                            }
471                        } else {
472                            let arg_key = trait_arg_key(&tr.args);
473                            let index_key = (tr.name.clone(), arg_key.clone(), type_key.clone());
474                            if self.param_trait_impl_index.contains_key(&index_key) {
475                                self.diags.error(
476                                    E_COHERENCE_OVERLAP,
477                                    format!(
478                                        "conflicting implementations of trait `{}[{}]` for type `{}`",
479                                        tr.name, arg_key, type_key,
480                                    ),
481                                    node.span,
482                                );
483                                return;
484                            }
485                        }
486                    }
487                }
488
489                let id = self.alloc_id();
490
491                // Human-readable origin used by the single-method-namespace
492                // check's diagnostics (DQ27): inherent vs `impl Trait for T`.
493                let origin = match &trait_ref {
494                    Some(tr) => format!("`impl {} for {}` block", tr.name, type_key),
495                    None => format!("inherent `impl {type_key}` block"),
496                };
497
498                // The single-method-namespace check (DQ27) governs methods that
499                // are dispatched by name (`x.foo()`). It excludes:
500                //   * parameterized trait impls (`From[Int]`, `From[String]`,
501                //     `Into[U]`): each instantiation legitimately carries the
502                //     same method name (`from`/`into`) and is selected by the
503                //     trait argument through the parameterized index, never as a
504                //     bare `x.from()` collision; and
505                //   * generic/blanket impls (`impl[T] Foo[T]`), which are also
506                //     exempt from the `E4010` exact-overlap check.
507                let trait_is_parameterized =
508                    trait_ref.as_ref().is_some_and(|tr| !tr.args.is_empty());
509                let track_namespace = !is_generic && !trait_is_parameterized;
510
511                // Collect method names, and register any associated type aliases.
512                let mut method_names = Vec::new();
513                for m in methods {
514                    match &m.kind {
515                        NodeKind::FnDecl { name, .. } => {
516                            method_names.push(name.name.clone());
517                            if track_namespace {
518                                self.record_method_def(&type_key, m, &origin);
519                            }
520                        }
521                        NodeKind::TypeAlias { name, ty, .. } => {
522                            // Associated type binding: `type Assoc = ConcreteType`.
523                            let resolved = type_from_node(ty);
524                            self.assoc_types.insert((id, name.name.clone()), resolved);
525                        }
526                        _ => {}
527                    }
528                }
529
530                // Register in the appropriate index.
531                if let Some(tr) = &trait_ref {
532                    if !is_generic {
533                        if tr.args.is_empty() {
534                            self.trait_impl_index
535                                .insert((tr.name.clone(), type_key.clone()), id);
536                        } else {
537                            self.param_trait_impl_index.insert(
538                                (tr.name.clone(), trait_arg_key(&tr.args), type_key.clone()),
539                                id,
540                            );
541                        }
542                    }
543                } else {
544                    // Inherent impl — last registration wins for the type key.
545                    self.inherent_impl_index.insert(type_key.clone(), id);
546                }
547
548                let target_type = Some(type_from_node(target));
549                self.entries.insert(
550                    id,
551                    ImplEntry {
552                        id,
553                        trait_ref,
554                        type_key,
555                        methods: method_names,
556                        is_generic,
557                        is_canonical: false,
558                        trait_args: resolved_trait_args,
559                        is_derived: false,
560                        target_type,
561                    },
562                );
563            }
564
565            NodeKind::TraitDecl {
566                name,
567                generic_params,
568                ..
569            } => {
570                // Extract supertrait bounds: any bound on a `Self` generic param
571                // is treated as a supertrait requirement.
572                for param in generic_params {
573                    if param.name.name == "Self" {
574                        for bound in &param.bounds {
575                            let supertrait = trait_name_from_path(bound);
576                            self.register_supertrait(name.name.clone(), supertrait);
577                        }
578                    }
579                }
580            }
581
582            NodeKind::ClassDecl { name, methods, .. } => {
583                // Class-body methods share the type's single method namespace
584                // (DQ27), so they participate in the duplicate-method check
585                // alongside inherent and trait-impl methods. (Class bodies are
586                // not registered into the impl indices here — method dispatch
587                // for class bodies is handled in the checker — but they must be
588                // visible to the namespace coherence check.)
589                let class_key = name.name.clone();
590                let origin = format!("`class {class_key}` body");
591                for m in methods {
592                    if matches!(m.kind, NodeKind::FnDecl { .. }) {
593                        self.record_method_def(&class_key, m, &origin);
594                    }
595                }
596            }
597
598            _ => {}
599        }
600    }
601
602    fn alloc_id(&mut self) -> ImplId {
603        let id = self.next_id;
604        self.next_id += 1;
605        id
606    }
607
608    /// Register a direct supertrait relationship: every impl of `sub_trait`
609    /// must also impl `super_trait`.
610    pub fn register_supertrait(
611        &mut self,
612        sub_trait: impl Into<String>,
613        super_trait: impl Into<String>,
614    ) {
615        self.supertraits
616            .entry(sub_trait.into())
617            .or_default()
618            .push(super_trait.into());
619    }
620
621    /// Programmatically register a trait impl for a concrete type.
622    ///
623    /// This is a convenience method for tests and downstream passes that need
624    /// to populate the table without building from AIR nodes. The registered
625    /// entry is *not* marked canonical; use
626    /// [`register_canonical_conformances`] for the compiler-provided
627    /// primitive conformances.
628    pub fn register_trait_impl(&mut self, trait_name: impl Into<String>, ty: &Type) -> ImplId {
629        self.register_trait_impl_inner(trait_name, &[], ty, false, false)
630    }
631
632    /// Programmatically register a *parameterized* trait impl for a concrete
633    /// type, e.g. `From[Source] for Target`. Like [`Self::register_trait_impl`] the
634    /// entry is not marked canonical. Pass `is_derived = true` for an entry
635    /// synthesized from another impl (e.g. the blanket `Into`).
636    pub fn register_param_trait_impl(
637        &mut self,
638        trait_name: impl Into<String>,
639        trait_args: &[Type],
640        ty: &Type,
641        is_derived: bool,
642    ) -> ImplId {
643        self.register_trait_impl_inner(trait_name, trait_args, ty, false, is_derived)
644    }
645
646    /// Inner registration shared by [`Self::register_trait_impl`],
647    /// [`Self::register_param_trait_impl`], and [`register_canonical_conformances`].
648    ///
649    /// This bypasses the orphan/sealing check applied to user `impl` blocks in
650    /// [`Self::visit_item`], so the compiler's own canonical registration is
651    /// never rejected.
652    ///
653    /// `trait_args` empty routes into the original two-tuple `trait_impl_index`
654    /// (bit-identical to the Q-bridge behavior); non-empty routes into the
655    /// parameterized three-tuple `param_trait_impl_index`.
656    fn register_trait_impl_inner(
657        &mut self,
658        trait_name: impl Into<String>,
659        trait_args: &[Type],
660        ty: &Type,
661        is_canonical: bool,
662        is_derived: bool,
663    ) -> ImplId {
664        let id = self.alloc_id();
665        let trait_name = trait_name.into();
666        let key = type_key(ty);
667        let args_vec = trait_args.to_vec();
668        let trait_ref = if args_vec.is_empty() {
669            TraitRef::new(&trait_name)
670        } else {
671            TraitRef::parameterized(&trait_name, args_vec.clone())
672        };
673        self.entries.insert(
674            id,
675            ImplEntry {
676                id,
677                trait_ref: Some(trait_ref),
678                type_key: key.clone(),
679                methods: vec![],
680                is_generic: false,
681                is_canonical,
682                trait_args: args_vec.clone(),
683                is_derived,
684                target_type: Some(ty.clone()),
685            },
686        );
687        if args_vec.is_empty() {
688            self.trait_impl_index.insert((trait_name, key), id);
689        } else {
690            self.param_trait_impl_index
691                .insert((trait_name, trait_arg_key(&args_vec), key), id);
692        }
693        id
694    }
695
696    /// Fold trait impls collected from imported modules into this table
697    /// (Q-xmod-impl), then re-run blanket-`Into` synthesis.
698    ///
699    /// Each `(trait_name, trait_args, target)` is registered the same way a
700    /// local impl would be: parameterized when `trait_args` is non-empty
701    /// (`From[A] for B`), plain otherwise (`Comparable for B`). An entry is
702    /// skipped when its slot is already occupied (a local impl, a canonical
703    /// conformance, or another imported impl already covers it), so importing
704    /// never clobbers a local registration and re-importing is idempotent.
705    ///
706    /// After registering the imported `From` impls, the blanket-`Into`
707    /// synthesis runs again so an imported `From[A] for B` yields the blanket
708    /// `Into[B] for A` needed by `a.into()` in the importing module.
709    pub fn fold_imported_impls(&mut self, impls: &[(String, Vec<Type>, Type)]) {
710        for (trait_name, trait_args, target) in impls {
711            let key = type_key(target);
712            if trait_args.is_empty() {
713                if self
714                    .trait_impl_index
715                    .contains_key(&(trait_name.clone(), key.clone()))
716                {
717                    continue; // already covered locally/canonically.
718                }
719                self.register_trait_impl(trait_name.clone(), target);
720            } else {
721                let arg_key = trait_arg_key(trait_args);
722                if self
723                    .param_trait_impl_index
724                    .contains_key(&(trait_name.clone(), arg_key, key))
725                {
726                    continue;
727                }
728                self.register_param_trait_impl(trait_name.clone(), trait_args, target, false);
729            }
730        }
731        // An imported `From[A] for B` must yield the blanket `Into[B] for A`.
732        self.synthesize_blanket_into();
733    }
734
735    /// Register an associated type binding for a given impl block.
736    ///
737    /// Overrides any existing binding for `(impl_id, name)`.
738    pub fn register_assoc_type(&mut self, impl_id: ImplId, name: impl Into<String>, ty: Type) {
739        self.assoc_types.insert((impl_id, name.into()), ty);
740    }
741
742    /// Look up an associated type binding by impl id and name.
743    ///
744    /// Returns `None` if no binding has been registered.
745    #[must_use]
746    pub fn resolve_assoc_type(&self, impl_id: ImplId, name: &str) -> Option<&Type> {
747        self.assoc_types.get(&(impl_id, name.to_owned()))
748    }
749
750    /// Return all supertraits of `trait_name`, collected transitively via BFS.
751    ///
752    /// The result list is in BFS order (direct supertraits before their
753    /// supertraits) with no duplicates. The original `trait_name` is **not**
754    /// included.
755    #[must_use]
756    pub fn all_supertraits(&self, trait_name: &str) -> Vec<String> {
757        let mut result = Vec::new();
758        let mut visited: HashSet<String> = HashSet::new();
759        let mut queue: VecDeque<String> = VecDeque::new();
760
761        if let Some(direct) = self.supertraits.get(trait_name) {
762            for st in direct {
763                if visited.insert(st.clone()) {
764                    queue.push_back(st.clone());
765                }
766            }
767        }
768
769        while let Some(name) = queue.pop_front() {
770            result.push(name.clone());
771            if let Some(supers) = self.supertraits.get(&name) {
772                for st in supers {
773                    if visited.insert(st.clone()) {
774                        queue.push_back(st.clone());
775                    }
776                }
777            }
778        }
779
780        result
781    }
782
783    /// Get the impl entry for an id.
784    #[must_use]
785    pub fn get_entry(&self, id: ImplId) -> Option<&ImplEntry> {
786        self.entries.get(&id)
787    }
788
789    /// Iterate over all registered entries.
790    pub fn entries(&self) -> impl Iterator<Item = &ImplEntry> {
791        self.entries.values()
792    }
793
794    /// Collect the user-declared trait impls that should be visible to a module
795    /// importing this one (Q-xmod-impl).
796    ///
797    /// Each entry is `(trait_name, trait_args, target_type)`. Only impls that a
798    /// downstream module needs to *re-register* are returned:
799    ///
800    /// * **canonical** entries (compiler-provided primitive conformances) are
801    ///   excluded — every module re-adds them via
802    ///   [`register_canonical_conformances`] / [`register_canonical_conversions`];
803    /// * **derived** entries (e.g. the blanket `Into[U]` synthesized from a
804    ///   `From[T] for U`) are excluded — they are re-synthesized locally by the
805    ///   blanket-`Into` synthesis from the re-registered `From`;
806    /// * **generic** impls are excluded — cross-module generic-impl
807    ///   instantiation is out of scope for v1.
808    ///
809    /// Both parameterized impls (`From[A] for B`) and plain trait impls
810    /// (`Comparable for B`) are returned, so a cross-module `.into()` resolves
811    /// and an imported generic fn's `T: Comparable` bound is satisfiable by an
812    /// imported `impl Comparable for B`.
813    ///
814    /// Impls whose **target type is a primitive** are excluded: the compiler's
815    /// canonical primitive conversions (`From[Int] for Float`, …) register
816    /// without the `is_canonical` flag set and would otherwise leak into the
817    /// export, and a primitive's conformances are re-registered locally in every
818    /// module anyway (and sealing forbids user core-trait impls on primitives).
819    /// The cross-module impls that matter target user-defined (`Named`) types.
820    #[must_use]
821    pub fn exportable_trait_impls(&self) -> Vec<(String, Vec<Type>, Type)> {
822        let mut out: Vec<(String, Vec<Type>, Type)> = Vec::new();
823        for entry in self.entries.values() {
824            if entry.is_canonical || entry.is_derived || entry.is_generic {
825                continue;
826            }
827            let Some(tr) = &entry.trait_ref else {
828                continue; // inherent impl — nothing to export here
829            };
830            let Some(target) = &entry.target_type else {
831                continue; // unresolved target — cannot re-register precisely
832            };
833            if matches!(target, Type::Primitive(_)) {
834                continue; // canonical/primitive conformance — not a user export
835            }
836            out.push((tr.name.clone(), entry.trait_args.clone(), target.clone()));
837        }
838        out
839    }
840
841    // ── Internal lookup helpers ────────────────────────────────────────────────
842
843    fn find_trait_impl(&self, trait_name: &str, type_key: &str) -> Option<ImplId> {
844        self.trait_impl_index
845            .get(&(trait_name.to_owned(), type_key.to_owned()))
846            .copied()
847    }
848
849    fn find_param_trait_impl(
850        &self,
851        trait_name: &str,
852        trait_arg_key: &str,
853        type_key: &str,
854    ) -> Option<ImplId> {
855        self.param_trait_impl_index
856            .get(&(
857                trait_name.to_owned(),
858                trait_arg_key.to_owned(),
859                type_key.to_owned(),
860            ))
861            .copied()
862    }
863
864    /// Returns `true` if *any* parameterized impl of `trait_name` exists for
865    /// `type_key`, regardless of the trait's type argument.
866    ///
867    /// This backs the v1 *arg-imprecise* satisfaction of a parameterized bound
868    /// such as `T: Into[U]`: because the bound's type argument is dropped at
869    /// parse time (the `where`-clause stores only the trait path), the checker
870    /// cannot key on the exact `[U]`. It instead accepts the bound when the
871    /// concrete `T` implements the trait for *some* argument. See the
872    /// session PR notes for this documented limitation.
873    #[must_use]
874    pub fn has_any_param_trait_impl(&self, trait_name: &str, type_key: &str) -> bool {
875        self.param_trait_impl_index
876            .keys()
877            .any(|(t, _arg, ty)| t == trait_name && ty == type_key)
878    }
879
880    fn find_inherent_impl(&self, type_key: &str) -> Option<ImplId> {
881        self.inherent_impl_index.get(type_key).copied()
882    }
883}
884
885impl Default for ImplTable {
886    fn default() -> Self {
887        Self::new()
888    }
889}
890
891// ─── Public resolution functions ──────────────────────────────────────────────
892
893/// Find the impl block that satisfies `trait_ref` for `ty` in `impls`.
894///
895/// Performs an exact-type lookup: the type key derived from `ty` must match
896/// the key stored when the impl was registered. Returns `None` if no impl
897/// exists for the `(trait, type)` pair.
898///
899/// To verify that supertrait obligations are also satisfied, call
900/// [`check_supertrait_obligations`] on the result.
901///
902/// For a parameterized trait (`trait_ref.args` non-empty), the lookup keys on
903/// the `(trait_name, trait_arg_key, target_type_key)` three-tuple, so
904/// `From[Int] for Float` and `From[String] for Float` resolve independently.
905/// Non-parameterized traits use the original two-tuple index (unchanged from
906/// the Q-bridge behavior).
907#[must_use]
908pub fn resolve_impl(trait_ref: &TraitRef, ty: &Type, impls: &ImplTable) -> Option<ImplId> {
909    let key = type_key(ty);
910    if trait_ref.args.is_empty() {
911        impls.find_trait_impl(&trait_ref.name, &key)
912    } else {
913        impls.find_param_trait_impl(&trait_ref.name, &trait_arg_key(&trait_ref.args), &key)
914    }
915}
916
917/// Check that all transitively required supertraits of `trait_ref` are
918/// satisfied by `ty` in `impls`.
919///
920/// Returns `true` if every supertrait reachable from `trait_ref` via the
921/// supertrait graph has a registered impl for `ty`.
922#[must_use]
923pub fn check_supertrait_obligations(trait_ref: &TraitRef, ty: &Type, impls: &ImplTable) -> bool {
924    let key = type_key(ty);
925    for supertrait in impls.all_supertraits(&trait_ref.name) {
926        if impls.find_trait_impl(&supertrait, &key).is_none() {
927            return false;
928        }
929    }
930    true
931}
932
933/// Dispatch a method call on `receiver` by searching `impls`.
934///
935/// Search order:
936/// 1. The inherent impl registered for `receiver`'s type key (if any).
937/// 2. All trait impls whose target type key matches `receiver`.
938///
939/// Returns the first match found, or `None` if no impl provides `method`.
940#[must_use]
941pub fn resolve_method(receiver: &Type, method: &str, impls: &ImplTable) -> Option<ResolvedMethod> {
942    let key = type_key(receiver);
943
944    // 1. Inherent impl.
945    if let Some(impl_id) = impls.find_inherent_impl(&key) {
946        if let Some(entry) = impls.get_entry(impl_id) {
947            if entry.methods.iter().any(|m| m == method) {
948                return Some(ResolvedMethod {
949                    impl_id,
950                    trait_ref: None,
951                    method: method.to_owned(),
952                });
953            }
954        }
955    }
956
957    // 2. Trait impls — iterate all entries whose type key matches.
958    for entry in impls.entries() {
959        if entry.type_key == key
960            && entry.trait_ref.is_some()
961            && entry.methods.iter().any(|m| m == method)
962        {
963            return Some(ResolvedMethod {
964                impl_id: entry.id,
965                trait_ref: entry.trait_ref.clone(),
966                method: method.to_owned(),
967            });
968        }
969    }
970
971    None
972}
973
974// ─── Key helpers ──────────────────────────────────────────────────────────────
975
976/// Produce a canonical string key for a [`Type`].
977///
978/// The key is used as the second component of the `(trait, type)` lookup in
979/// the [`ImplTable`]. It is deterministic and human-readable but is **not**
980/// intended as a user-facing display format.
981#[must_use]
982pub fn type_key(ty: &Type) -> String {
983    match ty {
984        Type::Primitive(p) => format!("{p:?}"),
985        Type::Named(n) => n.name.clone(),
986        Type::Generic(g) => {
987            let args = g.args.iter().map(type_key).collect::<Vec<_>>().join(", ");
988            format!("{}[{}]", g.constructor, args)
989        }
990        Type::Tuple(elems) => {
991            let elems = elems.iter().map(type_key).collect::<Vec<_>>().join(", ");
992            format!("({})", elems)
993        }
994        Type::Function(f) => {
995            let params = f.params.iter().map(type_key).collect::<Vec<_>>().join(", ");
996            format!("Fn({}) -> {}", params, type_key(&f.ret))
997        }
998        Type::Optional(inner) => format!("{}?", type_key(inner)),
999        Type::Result(ok, err) => format!("Result[{}, {}]", type_key(ok), type_key(err)),
1000        Type::TypeVar(id) => format!("?{id}"),
1001        Type::Refined(base, _) => type_key(base),
1002        Type::Flexible(_) => "Flexible".to_string(),
1003        Type::Error => "Error".to_string(),
1004    }
1005}
1006
1007/// Produce a canonical key string for a parameterized trait's type arguments.
1008///
1009/// `From[Int]` yields `"Int"`; `Pair[Int, String]` yields `"Int, String"`;
1010/// a non-parameterized trait yields the empty string. The key is the third
1011/// component (well, second of the trait portion) of the parameterized
1012/// lookup tuple and mirrors [`type_key`]'s encoding.
1013#[must_use]
1014pub fn trait_arg_key(args: &[Type]) -> String {
1015    args.iter().map(type_key).collect::<Vec<_>>().join(", ")
1016}
1017
1018/// Produce a structural signature key for a method (`NodeKind::FnDecl`) used by
1019/// the single-method-namespace coherence check (DQ27).
1020///
1021/// The key encodes the parameter shape and the return type, *not* the method
1022/// name. An unannotated receiver (`self`) and an explicit `Self` type both
1023/// normalize the same way so an inherent `render(self) -> String` and a
1024/// trait-impl `render(self) -> String` produce identical keys (a true
1025/// duplicate), while `foo(self) -> Int` and `foo(self) -> String` differ (a
1026/// genuine signature conflict). The key is deterministic and intended only for
1027/// equality comparison, not display.
1028fn method_sig_key(method: &AIRNode) -> String {
1029    let NodeKind::FnDecl {
1030        params,
1031        return_type,
1032        ..
1033    } = &method.kind
1034    else {
1035        return String::new();
1036    };
1037    let param_keys: Vec<String> = params
1038        .iter()
1039        .map(|p| match &p.kind {
1040            NodeKind::Param {
1041                pattern, ty: None, ..
1042            } => {
1043                // Unannotated param — keyed by its bound name. The receiver
1044                // `self` thus normalizes to `"self"` regardless of which block
1045                // declared the method.
1046                if let NodeKind::BindPat { name, .. } = &pattern.kind {
1047                    format!("@{}", name.name)
1048                } else {
1049                    "@_".to_owned()
1050                }
1051            }
1052            NodeKind::Param {
1053                ty: Some(ty_node), ..
1054            } => type_key_from_node(ty_node),
1055            _ => "?".to_owned(),
1056        })
1057        .collect();
1058    let ret_key = return_type
1059        .as_deref()
1060        .map_or_else(|| "Void".to_owned(), type_key_from_node);
1061    format!("({}) -> {}", param_keys.join(", "), ret_key)
1062}
1063
1064/// Extract a canonical trait name string from a [`TypePath`].
1065fn trait_name_from_path(path: &TypePath) -> String {
1066    path.segments
1067        .iter()
1068        .map(|s| s.name.as_str())
1069        .collect::<Vec<_>>()
1070        .join(".")
1071}
1072
1073/// Produce a canonical type key string from an AIR type-expression node.
1074///
1075/// Used during [`ImplTable::build_from`] to key the target of an impl block.
1076fn type_key_from_node(node: &AIRNode) -> String {
1077    match &node.kind {
1078        NodeKind::TypeNamed { path, args } => {
1079            let name = path
1080                .segments
1081                .iter()
1082                .map(|s| s.name.as_str())
1083                .collect::<Vec<_>>()
1084                .join(".");
1085            if args.is_empty() {
1086                name
1087            } else {
1088                let arg_keys: Vec<_> = args.iter().map(type_key_from_node).collect();
1089                format!("{}[{}]", name, arg_keys.join(", "))
1090            }
1091        }
1092        NodeKind::TypeTuple { elems } => {
1093            let elem_keys: Vec<_> = elems.iter().map(type_key_from_node).collect();
1094            format!("({})", elem_keys.join(", "))
1095        }
1096        NodeKind::TypeOptional { inner } => format!("{}?", type_key_from_node(inner)),
1097        NodeKind::TypeFunction { params, ret, .. } => {
1098            let param_keys: Vec<_> = params.iter().map(type_key_from_node).collect();
1099            format!(
1100                "Fn({}) -> {}",
1101                param_keys.join(", "),
1102                type_key_from_node(ret)
1103            )
1104        }
1105        NodeKind::TypeSelf => "Self".to_string(),
1106        _ => "Unknown".to_string(),
1107    }
1108}
1109
1110/// Best-effort conversion of a type-expression AIR node to a [`Type`].
1111///
1112/// Used when extracting associated type bindings from impl body items.
1113/// Unrecognised nodes produce [`Type::Error`].
1114fn type_from_node(node: &AIRNode) -> Type {
1115    match &node.kind {
1116        NodeKind::TypeNamed { path, args } => {
1117            let name = path
1118                .segments
1119                .iter()
1120                .map(|s| s.name.as_str())
1121                .collect::<Vec<_>>()
1122                .join(".");
1123            if args.is_empty() {
1124                match name.as_str() {
1125                    "Int" => Type::Primitive(PrimitiveType::Int),
1126                    "Float" => Type::Primitive(PrimitiveType::Float),
1127                    "Bool" => Type::Primitive(PrimitiveType::Bool),
1128                    "String" => Type::Primitive(PrimitiveType::String),
1129                    "Char" => Type::Primitive(PrimitiveType::Char),
1130                    "Void" => Type::Primitive(PrimitiveType::Void),
1131                    "Never" => Type::Primitive(PrimitiveType::Never),
1132                    _ => Type::Named(NamedType { name }),
1133                }
1134            } else {
1135                let type_args: Vec<_> = args.iter().map(type_from_node).collect();
1136                Type::Generic(GenericType {
1137                    constructor: name,
1138                    args: type_args,
1139                })
1140            }
1141        }
1142        NodeKind::TypeOptional { inner } => Type::Optional(Box::new(type_from_node(inner))),
1143        NodeKind::TypeTuple { elems } => Type::Tuple(elems.iter().map(type_from_node).collect()),
1144        NodeKind::TypeSelf => Type::Named(NamedType {
1145            name: "Self".to_owned(),
1146        }),
1147        _ => Type::Error,
1148    }
1149}
1150
1151// ─── Canonical primitive conformances (Q-bridge) ────────────────────────────────
1152
1153/// The set of core-trait names whose conformances for primitive types are
1154/// *sealed*: user code may not write its own `impl <CoreTrait> for <Primitive>`
1155/// (orphan-rule violation → `E4011`). The newtype pattern is the escape hatch.
1156///
1157/// Scoped strictly to the (core trait, primitive) quadrant — this is **not** a
1158/// general orphan model.
1159pub const SEALED_CORE_TRAITS: &[&str] = &["Equatable", "Comparable", "Displayable", "Hashable"];
1160
1161/// The set of primitive type keys (as produced by [`type_key`]) for which core
1162/// traits are sealed. Used together with [`SEALED_CORE_TRAITS`] to detect a
1163/// user `impl <CoreTrait> for <Primitive>`.
1164pub const SEALED_PRIMITIVE_KEYS: &[&str] = &[
1165    "Int", "Float", "String", "Bool", "Char", "Int8", "Int16", "Int32", "Int64", "Int128", "UInt8",
1166    "UInt16", "UInt32", "UInt64", "Float32", "Float64",
1167];
1168
1169/// Sized signed/unsigned integer primitives that share `Int`'s conformances.
1170const SIZED_INTS: &[PrimitiveType] = &[
1171    PrimitiveType::Int8,
1172    PrimitiveType::Int16,
1173    PrimitiveType::Int32,
1174    PrimitiveType::Int64,
1175    PrimitiveType::Int128,
1176    PrimitiveType::UInt8,
1177    PrimitiveType::UInt16,
1178    PrimitiveType::UInt32,
1179    PrimitiveType::UInt64,
1180];
1181
1182/// Sized floating-point primitives that share `Float`'s conformances.
1183const SIZED_FLOATS: &[PrimitiveType] = &[PrimitiveType::Float32, PrimitiveType::Float64];
1184
1185/// Register the compiler-provided canonical trait conformances for primitive
1186/// types into `table`.
1187///
1188/// These conformances populate the *same* trait-impl index that user `impl`
1189/// blocks do, so the type checker resolves primitives' trait methods and
1190/// generic-bound satisfaction uniformly (codegen still lowers primitive
1191/// operations via the existing intrinsic fast path — no dynamic dispatch).
1192///
1193/// Registration uses `ImplTable::register_trait_impl_inner` directly, which
1194/// bypasses the sealing check applied to user `impl` blocks, so the compiler's
1195/// own registration is never rejected. Call this **after**
1196/// [`ImplTable::build_from`] so user-impl sealing runs first.
1197///
1198/// The matrix (see the Q-bridge plan; the normative matrix is tracked as
1199/// DQ10):
1200/// - `Equatable`:   Int, Float, String, Bool, Char + sized ints/floats
1201/// - `Comparable`:  Int, Float, String, Char (not Bool) + sized ints/floats
1202/// - `Displayable`: Int, Float, String, Bool, Char + sized ints/floats
1203/// - `Hashable`:    Int, String, Bool, Char (not Float — NaN) + sized ints
1204///
1205/// Also registers the supertrait edge `Comparable → Equatable` (§18.5).
1206pub fn register_canonical_conformances(table: &mut ImplTable) {
1207    // Supertrait obligation: every `Comparable` type is also `Equatable`.
1208    table.register_supertrait("Comparable", "Equatable");
1209
1210    // Helper: register `trait_name` for each primitive in `prims`.
1211    let register = |table: &mut ImplTable, trait_name: &str, prims: &[PrimitiveType]| {
1212        for p in prims {
1213            let ty = Type::Primitive(p.clone());
1214            table.register_trait_impl_inner(trait_name, &[], &ty, true, false);
1215        }
1216    };
1217
1218    // Base scalar sets per trait (sized numerics appended below).
1219    const EQUATABLE_BASE: &[PrimitiveType] = &[
1220        PrimitiveType::Int,
1221        PrimitiveType::Float,
1222        PrimitiveType::String,
1223        PrimitiveType::Bool,
1224        PrimitiveType::Char,
1225    ];
1226    const COMPARABLE_BASE: &[PrimitiveType] = &[
1227        PrimitiveType::Int,
1228        PrimitiveType::Float,
1229        PrimitiveType::String,
1230        PrimitiveType::Char,
1231    ];
1232    const DISPLAYABLE_BASE: &[PrimitiveType] = &[
1233        PrimitiveType::Int,
1234        PrimitiveType::Float,
1235        PrimitiveType::String,
1236        PrimitiveType::Bool,
1237        PrimitiveType::Char,
1238    ];
1239    const HASHABLE_BASE: &[PrimitiveType] = &[
1240        PrimitiveType::Int,
1241        PrimitiveType::String,
1242        PrimitiveType::Bool,
1243        PrimitiveType::Char,
1244    ];
1245
1246    // Equatable: base + sized ints + sized floats.
1247    register(table, "Equatable", EQUATABLE_BASE);
1248    register(table, "Equatable", SIZED_INTS);
1249    register(table, "Equatable", SIZED_FLOATS);
1250
1251    // Comparable: base (no Bool) + sized ints + sized floats.
1252    register(table, "Comparable", COMPARABLE_BASE);
1253    register(table, "Comparable", SIZED_INTS);
1254    register(table, "Comparable", SIZED_FLOATS);
1255
1256    // Displayable: base + sized ints + sized floats.
1257    register(table, "Displayable", DISPLAYABLE_BASE);
1258    register(table, "Displayable", SIZED_INTS);
1259    register(table, "Displayable", SIZED_FLOATS);
1260
1261    // Hashable: base (no Float — NaN breaks the hash/eq law) + sized ints only.
1262    register(table, "Hashable", HASHABLE_BASE);
1263    register(table, "Hashable", SIZED_INTS);
1264}
1265
1266/// Register the compiler-provided canonical *conversions* between primitive
1267/// types into `table`.
1268///
1269/// These populate the parameterized `From`/`TryFrom` indexes (and, for each
1270/// `From`, the blanket reverse `Into`) so that `(5).into()` resolving to a
1271/// `Float`, `Float.from(3)`, and `Int.try_from(s)` all type-check uniformly
1272/// with user conversions. Call this **after** [`ImplTable::build_from`] and
1273/// [`register_canonical_conformances`].
1274///
1275/// The v1 *minimum-useful* matrix (the normative matrix is escalated to Design,
1276/// parallel to DQ10):
1277/// - `From[Int] for Float`
1278/// - signed-integer widening: each narrower signed int → each wider one, and
1279///   every sized signed int → the unsized `Int`
1280/// - `From[Float32] for Float`
1281/// - `From[Char] for String`
1282/// - `TryFrom[String] for Int` and `TryFrom[String] for Float`
1283///
1284/// **Lossy / narrowing conversions are intentionally excluded from v1** — a
1285/// narrowing conversion must go through `TryFrom`, or is deferred. These
1286/// canonical conversions ship **unsealed** (whether to seal them under §18.5
1287/// is an escalated Design question): user code may currently add its own
1288/// conversions for primitives.
1289pub fn register_canonical_conversions(table: &mut ImplTable) {
1290    // Register `From[source] for target`, plus its blanket reverse
1291    // `Into[target] for source` (marked derived). Both are canonical-internal
1292    // registrations that bypass user sealing.
1293    let from = |table: &mut ImplTable, source: PrimitiveType, target: PrimitiveType| {
1294        let source_ty = Type::Primitive(source);
1295        let target_ty = Type::Primitive(target);
1296        table.register_param_trait_impl(
1297            "From",
1298            std::slice::from_ref(&source_ty),
1299            &target_ty,
1300            false,
1301        );
1302        // Blanket reverse: Into[target] for source.
1303        table.register_param_trait_impl("Into", std::slice::from_ref(&target_ty), &source_ty, true);
1304    };
1305
1306    // From[Int] for Float.
1307    from(table, PrimitiveType::Int, PrimitiveType::Float);
1308
1309    // From[Float32] for Float.
1310    from(table, PrimitiveType::Float32, PrimitiveType::Float);
1311
1312    // From[Char] for String.
1313    from(table, PrimitiveType::Char, PrimitiveType::String);
1314
1315    // Signed-integer widening: narrower → wider, in size order, plus every
1316    // sized signed int → the unsized `Int`. Widening is always lossless.
1317    const SIGNED_WIDENING: &[PrimitiveType] = &[
1318        PrimitiveType::Int8,
1319        PrimitiveType::Int16,
1320        PrimitiveType::Int32,
1321        PrimitiveType::Int64,
1322        PrimitiveType::Int128,
1323    ];
1324    for (i, narrow) in SIGNED_WIDENING.iter().enumerate() {
1325        for wide in &SIGNED_WIDENING[i + 1..] {
1326            from(table, narrow.clone(), wide.clone());
1327        }
1328        // Every sized signed int widens into the unsized `Int`.
1329        from(table, narrow.clone(), PrimitiveType::Int);
1330    }
1331
1332    // Fallible parsing conversions: TryFrom[String] for Int / for Float. These
1333    // are NOT blanket-reversed (no TryInto in v1).
1334    let string_ty = Type::Primitive(PrimitiveType::String);
1335    table.register_param_trait_impl(
1336        "TryFrom",
1337        std::slice::from_ref(&string_ty),
1338        &Type::Primitive(PrimitiveType::Int),
1339        false,
1340    );
1341    table.register_param_trait_impl(
1342        "TryFrom",
1343        std::slice::from_ref(&string_ty),
1344        &Type::Primitive(PrimitiveType::Float),
1345        false,
1346    );
1347}
1348
1349// ─── Tests ────────────────────────────────────────────────────────────────────
1350
1351#[cfg(test)]
1352mod tests {
1353    use super::*;
1354    use crate::{NamedType, PrimitiveType, Type};
1355
1356    // ── Helpers ───────────────────────────────────────────────────────────────
1357
1358    fn named(name: &str) -> Type {
1359        Type::Named(NamedType {
1360            name: name.to_owned(),
1361        })
1362    }
1363
1364    fn int() -> Type {
1365        Type::Primitive(PrimitiveType::Int)
1366    }
1367
1368    fn bool_ty() -> Type {
1369        Type::Primitive(PrimitiveType::Bool)
1370    }
1371
1372    fn dummy_span() -> bock_errors::Span {
1373        use bock_errors::{FileId, Span};
1374        Span {
1375            file: FileId(0),
1376            start: 0,
1377            end: 0,
1378        }
1379    }
1380
1381    fn make_air_node(kind: NodeKind) -> AIRNode {
1382        AIRNode::new(0, dummy_span(), kind)
1383    }
1384
1385    fn make_module(items: Vec<AIRNode>) -> AIRNode {
1386        make_air_node(NodeKind::Module {
1387            path: None,
1388            annotations: vec![],
1389            imports: vec![],
1390            items,
1391        })
1392    }
1393
1394    fn make_type_named(name: &str) -> AIRNode {
1395        use bock_ast::{Ident, TypePath};
1396        let ident = Ident {
1397            name: name.to_owned(),
1398            span: dummy_span(),
1399        };
1400        make_air_node(NodeKind::TypeNamed {
1401            path: TypePath {
1402                segments: vec![ident],
1403                span: dummy_span(),
1404            },
1405            args: vec![],
1406        })
1407    }
1408
1409    fn make_fn_decl(name: &str) -> AIRNode {
1410        make_fn_decl_ret(name, None)
1411    }
1412
1413    /// Like [`make_fn_decl`] but with an explicit return-type name, so the
1414    /// single-method-namespace check can distinguish matching vs conflicting
1415    /// signatures.
1416    fn make_fn_decl_ret(name: &str, ret: Option<&str>) -> AIRNode {
1417        use bock_ast::{Ident, Visibility};
1418        let body = make_air_node(NodeKind::Block {
1419            stmts: vec![],
1420            tail: None,
1421        });
1422        make_air_node(NodeKind::FnDecl {
1423            annotations: vec![],
1424            visibility: Visibility::Private,
1425            is_async: false,
1426            name: Ident {
1427                name: name.to_owned(),
1428                span: dummy_span(),
1429            },
1430            generic_params: vec![],
1431            params: vec![],
1432            return_type: ret.map(|r| Box::new(make_type_named(r))),
1433            effect_clause: vec![],
1434            where_clause: vec![],
1435            body: Box::new(body),
1436        })
1437    }
1438
1439    /// Build `class Name { methods }` (fields omitted — irrelevant to the
1440    /// method-namespace check).
1441    fn make_class_decl(name: &str, methods: Vec<AIRNode>) -> AIRNode {
1442        use bock_ast::{Ident, Visibility};
1443        make_air_node(NodeKind::ClassDecl {
1444            annotations: vec![],
1445            visibility: Visibility::Private,
1446            name: Ident {
1447                name: name.to_owned(),
1448                span: dummy_span(),
1449            },
1450            generic_params: vec![],
1451            base: None,
1452            traits: vec![],
1453            fields: vec![],
1454            methods,
1455        })
1456    }
1457
1458    fn make_impl_block(
1459        trait_name: Option<&str>,
1460        target_name: &str,
1461        methods: Vec<AIRNode>,
1462    ) -> AIRNode {
1463        use bock_ast::{Ident, TypePath};
1464        let trait_path = trait_name.map(|n| TypePath {
1465            segments: vec![Ident {
1466                name: n.to_owned(),
1467                span: dummy_span(),
1468            }],
1469            span: dummy_span(),
1470        });
1471        make_air_node(NodeKind::ImplBlock {
1472            annotations: vec![],
1473            generic_params: vec![],
1474            trait_path,
1475            trait_args: vec![],
1476            target: Box::new(make_type_named(target_name)),
1477            where_clause: vec![],
1478            methods,
1479        })
1480    }
1481
1482    /// Build `impl Trait[arg_names...] for Target { methods }`.
1483    fn make_param_impl_block(
1484        trait_name: &str,
1485        trait_arg_names: &[&str],
1486        target_name: &str,
1487        methods: Vec<AIRNode>,
1488    ) -> AIRNode {
1489        use bock_ast::{Ident, TypePath};
1490        let trait_path = Some(TypePath {
1491            segments: vec![Ident {
1492                name: trait_name.to_owned(),
1493                span: dummy_span(),
1494            }],
1495            span: dummy_span(),
1496        });
1497        let trait_args = trait_arg_names
1498            .iter()
1499            .map(|n| make_type_named(n))
1500            .collect::<Vec<_>>();
1501        make_air_node(NodeKind::ImplBlock {
1502            annotations: vec![],
1503            generic_params: vec![],
1504            trait_path,
1505            trait_args,
1506            target: Box::new(make_type_named(target_name)),
1507            where_clause: vec![],
1508            methods,
1509        })
1510    }
1511
1512    // ── type_key ──────────────────────────────────────────────────────────────
1513
1514    #[test]
1515    fn type_key_primitive() {
1516        assert_eq!(type_key(&int()), "Int");
1517        assert_eq!(type_key(&bool_ty()), "Bool");
1518    }
1519
1520    #[test]
1521    fn type_key_named() {
1522        assert_eq!(type_key(&named("User")), "User");
1523    }
1524
1525    #[test]
1526    fn type_key_generic() {
1527        use crate::GenericType;
1528        let ty = Type::Generic(GenericType {
1529            constructor: "List".to_owned(),
1530            args: vec![int()],
1531        });
1532        assert_eq!(type_key(&ty), "List[Int]");
1533    }
1534
1535    #[test]
1536    fn type_key_optional() {
1537        assert_eq!(type_key(&Type::Optional(Box::new(int()))), "Int?");
1538    }
1539
1540    #[test]
1541    fn type_key_result() {
1542        assert_eq!(
1543            type_key(&Type::Result(Box::new(int()), Box::new(named("Err")))),
1544            "Result[Int, Err]"
1545        );
1546    }
1547
1548    // ── ImplTable construction ─────────────────────────────────────────────────
1549
1550    #[test]
1551    fn build_empty_module() {
1552        let module = make_module(vec![]);
1553        let table = ImplTable::build_from(&module);
1554        assert!(!table.diags.has_errors());
1555        assert_eq!(table.entries.len(), 0);
1556    }
1557
1558    #[test]
1559    fn build_registers_trait_impl() {
1560        let eq_method = make_fn_decl("equals");
1561        let impl_block = make_impl_block(Some("Equatable"), "User", vec![eq_method]);
1562        let module = make_module(vec![impl_block]);
1563        let table = ImplTable::build_from(&module);
1564        assert!(!table.diags.has_errors());
1565        let id = resolve_impl(&TraitRef::new("Equatable"), &named("User"), &table);
1566        assert!(id.is_some());
1567    }
1568
1569    #[test]
1570    fn build_registers_inherent_impl() {
1571        let method = make_fn_decl("greet");
1572        let impl_block = make_impl_block(None, "User", vec![method]);
1573        let module = make_module(vec![impl_block]);
1574        let table = ImplTable::build_from(&module);
1575        let result = resolve_method(&named("User"), "greet", &table);
1576        assert!(result.is_some());
1577        let r = result.unwrap();
1578        assert!(r.trait_ref.is_none());
1579        assert_eq!(r.method, "greet");
1580    }
1581
1582    // ── resolve_impl ──────────────────────────────────────────────────────────
1583
1584    #[test]
1585    fn resolve_impl_found() {
1586        let mut table = ImplTable::new();
1587        let id = table.alloc_id();
1588        table.entries.insert(
1589            id,
1590            ImplEntry {
1591                id,
1592                trait_ref: Some(TraitRef::new("Printable")),
1593                type_key: "Int".to_owned(),
1594                methods: vec!["print".to_owned()],
1595                is_generic: false,
1596                is_canonical: false,
1597                trait_args: vec![],
1598                is_derived: false,
1599                target_type: None,
1600            },
1601        );
1602        table
1603            .trait_impl_index
1604            .insert(("Printable".to_owned(), "Int".to_owned()), id);
1605
1606        assert_eq!(
1607            resolve_impl(&TraitRef::new("Printable"), &int(), &table),
1608            Some(id)
1609        );
1610    }
1611
1612    #[test]
1613    fn resolve_impl_not_found() {
1614        let table = ImplTable::new();
1615        assert_eq!(
1616            resolve_impl(&TraitRef::new("Printable"), &int(), &table),
1617            None
1618        );
1619    }
1620
1621    #[test]
1622    fn resolve_impl_wrong_type() {
1623        let mut table = ImplTable::new();
1624        let id = table.alloc_id();
1625        table.entries.insert(
1626            id,
1627            ImplEntry {
1628                id,
1629                trait_ref: Some(TraitRef::new("Printable")),
1630                type_key: "Int".to_owned(),
1631                methods: vec!["print".to_owned()],
1632                is_generic: false,
1633                is_canonical: false,
1634                trait_args: vec![],
1635                is_derived: false,
1636                target_type: None,
1637            },
1638        );
1639        table
1640            .trait_impl_index
1641            .insert(("Printable".to_owned(), "Int".to_owned()), id);
1642
1643        // Bool does not implement Printable.
1644        assert_eq!(
1645            resolve_impl(&TraitRef::new("Printable"), &bool_ty(), &table),
1646            None
1647        );
1648    }
1649
1650    // ── resolve_method ────────────────────────────────────────────────────────
1651
1652    #[test]
1653    fn resolve_method_inherent() {
1654        let method = make_fn_decl("to_string");
1655        let impl_block = make_impl_block(None, "User", vec![method]);
1656        let module = make_module(vec![impl_block]);
1657        let table = ImplTable::build_from(&module);
1658
1659        let r = resolve_method(&named("User"), "to_string", &table);
1660        assert!(r.is_some());
1661        let r = r.unwrap();
1662        assert!(r.trait_ref.is_none());
1663        assert_eq!(r.method, "to_string");
1664    }
1665
1666    #[test]
1667    fn resolve_method_from_trait_impl() {
1668        let method = make_fn_decl("serialize");
1669        let impl_block = make_impl_block(Some("Serializable"), "User", vec![method]);
1670        let module = make_module(vec![impl_block]);
1671        let table = ImplTable::build_from(&module);
1672
1673        let r = resolve_method(&named("User"), "serialize", &table);
1674        assert!(r.is_some());
1675        let r = r.unwrap();
1676        assert_eq!(
1677            r.trait_ref.as_ref().map(|t| t.name.as_str()),
1678            Some("Serializable")
1679        );
1680        assert_eq!(r.method, "serialize");
1681    }
1682
1683    #[test]
1684    fn resolve_method_not_found() {
1685        let table = ImplTable::new();
1686        assert!(resolve_method(&int(), "foo", &table).is_none());
1687    }
1688
1689    #[test]
1690    fn resolve_method_inherent_takes_priority_over_trait() {
1691        let inherent_method = make_fn_decl("display");
1692        let trait_method = make_fn_decl("display");
1693        let inherent_impl = make_impl_block(None, "User", vec![inherent_method]);
1694        let trait_impl = make_impl_block(Some("Display"), "User", vec![trait_method]);
1695        let module = make_module(vec![inherent_impl, trait_impl]);
1696        let table = ImplTable::build_from(&module);
1697
1698        let r = resolve_method(&named("User"), "display", &table).unwrap();
1699        // Inherent impl has priority — no trait_ref.
1700        assert!(r.trait_ref.is_none());
1701    }
1702
1703    // ── Coherence ─────────────────────────────────────────────────────────────
1704
1705    #[test]
1706    fn coherence_detects_exact_overlap() {
1707        let impl1 = make_impl_block(Some("Equatable"), "Point", vec![make_fn_decl("equals")]);
1708        let impl2 = make_impl_block(Some("Equatable"), "Point", vec![make_fn_decl("equals")]);
1709        let module = make_module(vec![impl1, impl2]);
1710        let table = ImplTable::build_from(&module);
1711
1712        assert!(table.diags.has_errors());
1713        assert_eq!(table.diags.error_count(), 1);
1714    }
1715
1716    #[test]
1717    fn coherence_allows_different_types() {
1718        // Same trait on two *distinct* (user) types is not an overlap. Uses
1719        // user types (`Point`/`Line`) rather than primitives so the (now
1720        // sealed) core-trait-for-primitive rule does not apply.
1721        let impl1 = make_impl_block(Some("Equatable"), "Point", vec![make_fn_decl("equals")]);
1722        let impl2 = make_impl_block(Some("Equatable"), "Line", vec![make_fn_decl("equals")]);
1723        let module = make_module(vec![impl1, impl2]);
1724        let table = ImplTable::build_from(&module);
1725
1726        assert!(!table.diags.has_errors());
1727    }
1728
1729    #[test]
1730    fn coherence_allows_different_traits() {
1731        let impl1 = make_impl_block(Some("Equatable"), "Point", vec![make_fn_decl("equals")]);
1732        let impl2 = make_impl_block(Some("Comparable"), "Point", vec![make_fn_decl("compare")]);
1733        let module = make_module(vec![impl1, impl2]);
1734        let table = ImplTable::build_from(&module);
1735
1736        assert!(!table.diags.has_errors());
1737    }
1738
1739    // ── DQ27 single-method-namespace coherence (E4012) ─────────────────────────
1740
1741    /// Helper: count diagnostics carrying the `E4012` duplicate-method code.
1742    fn count_e4012(table: &ImplTable) -> usize {
1743        table
1744            .diags
1745            .iter()
1746            .filter(|d| d.code == E_DUPLICATE_METHOD)
1747            .count()
1748    }
1749
1750    #[test]
1751    fn namespace_rejects_inherent_and_trait_same_method() {
1752        // The react-components collision: an inherent `render` plus a trait-impl
1753        // `render` for the same type defines `render` twice in one namespace.
1754        let inherent = make_impl_block(
1755            None,
1756            "Button",
1757            vec![make_fn_decl_ret("render", Some("String"))],
1758        );
1759        let trait_impl = make_impl_block(
1760            Some("Component"),
1761            "Button",
1762            vec![make_fn_decl_ret("render", Some("String"))],
1763        );
1764        let module = make_module(vec![inherent, trait_impl]);
1765        let table = ImplTable::build_from(&module);
1766
1767        assert!(table.diags.has_errors());
1768        assert_eq!(count_e4012(&table), 1);
1769    }
1770
1771    #[test]
1772    fn namespace_allows_empty_trait_impl_satisfied_by_inherent() {
1773        // An inherent `render` plus an EMPTY `impl Component for Button {}` is
1774        // well-formed: the inherent method satisfies the requirement, and
1775        // `render` is defined exactly once.
1776        let inherent = make_impl_block(
1777            None,
1778            "Button",
1779            vec![make_fn_decl_ret("render", Some("String"))],
1780        );
1781        let trait_impl = make_impl_block(Some("Component"), "Button", vec![]);
1782        let module = make_module(vec![inherent, trait_impl]);
1783        let table = ImplTable::build_from(&module);
1784
1785        assert!(!table.diags.has_errors());
1786        assert_eq!(count_e4012(&table), 0);
1787    }
1788
1789    #[test]
1790    fn namespace_allows_distinct_method_names() {
1791        // Inherent `click` + trait-impl `render` are distinct names — no clash.
1792        let inherent = make_impl_block(None, "Button", vec![make_fn_decl("click")]);
1793        let trait_impl = make_impl_block(
1794            Some("Component"),
1795            "Button",
1796            vec![make_fn_decl_ret("render", Some("String"))],
1797        );
1798        let module = make_module(vec![inherent, trait_impl]);
1799        let table = ImplTable::build_from(&module);
1800
1801        assert!(!table.diags.has_errors());
1802        assert_eq!(count_e4012(&table), 0);
1803    }
1804
1805    #[test]
1806    fn namespace_rejects_conflicting_signatures_across_traits() {
1807        // Two traits both requiring `foo` for the same type, with incompatible
1808        // return types, are unsatisfiable on v1 targets (one slot per name).
1809        let impl_a = make_impl_block(
1810            Some("TraitA"),
1811            "Widget",
1812            vec![make_fn_decl_ret("foo", Some("Int"))],
1813        );
1814        let impl_b = make_impl_block(
1815            Some("TraitB"),
1816            "Widget",
1817            vec![make_fn_decl_ret("foo", Some("String"))],
1818        );
1819        let module = make_module(vec![impl_a, impl_b]);
1820        let table = ImplTable::build_from(&module);
1821
1822        assert!(table.diags.has_errors());
1823        assert_eq!(count_e4012(&table), 1);
1824    }
1825
1826    #[test]
1827    fn namespace_rejects_class_body_and_trait_same_method() {
1828        // A class-body `render` plus a trait-impl `render` is also a duplicate:
1829        // class-body methods share the type's single namespace.
1830        let class = make_class_decl("Button", vec![make_fn_decl_ret("render", Some("String"))]);
1831        let trait_impl = make_impl_block(
1832            Some("Component"),
1833            "Button",
1834            vec![make_fn_decl_ret("render", Some("String"))],
1835        );
1836        let module = make_module(vec![class, trait_impl]);
1837        let table = ImplTable::build_from(&module);
1838
1839        assert!(table.diags.has_errors());
1840        assert_eq!(count_e4012(&table), 1);
1841    }
1842
1843    #[test]
1844    fn namespace_allows_class_body_satisfying_trait() {
1845        // A class-body `render` plus an EMPTY `impl Component for Button {}` is
1846        // well-formed (mirrors §6.4's `class Button : Component { fn render }`).
1847        let class = make_class_decl("Button", vec![make_fn_decl_ret("render", Some("String"))]);
1848        let trait_impl = make_impl_block(Some("Component"), "Button", vec![]);
1849        let module = make_module(vec![class, trait_impl]);
1850        let table = ImplTable::build_from(&module);
1851
1852        assert!(!table.diags.has_errors());
1853        assert_eq!(count_e4012(&table), 0);
1854    }
1855
1856    #[test]
1857    fn coherence_skips_generic_impls() {
1858        use bock_ast::{GenericParam, Ident, TypePath};
1859
1860        let generic_param = GenericParam {
1861            id: 0,
1862            span: dummy_span(),
1863            name: Ident {
1864                name: "T".to_owned(),
1865                span: dummy_span(),
1866            },
1867            bounds: vec![],
1868        };
1869        let impl1 = make_air_node(NodeKind::ImplBlock {
1870            annotations: vec![],
1871            generic_params: vec![generic_param.clone()],
1872            trait_path: Some(TypePath {
1873                segments: vec![Ident {
1874                    name: "Printable".to_owned(),
1875                    span: dummy_span(),
1876                }],
1877                span: dummy_span(),
1878            }),
1879            trait_args: vec![],
1880            target: Box::new(make_type_named("T")),
1881            where_clause: vec![],
1882            methods: vec![],
1883        });
1884        let impl2 = make_air_node(NodeKind::ImplBlock {
1885            annotations: vec![],
1886            generic_params: vec![generic_param],
1887            trait_path: Some(TypePath {
1888                segments: vec![Ident {
1889                    name: "Printable".to_owned(),
1890                    span: dummy_span(),
1891                }],
1892                span: dummy_span(),
1893            }),
1894            trait_args: vec![],
1895            target: Box::new(make_type_named("T")),
1896            where_clause: vec![],
1897            methods: vec![],
1898        });
1899        let module = make_module(vec![impl1, impl2]);
1900        let table = ImplTable::build_from(&module);
1901
1902        // Generic impls are exempt from exact-type coherence.
1903        assert!(!table.diags.has_errors());
1904    }
1905
1906    // ── Supertrait obligations ─────────────────────────────────────────────────
1907
1908    #[test]
1909    fn supertrait_registration_and_lookup() {
1910        let mut table = ImplTable::new();
1911        table.register_supertrait("Hashable", "Equatable");
1912        let supers = table.all_supertraits("Hashable");
1913        assert_eq!(supers, vec!["Equatable"]);
1914    }
1915
1916    #[test]
1917    fn supertrait_transitive() {
1918        let mut table = ImplTable::new();
1919        table.register_supertrait("C", "B");
1920        table.register_supertrait("B", "A");
1921        let supers = table.all_supertraits("C");
1922        assert_eq!(supers, vec!["B", "A"]);
1923    }
1924
1925    #[test]
1926    fn check_supertrait_obligations_satisfied() {
1927        let mut table = ImplTable::new();
1928        table.register_supertrait("Hashable", "Equatable");
1929
1930        // Register impls for both Equatable and Hashable on User.
1931        let eq_method = make_fn_decl("equals");
1932        let hash_method = make_fn_decl("hash");
1933        let eq_impl = make_impl_block(Some("Equatable"), "User", vec![eq_method]);
1934        let hash_impl = make_impl_block(Some("Hashable"), "User", vec![hash_method]);
1935        let module = make_module(vec![eq_impl, hash_impl]);
1936        let table_built = ImplTable::build_from(&module);
1937
1938        // Manually merge supertrait registration into built table.
1939        let mut full_table = table_built;
1940        full_table.register_supertrait("Hashable", "Equatable");
1941
1942        assert!(check_supertrait_obligations(
1943            &TraitRef::new("Hashable"),
1944            &named("User"),
1945            &full_table
1946        ));
1947    }
1948
1949    #[test]
1950    fn check_supertrait_obligations_missing() {
1951        let mut table = ImplTable::new();
1952        table.register_supertrait("Hashable", "Equatable");
1953
1954        // Only Hashable impl, missing Equatable.
1955        let hash_method = make_fn_decl("hash");
1956        let hash_impl = make_impl_block(Some("Hashable"), "User", vec![hash_method]);
1957        let module = make_module(vec![hash_impl]);
1958        let table_built = ImplTable::build_from(&module);
1959        let mut full_table = table_built;
1960        full_table.register_supertrait("Hashable", "Equatable");
1961
1962        // Equatable supertrait is not satisfied.
1963        assert!(!check_supertrait_obligations(
1964            &TraitRef::new("Hashable"),
1965            &named("User"),
1966            &full_table
1967        ));
1968    }
1969
1970    // ── Associated types ───────────────────────────────────────────────────────
1971
1972    #[test]
1973    fn assoc_type_manual_registration() {
1974        let mut table = ImplTable::new();
1975        let impl_id = table.alloc_id();
1976        table.register_assoc_type(impl_id, "Item", int());
1977
1978        assert_eq!(table.resolve_assoc_type(impl_id, "Item"), Some(&int()));
1979        assert_eq!(table.resolve_assoc_type(impl_id, "Missing"), None);
1980    }
1981
1982    #[test]
1983    fn assoc_type_not_found_for_other_impl() {
1984        let mut table = ImplTable::new();
1985        let id1 = table.alloc_id();
1986        let id2 = table.alloc_id();
1987        table.register_assoc_type(id1, "Item", int());
1988
1989        assert_eq!(table.resolve_assoc_type(id2, "Item"), None);
1990    }
1991
1992    // ── Method dispatch on generic types ──────────────────────────────────────
1993
1994    #[test]
1995    fn resolve_method_on_generic_type() {
1996        let method = make_fn_decl("push");
1997        let impl_block = make_impl_block(None, "List", vec![method]);
1998        let module = make_module(vec![impl_block]);
1999        let table = ImplTable::build_from(&module);
2000
2001        // The inherent impl is registered under the key "List" (no type args in
2002        // the target node). A receiver of List[Int] won't match by key since its
2003        // key is "List[Int]", but a plain Named("List") will.
2004        let receiver = Type::Named(NamedType {
2005            name: "List".to_owned(),
2006        });
2007        let r = resolve_method(&receiver, "push", &table);
2008        assert!(r.is_some());
2009    }
2010
2011    // ── Canonical primitive conformances (Q-bridge) ────────────────────────────
2012
2013    fn float() -> Type {
2014        Type::Primitive(PrimitiveType::Float)
2015    }
2016
2017    fn string() -> Type {
2018        Type::Primitive(PrimitiveType::String)
2019    }
2020
2021    fn char_ty() -> Type {
2022        Type::Primitive(PrimitiveType::Char)
2023    }
2024
2025    #[test]
2026    fn canonical_comparable_int_is_registered() {
2027        let mut table = ImplTable::new();
2028        register_canonical_conformances(&mut table);
2029        assert!(resolve_impl(&TraitRef::new("Comparable"), &int(), &table).is_some());
2030    }
2031
2032    #[test]
2033    fn canonical_equatable_covers_expected_primitives() {
2034        let mut table = ImplTable::new();
2035        register_canonical_conformances(&mut table);
2036        for ty in [int(), float(), string(), bool_ty(), char_ty()] {
2037            assert!(
2038                resolve_impl(&TraitRef::new("Equatable"), &ty, &table).is_some(),
2039                "Equatable should cover {ty:?}"
2040            );
2041        }
2042    }
2043
2044    #[test]
2045    fn canonical_comparable_excludes_bool() {
2046        let mut table = ImplTable::new();
2047        register_canonical_conformances(&mut table);
2048        // Bool is Equatable but intentionally NOT Comparable.
2049        assert!(resolve_impl(&TraitRef::new("Equatable"), &bool_ty(), &table).is_some());
2050        assert!(resolve_impl(&TraitRef::new("Comparable"), &bool_ty(), &table).is_none());
2051    }
2052
2053    #[test]
2054    fn canonical_hashable_excludes_float() {
2055        let mut table = ImplTable::new();
2056        register_canonical_conformances(&mut table);
2057        // Float is Equatable/Comparable but NOT Hashable (NaN breaks hash/eq).
2058        assert!(resolve_impl(&TraitRef::new("Equatable"), &float(), &table).is_some());
2059        assert!(resolve_impl(&TraitRef::new("Hashable"), &float(), &table).is_none());
2060        // Int is Hashable.
2061        assert!(resolve_impl(&TraitRef::new("Hashable"), &int(), &table).is_some());
2062    }
2063
2064    #[test]
2065    fn canonical_covers_sized_numerics() {
2066        let mut table = ImplTable::new();
2067        register_canonical_conformances(&mut table);
2068        let i32_ty = Type::Primitive(PrimitiveType::Int32);
2069        let u64_ty = Type::Primitive(PrimitiveType::UInt64);
2070        let f32_ty = Type::Primitive(PrimitiveType::Float32);
2071        assert!(resolve_impl(&TraitRef::new("Comparable"), &i32_ty, &table).is_some());
2072        assert!(resolve_impl(&TraitRef::new("Equatable"), &u64_ty, &table).is_some());
2073        assert!(resolve_impl(&TraitRef::new("Comparable"), &f32_ty, &table).is_some());
2074        // Sized float is not Hashable, matching Float.
2075        assert!(resolve_impl(&TraitRef::new("Hashable"), &f32_ty, &table).is_none());
2076        // Sized int IS Hashable.
2077        assert!(resolve_impl(&TraitRef::new("Hashable"), &i32_ty, &table).is_some());
2078    }
2079
2080    #[test]
2081    fn canonical_entries_are_marked_canonical() {
2082        let mut table = ImplTable::new();
2083        register_canonical_conformances(&mut table);
2084        let id = resolve_impl(&TraitRef::new("Comparable"), &int(), &table).unwrap();
2085        assert!(table.get_entry(id).unwrap().is_canonical);
2086    }
2087
2088    #[test]
2089    fn canonical_registers_comparable_equatable_supertrait() {
2090        let mut table = ImplTable::new();
2091        register_canonical_conformances(&mut table);
2092        assert_eq!(table.all_supertraits("Comparable"), vec!["Equatable"]);
2093    }
2094
2095    #[test]
2096    fn user_register_trait_impl_is_not_canonical() {
2097        let mut table = ImplTable::new();
2098        let id = table.register_trait_impl("MyTrait", &named("User"));
2099        assert!(!table.get_entry(id).unwrap().is_canonical);
2100    }
2101
2102    // ── Sealing: user `impl <CoreTrait> for <Primitive>` (Q1b / E4011) ─────────
2103
2104    #[test]
2105    fn sealing_rejects_user_impl_core_trait_for_primitive() {
2106        // `impl Comparable for Int` in user code must be rejected (E4011).
2107        let method = make_fn_decl("compare");
2108        let impl_block = make_impl_block(Some("Comparable"), "Int", vec![method]);
2109        let module = make_module(vec![impl_block]);
2110        let table = ImplTable::build_from(&module);
2111
2112        assert!(table.diags.has_errors());
2113        assert_eq!(table.diags.error_count(), 1);
2114        let diag = table.diags.iter().next().unwrap();
2115        assert_eq!(diag.code, E_SEALED_PRIMITIVE_IMPL);
2116        // The offending impl must NOT have been registered.
2117        assert!(resolve_impl(&TraitRef::new("Comparable"), &int(), &table).is_none());
2118        // A newtype help note is attached.
2119        assert!(diag.notes.iter().any(|n| n.contains("newtype")));
2120    }
2121
2122    #[test]
2123    fn sealing_rejects_each_sealed_core_trait_for_primitive() {
2124        for trait_name in ["Equatable", "Comparable", "Displayable", "Hashable"] {
2125            let impl_block = make_impl_block(Some(trait_name), "String", vec![make_fn_decl("m")]);
2126            let module = make_module(vec![impl_block]);
2127            let table = ImplTable::build_from(&module);
2128            assert!(
2129                table.diags.has_errors(),
2130                "{trait_name} for String should be sealed"
2131            );
2132        }
2133    }
2134
2135    #[test]
2136    fn sealing_allows_user_impl_core_trait_for_newtype() {
2137        // Positive control: `impl Comparable for MyNewtype` is fine.
2138        let method = make_fn_decl("compare");
2139        let impl_block = make_impl_block(Some("Comparable"), "MyNewtype", vec![method]);
2140        let module = make_module(vec![impl_block]);
2141        let table = ImplTable::build_from(&module);
2142
2143        assert!(!table.diags.has_errors());
2144        assert!(resolve_impl(&TraitRef::new("Comparable"), &named("MyNewtype"), &table).is_some());
2145    }
2146
2147    #[test]
2148    fn sealing_allows_user_impl_noncore_trait_for_primitive() {
2149        // A non-core trait for a primitive is out of scope of the seal — the
2150        // seal is strictly the (core trait, primitive) quadrant.
2151        let impl_block = make_impl_block(Some("MyTrait"), "Int", vec![make_fn_decl("m")]);
2152        let module = make_module(vec![impl_block]);
2153        let table = ImplTable::build_from(&module);
2154        assert!(!table.diags.has_errors());
2155    }
2156
2157    #[test]
2158    fn canonical_registration_bypasses_sealing() {
2159        // The compiler's own canonical conformances use
2160        // `register_trait_impl_inner` and must NOT trip the seal even though
2161        // they are (core trait, primitive) pairs.
2162        let mut table = ImplTable::new();
2163        register_canonical_conformances(&mut table);
2164        assert!(!table.diags.has_errors());
2165        assert!(resolve_impl(&TraitRef::new("Comparable"), &int(), &table).is_some());
2166    }
2167
2168    // ── Parameterized-trait resolution (T2/T3) ─────────────────────────────────
2169
2170    #[test]
2171    fn param_impl_distinct_args_resolve_independently() {
2172        // impl From[Int] for Float  and  impl From[String] for Float
2173        // must resolve to different impls — no false collision.
2174        let from_int = make_param_impl_block("From", &["Int"], "Float", vec![make_fn_decl("from")]);
2175        let from_str =
2176            make_param_impl_block("From", &["String"], "Float", vec![make_fn_decl("from")]);
2177        let module = make_module(vec![from_int, from_str]);
2178        let table = ImplTable::build_from(&module);
2179
2180        assert!(
2181            !table.diags.has_errors(),
2182            "distinct trait args must not collide"
2183        );
2184        let float = Type::Primitive(PrimitiveType::Float);
2185        let id_int = resolve_impl(
2186            &TraitRef::parameterized("From", vec![int()]),
2187            &float,
2188            &table,
2189        );
2190        let id_str = resolve_impl(
2191            &TraitRef::parameterized("From", vec![Type::Primitive(PrimitiveType::String)]),
2192            &float,
2193            &table,
2194        );
2195        assert!(id_int.is_some(), "From[Int] for Float should resolve");
2196        assert!(id_str.is_some(), "From[String] for Float should resolve");
2197        assert_ne!(id_int, id_str, "the two impls must be distinct");
2198    }
2199
2200    #[test]
2201    fn param_impl_missing_arg_does_not_resolve() {
2202        // Only From[Int] for Float is registered; From[Bool] for Float is not.
2203        let from_int = make_param_impl_block("From", &["Int"], "Float", vec![make_fn_decl("from")]);
2204        let module = make_module(vec![from_int]);
2205        let table = ImplTable::build_from(&module);
2206        let float = Type::Primitive(PrimitiveType::Float);
2207        assert!(resolve_impl(
2208            &TraitRef::parameterized("From", vec![bool_ty()]),
2209            &float,
2210            &table
2211        )
2212        .is_none());
2213    }
2214
2215    #[test]
2216    fn param_impl_duplicate_args_collide() {
2217        // Two identical impl From[Int] for Float → E4010 coherence error.
2218        let a = make_param_impl_block("From", &["Int"], "Float", vec![make_fn_decl("from")]);
2219        let b = make_param_impl_block("From", &["Int"], "Float", vec![make_fn_decl("from")]);
2220        let module = make_module(vec![a, b]);
2221        let table = ImplTable::build_from(&module);
2222        assert!(
2223            table.diags.has_errors(),
2224            "duplicate From[Int] for Float must be a coherence error"
2225        );
2226    }
2227
2228    #[test]
2229    fn param_and_nonparam_indexes_are_independent() {
2230        // A non-parameterized impl and a parameterized impl of the same trait
2231        // name for the same target type live in separate indexes and do not
2232        // collide. (Edge case; primarily a sanity check on index isolation.)
2233        let bare = make_impl_block(Some("From"), "Float", vec![make_fn_decl("from")]);
2234        let param = make_param_impl_block("From", &["Int"], "Float", vec![make_fn_decl("from")]);
2235        let module = make_module(vec![bare, param]);
2236        let table = ImplTable::build_from(&module);
2237        assert!(!table.diags.has_errors());
2238        let float = Type::Primitive(PrimitiveType::Float);
2239        assert!(resolve_impl(&TraitRef::new("From"), &float, &table).is_some());
2240        assert!(resolve_impl(
2241            &TraitRef::parameterized("From", vec![int()]),
2242            &float,
2243            &table
2244        )
2245        .is_some());
2246    }
2247
2248    // ── Blanket From ⇒ Into synthesis (T4) ─────────────────────────────────────
2249
2250    #[test]
2251    fn blanket_into_derived_from_explicit_from() {
2252        // impl From[Int] for Float  ⇒  derived impl Into[Float] for Int.
2253        let from = make_param_impl_block("From", &["Int"], "Float", vec![make_fn_decl("from")]);
2254        let module = make_module(vec![from]);
2255        let table = ImplTable::build_from(&module);
2256        assert!(!table.diags.has_errors());
2257
2258        let float = Type::Primitive(PrimitiveType::Float);
2259        // Into[Float] for Int must resolve.
2260        let into_id = resolve_impl(
2261            &TraitRef::parameterized("Into", vec![float.clone()]),
2262            &int(),
2263            &table,
2264        );
2265        assert!(
2266            into_id.is_some(),
2267            "blanket Into[Float] for Int should resolve"
2268        );
2269        let entry = table.get_entry(into_id.unwrap()).unwrap();
2270        assert!(
2271            entry.is_derived,
2272            "synthesized Into entry must be is_derived"
2273        );
2274    }
2275
2276    #[test]
2277    fn blanket_into_does_not_clobber_explicit() {
2278        // Explicit  impl Into[U] for A  plus  impl From[A] for U  must not
2279        // produce E4010, and resolution must return the EXPLICIT Into.
2280        let explicit_into =
2281            make_param_impl_block("Into", &["Float"], "Apple", vec![make_fn_decl("into")]);
2282        let from = make_param_impl_block("From", &["Apple"], "Float", vec![make_fn_decl("from")]);
2283        let module = make_module(vec![explicit_into, from]);
2284        let table = ImplTable::build_from(&module);
2285
2286        assert!(
2287            !table.diags.has_errors(),
2288            "explicit Into + blanket From must not collide"
2289        );
2290        let float = Type::Primitive(PrimitiveType::Float);
2291        let apple = Type::Named(NamedType {
2292            name: "Apple".to_owned(),
2293        });
2294        let into_id = resolve_impl(
2295            &TraitRef::parameterized("Into", vec![float]),
2296            &apple,
2297            &table,
2298        )
2299        .expect("Into[Float] for Apple should resolve");
2300        let entry = table.get_entry(into_id).unwrap();
2301        assert!(
2302            !entry.is_derived,
2303            "explicit Into must win over the blanket-derived one"
2304        );
2305    }
2306
2307    // ── Canonical primitive conversions (T8) ───────────────────────────────────
2308
2309    #[test]
2310    fn canonical_conversions_register_from_int_for_float() {
2311        let mut table = ImplTable::new();
2312        register_canonical_conversions(&mut table);
2313        let float = Type::Primitive(PrimitiveType::Float);
2314        // From[Int] for Float resolves.
2315        assert!(resolve_impl(
2316            &TraitRef::parameterized("From", vec![int()]),
2317            &float,
2318            &table
2319        )
2320        .is_some());
2321        // Blanket Into[Float] for Int resolves.
2322        assert!(resolve_impl(
2323            &TraitRef::parameterized("Into", vec![float]),
2324            &int(),
2325            &table
2326        )
2327        .is_some());
2328    }
2329
2330    #[test]
2331    fn canonical_conversions_widen_signed_ints() {
2332        let mut table = ImplTable::new();
2333        register_canonical_conversions(&mut table);
2334        // Int8 -> Int64 (widening) and Int8 -> Int.
2335        let i8 = Type::Primitive(PrimitiveType::Int8);
2336        let i64 = Type::Primitive(PrimitiveType::Int64);
2337        assert!(resolve_impl(
2338            &TraitRef::parameterized("From", vec![i8.clone()]),
2339            &i64,
2340            &table
2341        )
2342        .is_some());
2343        assert!(resolve_impl(
2344            &TraitRef::parameterized("From", vec![i8.clone()]),
2345            &int(),
2346            &table
2347        )
2348        .is_some());
2349        // Narrowing Int64 -> Int8 is NOT registered (lossy, excluded from v1).
2350        assert!(resolve_impl(&TraitRef::parameterized("From", vec![i64]), &i8, &table).is_none());
2351    }
2352
2353    #[test]
2354    fn canonical_conversions_try_from_string() {
2355        let mut table = ImplTable::new();
2356        register_canonical_conversions(&mut table);
2357        let string = Type::Primitive(PrimitiveType::String);
2358        assert!(resolve_impl(
2359            &TraitRef::parameterized("TryFrom", vec![string.clone()]),
2360            &int(),
2361            &table
2362        )
2363        .is_some());
2364        assert!(resolve_impl(
2365            &TraitRef::parameterized("TryFrom", vec![string]),
2366            &Type::Primitive(PrimitiveType::Float),
2367            &table
2368        )
2369        .is_some());
2370    }
2371
2372    #[test]
2373    fn blanket_into_not_derived_for_try_from() {
2374        // TryFrom is intentionally NOT blanket-reversed (no TryInto in v1).
2375        let tryfrom = make_param_impl_block(
2376            "TryFrom",
2377            &["String"],
2378            "Int",
2379            vec![make_fn_decl("try_from")],
2380        );
2381        let module = make_module(vec![tryfrom]);
2382        let table = ImplTable::build_from(&module);
2383        let string = Type::Primitive(PrimitiveType::String);
2384        assert!(
2385            resolve_impl(
2386                &TraitRef::parameterized("TryInto", vec![int()]),
2387                &string,
2388                &table
2389            )
2390            .is_none(),
2391            "no TryInto should be synthesized"
2392        );
2393        assert!(
2394            resolve_impl(
2395                &TraitRef::parameterized("Into", vec![int()]),
2396                &string,
2397                &table
2398            )
2399            .is_none(),
2400            "TryFrom must not synthesize a (lossless) Into"
2401        );
2402    }
2403}