Proposal: Trait Resolution and Conflict Handling

Status: Approved Author: Eric (with AI assistance) Created: 2026-01-29 Approved: 2026-01-30 Affects: Compiler, type system, trait system


Summary

This proposal specifies rules for resolving trait implementation conflicts, including the diamond problem in trait inheritance, conflicting default implementations, coherence rules (orphan rules), and extension method conflicts.


Problem Statement

The spec defines traits and implementations but doesn’t address:

  1. Diamond problem: What happens when a type inherits the same trait through multiple paths?
  2. Conflicting defaults: When multiple trait bounds provide different default implementations
  3. Coherence rules: Who can implement which traits for which types?
  4. Extension conflicts: When extension methods conflict with inherent methods
  5. Super trait calls: Can implementations call parent trait methods?

Trait Inheritance and Diamond Problem

Diamond Scenario

trait A { @method (self) -> int }
trait B: A { }
trait C: A { }
trait D: B + C { }  // D inherits A through both B and C

Resolution Rule

Single Implementation: A type implementing D provides ONE implementation of A.method. There is no duplication or conflict because traits define interface, not implementation.

impl MyType: D {
    @method (self) -> int = 42  // Single implementation satisfies A via B and C
}

Conflicting Default Implementations

If both B and C override A’s default:

trait A { @method (self) -> int = 0 }
trait B: A { @method (self) -> int = 1 }
trait C: A { @method (self) -> int = 2 }
trait D: B + C { }

impl MyType: D { }  // ERROR: ambiguous default for @method

Resolution: The implementing type MUST provide an explicit implementation when defaults conflict.

impl MyType: D {
    @method (self) -> int = 3  // Explicit implementation resolves ambiguity
}

Coherence Rules (Orphan Rules)

Definition

Coherence ensures that for any type T and trait Trait, there is at most one implementation of T: Trait visible in any compilation unit.

Orphan Rules

An implementation impl Type: Trait is allowed only if at least one of these is true:

  1. Trait is defined in the current module
  2. Type is defined in the current module
  3. Type is a generic parameter constrained in the current module
// In module my_app

// OK: Type is local
type MyType = { ... }
impl MyType: ExternalTrait { }

// OK: Trait is local
trait MyTrait { ... }
impl ExternalType: MyTrait { }

// ERROR: Both trait and type are external (orphan)
impl std.Vec: std.Display { }  // Error: orphan implementation

Blanket Implementations

Blanket implementations (impl<T> T: Trait where ...) follow the same rules:

// OK in std library: both Printable and From<T> are std traits
impl<T: Printable> str: From<T> { }

// ERROR in user code: cannot add blanket impl for external trait
impl<T> T: ExternalTrait { }  // Error: orphan blanket

Rationale

Orphan rules prevent:

  • Conflicting implementations from different libraries
  • “Spooky action at a distance” where importing a module changes behavior
  • Unpredictable trait resolution based on import order

Trait Method Resolution Order

Method Lookup Priority

When calling value.method():

  1. Inherent methods — methods in impl Type { } (not trait impl)
  2. Trait methods from explicit bounds — methods from where T: Trait
  3. Trait methods from in-scope traits — traits imported into current scope
  4. Extension methods — methods added via extend
type Foo = { }

impl Foo {
    @method (self) -> int = 1  // Priority 1: inherent
}

trait Bar { @method (self) -> int }
impl Foo: Bar {
    @method (self) -> int = 2  // Priority 3: trait (if Bar in scope)
}

extend Baz {
    @method (self) -> int = 3  // Priority 4: extension
}

let x = Foo { }
x.method()  // Returns 1 (inherent wins)

Ambiguity Resolution

If multiple traits provide the same method and none are inherent:

trait A { @method (self) -> int }
trait B { @method (self) -> int }

impl Foo: A { @method (self) -> int = 1 }
impl Foo: B { @method (self) -> int = 2 }

let x: Foo = ...
x.method()  // ERROR: ambiguous method call

Resolution: Use fully-qualified syntax:

A.method(x)  // Calls A's implementation
B.method(x)  // Calls B's implementation

Super Trait Method Calls

Calling Parent Default

An implementation can call the parent trait’s default implementation using Trait.method(self):

trait Parent {
    @method (self) -> int = 10
}

trait Child: Parent {
    @method (self) -> int = Parent.method(self) + 1
}

Calling in Impl Override

The same syntax works when overriding in impl:

impl MyType: Parent {
    @method (self) -> int = Parent.method(self) * 2
}

Parent.method(self) always calls the trait’s default implementation, regardless of whether it’s used in a trait definition or an impl block.


Extension Method Conflicts

Extension vs Inherent

Inherent methods always win over extensions:

impl str {
    @trim (self) -> str = ...  // Inherent
}

extend str {
    @trim (self) -> str = ...  // Extension - never called
}

"hello".trim()  // Calls inherent

Extension vs Extension

When multiple extensions provide the same method:

// In module a
extend Iterator {
    @sum (self) -> int = ...
}

// In module b
extend Iterator {
    @sum (self) -> int = ...
}

// In module c
extension "a" { Iterator.sum }
extension "b" { Iterator.sum }  // ERROR: conflicting extension imports

Conflicts are detected based on what is in scope, not just explicit extension statements. Re-exported extensions also conflict:

// In module c
use "./utils" { sum_extension }    // re-exports a.Iterator.sum
extension "b" { Iterator.sum }      // ERROR: Iterator.sum already in scope

Resolution: Only one extension for a given method may be in scope.


Associated Type Constraints

Constraint Syntax

trait Container {
    type Item
}

@process<C: Container> (c: C) -> C.Item where C.Item: Clone = ...

Associated Type Disambiguation

When a type implements multiple traits with same-named associated types, use qualified paths to disambiguate:

trait A { type Item }
trait B { type Item }

// Qualified path syntax: Type::Trait::AssocType
@f<C: A + B> (c: C) where C::A::Item: Clone = ...

// To require both Items to be the same type:
@g<C: A + B> (c: C) where C::A::Item == C::B::Item, C::A::Item: Clone = ...

If a type implements both A and B, it has two distinct associated types:

  • C::A::Item — the Item from trait A
  • C::B::Item — the Item from trait B

Without qualification, C.Item is ambiguous when multiple traits define Item.


Implementation Priority

Specificity Rules

When multiple impls could apply, more specific wins:

impl<T> T: Trait { }              // Generic blanket
impl<T: Clone> T: Trait { }      // Constrained blanket (more specific)
impl MyType: Trait { }            // Concrete (most specific)

For MyType:

  • If MyType: Clone, concrete impl wins
  • Concrete always beats blanket

No Overlap Guarantee

The compiler ensures no two applicable impls have equal specificity:

impl<T: A> T: Trait { }
impl<T: B> T: Trait { }

type Foo = { }
impl Foo: A { }
impl Foo: B { }

// ERROR at impl sites: overlapping implementations
// If Foo: A + B, which impl of Trait applies?

Error Messages

Conflicting Implementations

error[E0600]: conflicting implementations of trait `Display`
  --> src/main.ori:10:1
   |
5  | impl MyType: Display { ... }
   | ---------------------------- first implementation here
...
10 | impl MyType: Display { ... }
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation

Orphan Implementation

error[E0601]: orphan implementation
  --> src/main.ori:5:1
   |
5  | impl std.Vec: std.Display { }
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: implement a local trait for external type, or a trait for local type
   = note: this restriction prevents conflicting implementations across crates

Ambiguous Method

error[E0602]: ambiguous method call
  --> src/main.ori:15:5
   |
15 |     x.method()
   |       ^^^^^^ method found in multiple traits
   |
   = note: candidate #1: `A.method` from trait `A`
   = note: candidate #2: `B.method` from trait `B`
   = help: use fully-qualified syntax: `A.method(x)` or `B.method(x)`

Conflicting Extensions

error[E0603]: conflicting extension methods
  --> src/main.ori:8:1
   |
5  | extension "a" { Iterator.sum }
   | ------------------------------ Iterator.sum first imported here
...
8  | extension "b" { Iterator.sum }
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting extension import
   |
   = help: only one extension for a given method may be in scope

Spec Changes Required

Update 08-declarations.md

Add sections for:

  1. Coherence rules

    • Orphan rules
    • Blanket implementation restrictions
    • Overlap detection
  2. Trait inheritance resolution

    • Diamond problem handling
    • Conflicting default implementations
  3. Method resolution order

    • Inherent > Trait > Extension priority
    • Ambiguity resolution with fully-qualified syntax
  4. Super trait method calls

    • Trait.method(self) syntax
  5. Associated type disambiguation

    • Qualified path syntax Type::Trait::AssocType

Summary

AspectRule
Diamond problemSingle implementation satisfies all paths
Conflicting defaultsExplicit impl required
Orphan ruleTrait or type must be local
Resolution orderInherent > Trait > Extension
Ambiguous methodsFully-qualified syntax required
Super callsTrait.method(self)
Associated typesType::Trait::AssocType for disambiguation
Extension conflictsOnly one per method in scope (includes re-exports)
Overlapping implsCompile error
SpecificityConcrete > Constrained > Generic