Proposal: Extension Methods
Status: Approved Author: Eric (with AI assistance) Created: 2026-01-30 Approved: 2026-01-30 Affects: Compiler, type system, module system
Summary
This proposal formalizes extension method semantics, including definition syntax, import mechanics, conflict resolution, and scoping rules.
Problem Statement
The spec documents extension syntax but leaves unclear:
- Scoping: When are extension methods visible?
- Conflict resolution: What happens when multiple extensions define the same method?
- Orphan rules: Can extensions be defined for foreign types?
- Method resolution order: How do extensions interact with inherent methods and traits?
Extension Definition
Syntax
Extensions add methods to existing types without modifying their definition:
extend Iterator {
@count (self) -> int = {
let count = 0
for _ in self do count = count + 1
count
}
}
Constrained Extensions
Extensions can have type constraints. Both syntax forms are equivalent:
// Angle bracket form
extend<T: Clone> [T] {
@duplicate_all (self) -> [T] = self.map(transform: x -> x.clone())
}
// Where clause form (equivalent)
extend [T] where T: Clone {
@duplicate_all (self) -> [T] = self.map(transform: x -> x.clone())
}
extend Iterator where Self.Item: Add {
@sum (self) -> Self.Item = self.fold(
initial: Self.Item.default(),
combine: (acc, x) -> acc + x,
)
}
What Can Be Extended
Extensions may be defined for:
- Concrete types:
extend Point { ... } - Generic types:
extend [T] { ... } - Trait implementors:
extend Iterator { ... } - Constrained generics:
extend [T] where T: Printable { ... }
Extensions cannot:
- Add fields to types
- Implement traits (use
impl Type: Traitinstead) - Override existing methods
- Add static methods (methods without
self— use inherentimplinstead)
Extension Import
Explicit Import Required
Extension methods are NOT automatically available. They must be explicitly imported:
extension std.iter.extensions { Iterator.count, Iterator.sum }
Import Syntax
extension "module_path" { Type.method, Type.other_method }
extension "./local_ext" { Point.distance }
Method-level granularity is required. Wildcard imports are not supported:
// ERROR: wildcards not allowed
extension std.iter.extensions { Iterator.* }
// OK: explicit methods
extension std.iter.extensions { Iterator.count, Iterator.sum, Iterator.last }
Visibility
Extensions follow normal visibility rules. Visibility is block-level: all methods in a pub extend block are public; all methods in a non-pub extend block are module-private. Individual method visibility modifiers are not supported.
// In extensions.ori
pub extend Iterator {
@count (self) -> int = ... // Publicly importable
}
extend Iterator {
@internal_helper (self) -> int = ... // Module-private
}
Value Semantics
Extension methods receive self by value and return new values (Ori has no in-place mutation — values are replaced, not modified):
extend [T] {
@with_first (self, value: T) -> [T] = [value, ...self]
}
---
## Method Resolution
### Resolution Order
When calling `value.method()`:
1. **Inherent methods** — methods defined in the type's `impl` block
2. **Trait methods** — methods from implemented traits
3. **Extension methods** — methods from imported extensions
Earlier entries shadow later ones.
### Conflict Resolution
If multiple imported extensions define the same method for a type:
```ori
extension "./ext_a" { Point.distance }
extension "./ext_b" { Point.distance }
let p = Point { x: 1, y: 2 }
p.distance() // ERROR: ambiguous extension method
Resolve ambiguity with qualified syntax:
use "./ext_a" as ext_a
ext_a.Point.distance(p)
No Implicit Override
Extensions cannot override inherent or trait methods:
type Point = { x: int, y: int }
impl Point {
@distance (self) -> float = ...
}
extend Point {
@distance (self) -> float = ... // ERROR: cannot override inherent method
}
Orphan Rules
Same-Package Rule
An extension must be defined in the same package as either:
- The type being extended, OR
- At least one trait bound in a constrained extension
// In my_package
// OK: extending local type
type MyType = { ... }
extend MyType { @helper (self) -> int = ... }
// OK: extending foreign type with local trait bound
extend [T] where T: MyLocalTrait { @special (self) -> int = ... }
// ERROR: extending foreign type without local trait
extend std.collections.HashMap { @my_method (self) -> int = ... }
Rationale
The orphan rule prevents:
- Global method pollution
- Conflicting extensions in different packages
- Surprising behavior from transitive dependencies
Scoping
File-Level Scope
Extension imports are scoped to the file where they appear:
// file_a.ori
extension std.iter.extensions { Iterator.count }
items.iter().count() // OK
// file_b.ori
items.iter().count() // ERROR: count not imported
No Transitive Export
Extension imports do not propagate through re-exports:
// lib.ori
extension std.iter.extensions { Iterator.count }
pub use "./helper" { process } // count not re-exported
// main.ori
use "my_lib" { process }
items.iter().count() // ERROR: count not available
To make an extension available, re-export the extension:
// lib.ori
pub extension std.iter.extensions { Iterator.count }
Generic Extensions
Both angle bracket and where clause syntax are valid for generic extensions (see Constrained Extensions above).
Associated Type Constraints
extend Iterator where Self.Item: Printable {
@print_all (self) -> void = for item in self do print(msg: item.to_str())
}
Examples
Standard Library Pattern
// std/iter/extensions.ori
pub extend Iterator {
@count (self) -> int = ...
@last (self) -> Option<Self.Item> = ...
@nth (self, n: int) -> Option<Self.Item> = ...
}
pub extend DoubleEndedIterator {
@rfind (self, predicate: (Self.Item) -> bool) -> Option<Self.Item> = ...
}
User Extension
// my_extensions.ori
pub extend str {
@word_count (self) -> int = self.split(sep: " ").count()
}
// main.ori
extension "./my_extensions" { str.word_count }
let text = "hello world example"
text.word_count() // 3
Error Messages
Ambiguous Extension
error[E0850]: ambiguous extension method
--> src/main.ori:10:5
|
10 | point.distance()
| ^^^^^^^^^^^^^^^^ `distance` is defined in multiple extensions
|
= note: candidates from:
- extension "./ext_a" { Point.distance }
- extension "./ext_b" { Point.distance }
= help: use qualified syntax: ext_a.Point.distance(point)
Extension Not Imported
error[E0851]: method `count` not found on `Iterator`
--> src/main.ori:5:10
|
5 | items.iter().count()
| ^^^^^ method not found
|
= note: `count` is an extension method in `std.iter.extensions`
= help: add `extension std.iter.extensions { Iterator.count }`
Orphan Violation
error[E0852]: cannot define extension for foreign type
--> src/ext.ori:1:1
|
1 | extend HashMap { ... }
| ^^^^^^^^^^^^^^ `HashMap` is defined in `std.collections`
|
= note: extensions must be in the same package as the type
= help: define a newtype wrapper or use a local trait bound
Spec Changes Required
Update 12-modules.md
Expand Extensions section with:
- Complete resolution order
- Conflict resolution rules
- Orphan rules
- Scoping rules
Update 08-declarations.md
Add extension definition section with:
- Syntax and semantics
- Constraint syntax
- Visibility rules
Summary
| Aspect | Behavior |
|---|---|
| Import style | Explicit, method-level granularity |
| Resolution order | Inherent > Trait > Extension |
| Conflicts | Compile error, use qualified syntax |
| Orphan rule | Same package as type or trait bound |
| Scope | File-level, no transitive propagation |
| Override | Not allowed |