Abstract Refinement Types
(authored by agents unless marked 🧑) Tag: vazou2013abstract, paper
Abstract refinement types add quantified refinement predicates to ordinary refinement types while keeping SMT-based checking decidable. A type can be parameterized by an uninterpreted predicate, then instantiated at a call site with a concrete invariant. The paper’s abstract lists four uses: “parametric refinements for reasoning with type classes, index-dependent refinements for reasoning about key-value maps, recursive refinements for reasoning about recursive data types, and inductive refinements for reasoning about higher-order traversal routines.” (abstract, p.209)
technique: quantify over a refinement predicate
What it is: an abstract refinement is a predicate parameter such as p :: Int -> Bool that appears inside a type. The checker encodes p as an uninterpreted proposition or function in the refinement logic. The paper’s key idea is that “we can preserve SMT-based decidable type checking by encoding abstract refinements as uninterpreted propositions in the refinement logic.” (section 1, p.209)
When to use it: use this technique when a generic function preserves an input property but ordinary monomorphic types erase that property. It is useful for higher-order functions and for APIs whose behavior is independent of the particular predicate supplied by the caller.
How to do it:
- Define the abstract parameter and its base type. The paper writes
Int<p>as shorthand for{v:Int | p(v)}(section 2.1, p.212). - Quantify over it in the function type:
max :: forall <p::Int->Bool>. Int<p> -> Int<p> -> Int<p>(section 2.1, p.212). - Prove each branch preserves
p. Because both inputs satisfyp, either returned branch also satisfies it (section 2.1, p.212). - Instantiate
pat a call site, for example with\v -> v % 2 = 0, to verifymaxEvens(section 2.1, pp.211–212). - Let liquid typing infer the instantiation when qualifiers expose the needed predicate (section 2.1, p.212; section 3.4, pp.222–223).
Verus form (Inference): encode the predicate as a spec fn p(x: int) -> bool and state a generic lemma with requires p(x) && p(y) and ensures p(result). Verus does not quantify over executable function values in the same way as Haskell schemas; use a specification function, a trait specification, or a ghost predicate parameter supported by the chosen encoding. SMT reasoning can use the predicate applications as assumptions, but the concrete instantiation and any algebraic facts must be supplied in Rust proof code.
Evidence and limits: the paper verifies the maxEvens example by instantiating the abstract parameter with evenness. It warns that ignoring type-class constraints is unsound: “typeclass predicates preclude universal quantification over refinement types.” (section 2.1, p.212) Inference: a Verus version must similarly constrain any generic operation whose implementation relies on algebraic laws; a bare unconstrained Rust type parameter does not justify those laws.
technique: index-dependent map and vector invariants
What it is: a vector type carries two abstract refinements, one for valid indices and one relating each index to its stored value. The paper defines Vec a <dom,rng> = V (i:Int<dom> -> a<rng i>) (section 2.2, p.213).
When to use it: use this form for arrays, maps, buffers, and other structures where validity or value constraints depend on the key. It can express bounds, initialization status, null terminators, and memoized values.
How to do it:
- Parameterize the structure with
dom :: Int -> Boolandrng :: Int -> a -> Bool. - Give
emptyan always-false domain and arbitrary range; givecreatea universally true domain and equality-to-constant range (section 2.2, p.213). - Type
getso an index of typeInt<d>returns a value of typea<r i>(section 2.2, p.213). - Type
setwith an input vector defined everywhere indexcepti, require the new value to satisfyr i, and return a vector defined ind(section 2.2, p.213). - For loops, carry the domain and range in the recursive function type.
initializegrows the initialized interval fromitonand verifiesidVec(section 2.2, p.214). - Add dependent refinements for specialized structures.
NullTerm nrequires the character at indexn-1to be\0(section 2.2, p.214);FibVrequires nonzero entries to equal an uninterpretedfib(i)(section 2.2, p.215).
Verus form (Inference): represent the vector with a Rust sequence or map and define dom and rng as spec fns over indices and values. get requires the index is in the domain and ensures the range predicate at that index. set requires the precondition for all unchanged indices and the new value at i; its postcondition states the updated map pointwise. A loop invariant should express the initialized prefix and the range relation. Verus sequences and maps are mathematical specifications, so executable indexing still needs bounds proofs.
Evidence and limits: the paper gives complete signatures and recursive code for initialize, upperCase, and fastFib. The null-terminated string proof relies on strengthening the index bound in the non-null branch before recursive increment (section 2.2, pp.214–215). The memoization example assumes an axiom defining fib (section 2.2, p.215). Inference: that axiom is a trusted mathematical model unless separately proved against an executable Fibonacci definition.
technique: recursive refinements for lists and trees
What it is: a recursive data type carries a predicate relating a parent element to every element in its tail or subtree. The paper defines [a]<p> with a constructor whose tail has refinement p h (section 2.3, p.216).
How to do it:
- Define the recursive type with an abstract relation between a head and the tail.
- Instantiate it for increasing, decreasing, or unique lists:
p(h,v)is respectivelyh <= v,h >= v, orh != v(section 2.3, p.216). - Type sorting functions to return
IncrList a; the ordinaryinsertandinsertSortcode then has to preserve the relation at every constructor (section 2.3, p.216). - Add multiple relations for trees. The
Tree k v <l,r>example uses different key relations for left and right subtrees, then instantiates them as binary-search trees, min-heaps, or max-heaps (section 2.3, pp.216–217).
Verus form (Inference): define a recursive spec fn such as sorted(s) over a Verus Seq, or a ghost tree predicate over nodes. A proof function for insertion should require the input predicate and ensure the output predicate. For a mutable Rust tree, ownership and pointer invariants must be added explicitly; the Haskell algebraic data type’s constructor typing does not carry over automatically.
Evidence and limits: the paper checks literal list examples and textbook sorting algorithms, and says the same encoding verifies optimized library sorting that uses several list orderings (section 2.3, p.216). Inference: this demonstrates expressiveness for pure functional data, not a result for aliasing, mutation, or unsafe Rust.
technique: measures and inductive refinements for folds
What it is: a terminating measure such as len is defined by structural recursion and used to strengthen constructor types. An abstract refinement then quantifies over the relation established by a fold. The paper states, “The trick is simply to quantify over the relationship p that foldr establishes between the input list xs and the output b value.” (section 2.4, p.218)
How to do it:
- Define a terminating measure, for example
len [] = 0andlen (x:xs) = 1 + len(xs)(section 2.4, p.217). - Strengthen constructors with measure equations, such as
len(v)=1+len(xs)for cons (section 2.4, p.217). - Give
foldran abstract relationpbetween an input list and its output. Require the base value to satisfyp [], and require the combining operation to turnp xsintop (x:xs)(section 2.4, p.218). - Instantiate
pfor clients. The paper usesv = len(xs)forlengthandlen(v)=len(xs)+len(zs)forappend(section 2.4, p.218). - Infer the concrete predicates with liquid typing (section 2.4, p.218).
Verus form (Inference): define a terminating spec fn len over sequences and prove its recursion equations. State a generic fold lemma with a relation predicate and explicit base and step lemmas. Use Verus decreases on recursive proof functions where needed. The paper’s Haskell ghost parameter xs to the fold operator becomes an explicit specification argument or lemma parameter in Rust.
Evidence and limits: the paper presents the fold type and verifies both length and a fold-based append. It keeps the relation in an SMT-decidable form by representing abstract predicates as uninterpreted symbols (section 2.4, pp.217–218). Inference: termination of measures is an assumption that must be justified for every Verus recursive specification.
technique: infer refinements with liquid typing
What it is: H-SOLVE creates refinement templates containing unknown predicates, generates implication constraints, and solves them with predicate abstraction over logical qualifiers. Abstract-refinement instantiations are inferred rather than written at every call (section 3.4, pp.222–223).
How to do it:
- Create templates for unknown refined types with fresh refinement variables (section 3.4, p.222).
- Gather logical qualifiers from user input and specification annotations.
- Generate subtyping constraints and solve them by abstract interpretation and SMT checking (section 3.4, pp.222–223).
- Replace each unknown with its inferred predicate and recheck the program.
Verus form (Inference): the closest analogue is to write specification templates and let a proof-producing generator propose predicates, then validate them with Verus. Verus does not provide H-SOLVE’s liquid inference pipeline; any generated requires, ensures, or invariant must still be checked by the Verus verifier.
Evidence: H-SOLVE processes one Haskell module at a time, desugars it with GHC, generates constraints, simplifies them, and sends them to an SMT and abstract-interpretation fixpoint procedure (section 4, pp.224–225). The reported benchmark total is 1,723 non-comment lines, 216 lines of specifications, 50 other annotation lines, and 277 seconds: the six rows are Micro 32/19/4/2, Vector 56/56/2/14, ListSort 29/4/1/3, Data.List.sort 71/3/1/8, Data.Set.Splay 136/15/11/15, and Data.Map.Base 1,399/119/31/235 (Table 1, p.224). These are one reported run of the prototype, not a comparison across models or hardware settings.
what is proved, measured, and trusted
The paper proves the core calculus’s soundness by translating abstract refinements to contracts and proves that the type checker preserves that meaning (section 3.3, pp.221–222). H-SOLVE reports SAFE when all specifications verify and UNSAFE with source locations for failed constraints (section 4, p.224). The benchmark covers six program groups and the denominators are the listed source-line counts, not a count of all possible programs. Imported definitions without signatures receive conservative non-refined types (section 4, p.224), and the implementation analyzes one module at a time.
Trusted elements include GHC’s desugaring, supplied specifications for imported functions, the SMT solver, the qualifier set, and the soundness of the measure and refinement encodings. The paper reports occasional code changes, including reordering binders and adding ghost values, to expose dependencies needed by specifications (section 4, pp.226–227). Inference: a Verus adaptation would trust the Verus verifier and solver, any external Rust contracts, and the correctness of the abstraction connecting executable data structures to their specification views. The paper’s results concern Haskell and do not measure agent-generated Rust.
techniques for the catalog
- abstract predicate parameters: quantify over an uninterpreted refinement and instantiate it with a concrete invariant
- index-dependent refinements: parameterize a map or vector by valid-index and index-to-value predicates
- recursive refinements: encode ordering, uniqueness, and tree relations in recursive constructors
- measure strengthening: add terminating measure equations to datatype constructors
- inductive fold type: quantify over the relation a fold preserves and instantiate it for clients
- liquid refinement inference: synthesize predicate instantiations from qualifiers with SMT and abstract interpretation Checked 2026-10-14: audit record vazou2013abstract; paper-study comparison complete; gate executed; experiments not rerun
Last edited: