Refinement Types
Attach logical predicates to types so the compiler can prove constraints at compile time.
Refinement Types
A refinement type narrows a base type with a logical predicate. The compiler uses an SMT solver (Z3) to check that values satisfy the predicate at compile time, catching out-of-range errors before the program runs.
Syntax
type Positive = { x: i32 | x > 0 }
type Percentage = { p: f64 | p >= 0.0 && p <= 100.0 }
type NonEmpty<T> = { arr: [T] | len(arr) > 0 }
The predicate after the | must be a pure boolean expression over the bound variable and constants. The compiler delegates this to Z3 for satisfiability checking.
Using refinement types
fn safe_divide(a: f64, b: { b: f64 | b != 0.0 }) -> f64 {
a / b
}
fn main() -> i32 with IO {
let result = safe_divide(10.0, 3.0) // OK: 3.0 != 0.0 proven
println(result)
// safe_divide(10.0, 0.0) // compile error: cannot prove 0.0 != 0.0
}
When a literal is passed, the solver can verify the constraint immediately. When a variable is passed, the solver uses the variable’s known refinements from its declaration site.
Dosing example
Refinement types are especially powerful in scientific domains where physical constraints matter:
type ValidDose = { dose: mg | dose > 0.0 && dose <= 2000.0 }
fn administer(patient_id: i64, dose: ValidDose) with IO {
println("Administering dose")
}
fn main() -> i32 with IO {
let dose: mg = 500.0
administer(1, dose) // OK: 500.0 within range
// let bad: mg = 5000.0
// administer(1, bad) // compile error: 5000.0 > 2000.0
}
Interaction with epistemic types
Refinement predicates compose with Knowledge<T>: a Knowledge<ValidDose> carries both the uncertainty envelope and the refinement constraint. The compiler checks both independently — the refinement holds on the nominal value, and confidence propagation tracks measurement uncertainty.
Where refinement checking lives
- Refinement type checking:
self-hosted/check/refinements.sio - SMT integration:
compiler/src/smt/(Z3 bindings, requiressmtfeature flag) - The checked native artifact (
souc-linux-x86_64) has SMT disabled; refinement checking is available when building with--features smt
Current limitations
- Z3 integration requires the
smtfeature flag at build time. - Not all predicate forms are supported — the solver handles arithmetic comparisons, boolean connectives, and array length constraints. Custom function calls in predicates are not yet supported.
- Runtime assertion fallback is available for predicates that cannot be statically verified.