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:
- Diamond problem: What happens when a type inherits the same trait through multiple paths?
- Conflicting defaults: When multiple trait bounds provide different default implementations
- Coherence rules: Who can implement which traits for which types?
- Extension conflicts: When extension methods conflict with inherent methods
- 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:
Traitis defined in the current moduleTypeis defined in the current moduleTypeis 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():
- Inherent methods — methods in
impl Type { }(not trait impl) - Trait methods from explicit bounds — methods from
where T: Trait - Trait methods from in-scope traits — traits imported into current scope
- 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 AC::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:
-
Coherence rules
- Orphan rules
- Blanket implementation restrictions
- Overlap detection
-
Trait inheritance resolution
- Diamond problem handling
- Conflicting default implementations
-
Method resolution order
- Inherent > Trait > Extension priority
- Ambiguity resolution with fully-qualified syntax
-
Super trait method calls
Trait.method(self)syntax
-
Associated type disambiguation
- Qualified path syntax
Type::Trait::AssocType
- Qualified path syntax
Summary
| Aspect | Rule |
|---|---|
| Diamond problem | Single implementation satisfies all paths |
| Conflicting defaults | Explicit impl required |
| Orphan rule | Trait or type must be local |
| Resolution order | Inherent > Trait > Extension |
| Ambiguous methods | Fully-qualified syntax required |
| Super calls | Trait.method(self) |
| Associated types | Type::Trait::AssocType for disambiguation |
| Extension conflicts | Only one per method in scope (includes re-exports) |
| Overlapping impls | Compile error |
| Specificity | Concrete > Constrained > Generic |