Contract Dependencies and Intersections¶
Accepted
Accepted for V1 contract dependencies, dependency upcasts, and semantic-contract intersections; depends on Semantic Contracts and Contract Dispatch.
Contract Dependencies¶
Contracts may depend on other semantic contracts by listing base contracts in contract(...):
fn MutableSequence(comptime T: Type) => contract(Sequence(T), MutableIndexable(T)) {
}
Dependency rules:
- dependencies reference semantic contracts only, not
satisfies(...)shapes - dependency graphs must be acyclic
- a type implementing a derived contract must also satisfy required base contracts
- a derived implementation may establish the base conformance if it provides or resolves all base operations
- implied base conformances must be coherent and unique
- a visible derived conformance implies visible base conformances recursively
- if explicit and implied base conformances are both visible, they must resolve to one effective base conformance
- competing effective base conformances are ambiguous and fail compilation
- base contract operations keep their declaring operation identity
- duplicate operation names inherited from unrelated base contracts do not merge just because their signatures match
- duplicate requirements through the same shared ancestor are one inherited requirement
Static constraint parameters and dynamic contract values may upcast along dependencies:
*dyn Derived -> *const dyn Base
*impl MutableSequence(f32) -> *const impl Sequence(f32) // static parameter form
Pointer const coercions compose with dependency upcasts. Dynamic dependency upcasts are valid only between dyn-safe applied contracts:
- borrowed dynamic pointers may upcast implicitly in expected-type contexts or explicitly through an
upcast(C)operation. - implicit dynamic pointer upcasts do not choose a target contract in otherwise ambiguous expression contexts; use explicit
upcast(C)when no expected type determines the target. - owned dynamic boxes never upcast implicitly; they use explicit consuming
upcast(C)onBox(dyn D). - dynamic upcast validity is checked at compile time from the represented contract type and target contract;
upcastis not a runtime-fallible dynamic cast.
Dynamic dependency upcasts operate on the represented erased surface. They do not re-run hidden concrete conformance lookup in the current scope.
Representation rules:
- if a
*dyn Derivedvalue already carries vtable metadata for a declared dependencyBase, upcasting to*dyn Baseor*const dyn Baseselects that represented dependency metadata. - the dependency conformance had to be visible when the original dynamic object metadata was constructed.
- later upcasts do not fail merely because the hidden concrete conformance would not be visible from the current scope.
- dynamic intersection subset upcasts follow the same rule:
*dyn (A & B)to*dyn Aselects the representedAcomponent metadata instead of re-checking hidden concrete conformance visibility. - if source and target intersections normalize to the same component set, the upcast is a same-surface metadata selection rather than a cast to a different dynamic surface.
Same-surface examples include *dyn (A & B) vs *dyn (B & A), and targets that redundantly spell an implied base that normalization removes.
Dynamic upcasts must be allocation-free metadata selection.
Representation requirements:
- upcasts may select or drop represented vtable pointers or descriptors.
- upcasts must not allocate, synthesize new vtables at runtime, or rebuild descriptor state.
- a representation that cannot support borrowed pointer or owned box upcasts by selecting represented metadata is not an acceptable V1 dynamic representation.
Base contract operations are part of the derived contract surface. A derived implementation may provide required operations from base contracts when the operation name is unambiguous. That lets one implementation establish the derived contract and its base contracts together.
If multiple base contracts contribute unrelated operations with the same name, the derived contract may still compose them, but the operation identities remain distinct.
Conflict rules:
- unqualified method lookup with that name is ambiguous.
- unqualified implementation-body functions with that name are ambiguous.
When this happens, implement the conflicting base contracts separately, then provide the derived conformance:
const ResetPosition = contract {
fn reset(self: *Self) void
}
const ResetStats = contract {
fn reset(self: *Self) void
}
const ResettableThing = contract(ResetPosition, ResetStats) {
}
impl Thing as ResetPosition {
fn reset(self: *Self) void {
}
}
impl Thing as ResetStats {
fn reset(self: *Self) void {
}
}
impl Thing as ResettableThing {
}
In a diamond, both paths may refer to the same original base operation identity:
const Base = contract {
fn reset(self: *Self) void
}
const A = contract(Base) {
}
const B = contract(Base) {
}
const C = contract(A, B) {
} // ok: one inherited Base.reset operation
A derived contract may provide default implementations for required operations from its base contracts when the operation is unambiguous. That satisfies the base operation; it does not create a second unrelated method.
If the derived contract declares an operation with the same name as a base operation, it must resolve to a deliberate override of exactly one operation identity. Ambiguous same-name overrides are rejected in V1.
Selection between visible explicit base conformances and base defaults is inferred by sema. Dedicated syntax for selecting a particular base conformance is deferred until a real ambiguity requires it.
Qualified implementation-body operation syntax, such as defining A.reset and B.reset inside a single derived impl, is deferred. Separate base impls are the V1 resolution path for same-name semantic conflicts.
Intersections¶
Intersections compose contracts:
Sequence(f32) & RealtimeSafe
Static intersections require the concrete type to implement all components. Dynamic intersections use the multi-vtable object representation described above.
Intersections normalize deterministically:
A & A == A
A & B == B & A
(A & B) & C == A & B & C
Duplicate components and implied base contracts normalize away. Redundant spelling should produce a fixable lint.
Method-name collisions in intersections are lookup-time errors, not invalid types. Normalization order never chooses one collided operation implicitly. The user must call through qualified syntax to select the intended contract operation.
Dynamic intersection objects may upcast to any subset of their represented components by dropping unused vtable pointers. Reordered spellings and redundant implied-base spellings that normalize to the same represented component set are equivalent upcast targets.