Chapter 9 stopped at the relational obligation 0≤𝑖<𝗅𝖾𝗇(𝑎): its finite semantic types could classify an array operation but could not relate one runtime index to one runtime length. This chapter takes that inequality as its entire assertion logic. A refinement type attaches a logical predicate to the values of an ordinary type; the subtyping judgment compares such types under a logical context.
The type 𝗂𝗇𝗍 says that an array index is an integer. It does not say that the index belongs to the particular array being read. For example, the simply typed function (𝜆𝑎.𝗅𝖾𝗍𝑛=𝗅𝖾𝗇𝑎𝗂𝗇𝗅𝖾𝗍𝑧=𝗀𝖾𝗍𝑎𝑛𝗂𝗇𝑧:𝑎:𝖺𝗋𝗋→𝗂𝗇𝗍) gets stuck on every array: its index is exactly one past the last position. An intersection or a fixed subtype of integers cannot repair the program, because the missing upper bound is the run-time measure 𝗅𝖾𝗇𝑎.
Refine an integer by conjunctions of difference constraints 𝑥−𝑦≤𝑘. The bounds 0≤𝑖<𝗅𝖾𝗇(𝑎) then become two paths in a finite weighted graph, and the checker replays those paths without trusting the search procedure.
Every intermediate computation is named by a let, and every computation operand is already an atom—a variable or value. Consequently a closed nonfinal term has at most one active redex, at its outer let or conditional. Integer literals range over all of ℤ, array literals contain integers, and every function value carries its full arrow annotation. 𝐵::=𝗂𝗇𝗍∣𝖺𝗋𝗋,𝑣::=𝑛∣⟨𝑛0,…,𝑛𝑘−1⟩∣(𝜆𝑥.𝑒:𝑥:𝑠→𝑡)∣(𝖿𝗂𝗑𝑓(𝑥).𝑒:𝑥:𝑠→𝑡),𝑎::=𝑥∣𝑣,𝑐::=𝑎+𝑘∣𝗅𝖾𝗇𝑎∣𝗀𝖾𝗍𝑎1𝑎2∣𝑎1𝑎2,𝑒::=𝑎∣𝗅𝖾𝗍𝑥=𝑐𝗂𝗇𝑒∣𝗂𝖿𝛿𝗍𝗁𝖾𝗇𝑒1𝖾𝗅𝗌𝖾𝑒2∣𝖾𝗋𝗋𝗈𝗋. Here 𝑘 is a literal constant, so 𝑎+𝑘 is the only arithmetic primitive. This restriction matters: the exact relation 𝑧=𝑥+𝑘 is expressible by two difference constraints, whereas 𝑧=𝑥+𝑦 is not. Each test 𝛿 is one difference constraint, so taking a branch adds a predicate in the same logic. The term 𝖾𝗋𝗋𝗈𝗋 is a checked failure, not a value. The conditional scrutinee 𝛿 is a logical difference atom, not a Boolean-valued term. At runtime all its vertices have integer values, and the truth value of the resulting inequality selects the branch.
This is the standard difference-logic fragment: formulas built from conjunctions of inequalities 𝑥−𝑦≤𝑘. Allowing arbitrary addition such as 𝑧=𝑥+𝑦 would leave this fragment for Presburger arithmetic, which is still decidable but requires a substantially heavier procedure. Allowing products of variables leaves difference logic and would require a different arithmetic theory and certificate format; no decision claim for that extension is made here. The narrow fragment is intentional: it lets us state and prove the complete certificate checker rather than trust an opaque solver.
Function application is a computation and must therefore occur on the right-hand side of a let. To reduce such an application without leaving the A-normal grammar, define bind composition𝑒1▹𝑥𝑒2 by recursion on 𝑒1: 𝑎▹𝑥𝑒2=𝑒2[𝑎/𝑥],(𝗅𝖾𝗍𝑦=𝑐𝗂𝗇𝑒)▹𝑥𝑒2=𝗅𝖾𝗍𝑦=𝑐𝗂𝗇(𝑒▹𝑥𝑒2),(𝗂𝖿𝛿𝗍𝗁𝖾𝗇𝑒0𝖾𝗅𝗌𝖾𝑒1)▹𝑥𝑒2=𝗂𝖿𝛿𝗍𝗁𝖾𝗇(𝑒0▹𝑥𝑒2)𝖾𝗅𝗌𝖾(𝑒1▹𝑥𝑒2),𝖾𝗋𝗋𝗈𝗋▹𝑥𝑒2=𝖾𝗋𝗋𝗈𝗋. Bound variables are renamed before the second clause when necessary. Bind composition substitutes an A-normal computation into its continuation while preserving the A-normal grammar.
Reduction is defined on closed terms. Closed atoms are values. For a literal array 𝐴 with entries 𝑛0,…,𝑛𝑚−1, the rules are as follows.
𝑞=𝑛+𝑘inℤ
𝗅𝖾𝗍𝑥=𝑛+𝑘𝗂𝗇𝑒⟼𝑒[𝑞/𝑥]
E-Shift
𝗅𝖾𝗍𝑥=𝗅𝖾𝗇𝐴𝗂𝗇𝑒⟼𝑒[𝑚/𝑥]
E-Len
0≤𝑖<𝑚
𝗅𝖾𝗍𝑥=𝗀𝖾𝗍𝐴𝑖𝗂𝗇𝑒⟼𝑒[𝑛𝑖/𝑥]
E-Get
𝗅𝖾𝗍𝑦=(𝜆𝑥.𝑒1:𝑥:𝑠→𝑡)𝑣𝗂𝗇𝑒2⟼𝑒1[𝑣/𝑥]▹𝑦𝑒2
E-Beta
𝐹=(𝖿𝗂𝗑𝑓(𝑥).𝑒1:𝑥:𝑠→𝑡)
𝗅𝖾𝗍𝑦=𝐹𝑣𝗂𝗇𝑒2⟼𝑒1[𝐹/𝑓,𝑣/𝑥]▹𝑦𝑒2
E-Fix
𝛿istrue
𝗂𝖿𝛿𝗍𝗁𝖾𝗇𝑒1𝖾𝗅𝗌𝖾𝑒2⟼𝑒1
E-IfT
𝛿isfalse
𝗂𝖿𝛿𝗍𝗁𝖾𝗇𝑒1𝖾𝗅𝗌𝖾𝑒2⟼𝑒2
E-IfF
Term substitution acts inside 𝛿 using the normalizing predicate substitution of definition 10.3. After closing substitution, every integer variable and array-length vertex has therefore become an integer literal, and a test normalizes to a comparison between integer constants. Exactly one of E-IfT and E-IfF applies. There is deliberately no rule for an out-of-bounds 𝗀𝖾𝗍. Such a term is stuck, and ruling it out will be the safety theorem rather than a convention inside evaluation.
Proof of Proposition 10.2 — The unguarded last index is stuck
Proof.𝗅𝖾𝗇⟨7⟩=1, so E-Len yields 𝗅𝖾𝗍𝑧=𝗀𝖾𝗍⟨7⟩1𝗂𝗇𝑧. Its only possible rule is E-Get; its side condition is 0≤1<1, which is false. No other rule has that head form. ◻
The stuck term is the calculus’s formal proxy for an unchecked bounds fault. Nothing here predicts whether a host implementation would trap, raise an exception, or perform an unsafe memory access; those machine behaviors are outside this source semantics. Array safety means that no reachable source term has this stuck get form.
★★★ Assume 𝑥≠𝑦, 𝑦∉fv(𝑒0), and 𝑥∉fv(𝑒2). Prove by induction on 𝑒0 that (𝑒0▹𝑥𝑒1)▹𝑦𝑒2=𝑒0▹𝑥(𝑒1▹𝑦𝑒2), after the capture-avoiding renamings demanded by the definition. Treat the conditional and 𝖾𝗋𝗋𝗈𝗋 clauses explicitly. For the atom case, first prove by induction on 𝑒1 the auxiliary commutation equation 𝑒1[𝑎/𝑥]▹𝑦𝑒2=(𝑒1▹𝑦𝑒2)[𝑎/𝑥] under the stated freshness hypotheses.
Write 𝐿𝑎 as an abbreviation for the measure 𝗅𝖾𝗇(𝑎); it is not a new program variable. In a scope, the vertices available to predicates are 𝑟::=𝟎∣𝑥∣𝐿𝑎, where 𝑥 and 𝑎 range over raw term-variable names and 𝟎 denotes the integer zero. Grammar alone does not assign a shape to those names: definition 10.4 admits 𝑥 only for an integer-shaped declaration and 𝐿𝑎 only for an array-shaped declaration. A difference atom and a predicate are 𝛿::=𝑟1−𝑟2≤𝑘,𝑝::=𝗍𝗋𝗎𝖾∣𝛿∧𝑝. Thus predicates are finite conjunctions only. We use the derived writings 𝑟1<𝑟2 for 𝑟1−𝑟2≤−1, 𝑟1≤𝑟2 for 𝑟1−𝑟2≤0, and 𝑟1=𝑟2 for the conjunction 𝑟1−𝑟2≤0∧𝑟2−𝑟1≤0. Negating one atom stays in the language because integers are discrete: ――――――𝑟1−𝑟2≤𝑘:=𝑟2−𝑟1≤−𝑘−1. There is no disjunction, multiplication, sum of two vertices, array equality, or arbitrary quantifier-free linear arithmetic.
For a base-type binder 𝜈, its observable vertex is 𝜇𝗂𝗇𝗍(𝜈)=𝜈 and 𝜇𝖺𝗋𝗋(𝜈)=𝐿𝜈. Types are 𝑡::={𝜈:𝐵∣𝑝}∣𝑥:𝑠→𝑡. A refinement type {𝜈:𝐵∣𝑝} restricts the values of base type 𝐵 to those whose distinguished binder 𝜈 satisfies 𝑝. The binder 𝑥 of an arrow may occur in the codomain only through 𝑥 when 𝑠 has integer shape, or through 𝐿𝑥 when 𝑠 has array shape. A function-typed 𝑥 has no predicate vertex. We abbreviate {𝜈:𝐵∣𝗍𝗋𝗎𝖾} by 𝐵.
Literal substitution is normalized back into the grammar. Replacing an integer variable by 𝑛 replaces its vertex by 𝟎+𝑛; replacing 𝐿𝑎 by an array literal of length 𝑚 replaces it by 𝟎+𝑚; constants are moved to the right of ≤. For instance, (𝑥−𝐿𝑎≤−1)[3/𝑥,⟨4,5⟩/𝑎]normalizesto𝟎−𝟎≤−2. Substitution for function variables changes no predicate. Under these clauses, 𝑡[𝑎/𝑥] is total and capture avoiding for every well-shaped atom 𝑎.
A context is a list Γ::=⋅∣Γ,𝑥:𝑡∣Γ,𝛿 with distinct term variables. Its vertex scope 𝑉(Γ) contains 𝑥 for an integer-shaped entry, 𝐿𝑥 for an array-shaped entry, and no vertex for a function-shaped entry. If 𝑝 is a conjunction, Γ,𝑝 abbreviates the context obtained by appending its atoms in order. The following rules define well-formedness; “𝑝 over 𝑊” means that every vertex of 𝑝 belongs to 𝑊∪{𝟎}. Γ⊧𝖺𝗅𝗅𝑝 means that every valuation satisfying the refinements and guards in Γ also satisfies 𝑝. By contrast, Γ⊢𝑎⇒𝑡 is a syntactic typing judgment.
⋅𝖼𝗍𝗑
WF-Empty
Γ𝖼𝗍𝗑Γ⊢𝑡𝗍𝗒𝗉𝖾𝑥∉dom(Γ)
Γ,𝑥:𝑡𝖼𝗍𝗑
WF-Var
Γ𝖼𝗍𝗑𝛿over𝑉(Γ)
Γ,𝛿𝖼𝗍𝗑
WF-Guard
Γ𝖼𝗍𝗑𝑝over𝑉(Γ)∪{𝜇𝐵(𝜈)}
Γ⊢{𝜈:𝐵∣𝑝}𝗍𝗒𝗉𝖾
WF-Base
Γ⊢𝑠𝗍𝗒𝗉𝖾Γ,𝑥:𝑠⊢𝑡𝗍𝗒𝗉𝖾
Γ⊢𝑥:𝑠→𝑡𝗍𝗒𝗉𝖾
WF-Arrow
The shape |𝑡| is obtained by erasing predicates: |{𝜈:𝐵∣𝑝}|=𝐵 and |𝑥:𝑠→𝑡|=|𝑠|→|𝑡|.
The type 𝑎:{𝜈:𝖺𝗋𝗋∣𝟎−𝐿𝜈≤−1}→𝗂𝗇𝗍 is well formed: its domain says 1≤𝗅𝖾𝗇(𝑎). In contrast, 𝑎:𝖺𝗋𝗋→{𝜈:𝗂𝗇𝗍∣𝑎−𝜈≤0} is not a type, since an array variable is not an integer vertex. Nor can a refinement say that two arrays are equal; only their lengths are observable.
★☆☆ Determine which of the following are well formed in the empty context. For each rejection, say first whether the displayed predicate is grammatical and, if it is, give the failed scope check: (𝑎)𝑎:𝖺𝗋𝗋→{𝜈:𝗂𝗇𝗍∣𝜈−𝐿𝑎≤−1},(𝑏)𝑖:𝗂𝗇𝗍→{𝜈:𝖺𝗋𝗋∣𝐿𝜈−𝑖≤0},(𝑐)𝑓:(𝗂𝗇𝗍→𝗂𝗇𝗍)→{𝜈:𝗂𝗇𝗍∣𝜈−𝑓≤0},(𝑑){𝜈:𝖺𝗋𝗋∣𝐿𝜈−𝟎≤4}.
A valuation 𝜌 assigns an integer to every integer variable and an integer array to every array variable. It interprets 𝟎 as 0 and 𝐿𝑎 as the length of 𝜌(𝑎). Satisfaction of an atom is integer comparison, and satisfaction of a conjunction is componentwise.
The predicate contributed by a base entry is obtained by replacing the distinguished vertex by the entry’s vertex: ⌊⋅⌋=𝗍𝗋𝗎𝖾,⌊Γ,𝑥:{𝜈:𝐵∣𝑝}⌋=⌊Γ⌋∧𝑝[𝑥/𝜈],⌊Γ,𝑥:(𝑦:𝑠→𝑡)⌋=⌊Γ⌋,⌊Γ,𝛿⌋=⌊Γ⌋∧𝛿. For arrays, 𝑝[𝑥/𝜈] replaces 𝐿𝜈 by 𝐿𝑥. We write 𝜌⊧Γ when 𝜌⊧⌊Γ⌋, and Γ⊧𝖺𝗅𝗅𝑝 when every valuation of the variables of Γ that satisfies Γ also satisfies 𝑝. Array valuations are actual finite arrays, so 𝟎−𝐿𝑎≤0 is valid for every array variable 𝑎. 𝜌⊧Γ is a fact about one valuation; Γ⊧𝖺𝗅𝗅𝑝 quantifies over all such 𝜌.
Let 𝑎 be a well-sorted atom of the same base shape as 𝑥, and let 𝑝 be a predicate over 𝑉(Γ,𝑥:𝑠,Δ)∪{𝟎}. For every valuation 𝜌 of Γ,Δ[𝑎/𝑥], extend 𝜌 by assigning to 𝑥 the value of 𝑎 under 𝜌. Then 𝜌⊧𝑝[𝑎/𝑥]⟺𝜌[𝑥↦𝜌(𝑎)]⊧𝑝, where the left predicate is normalized as in definition 10.3. The equivalence also holds entry by entry for the context embedding of Δ.
Proof of Lemma 22.8 — Semantic substitution for predicate vertices
Proof. It is enough to check vertices. An unrelated vertex keeps the same integer value. If 𝑥 has integer shape, substitution replaces 𝑥 by the integer representative of 𝑎, so both sides evaluate it as 𝜌(𝑎). If 𝑥 has array shape, substitution replaces 𝐿𝑥 by the length representative of 𝑎, so both sides evaluate it as 𝗅𝖾𝗇(𝜌(𝑎)). Moving the representative’s integer offset to the right of ≤ preserves the atom’s truth value. Conjunction and the ordered context embedding follow by induction on their finite lists. ◻
For example, in Γ0=𝑎:{𝜈:𝖺𝗋𝗋∣𝟎−𝐿𝜈≤−1},𝑛:{𝜈:𝗂𝗇𝗍∣𝜈−𝐿𝑎≤0∧𝐿𝑎−𝜈≤0}, we have Γ0⊧𝖺𝗅𝗅𝟎−𝑛≤−1. A satisfying valuation has 𝑛=𝐿𝑎 and 𝐿𝑎≥1. The superficially similar conclusion 𝑛−𝐿𝑎≤−1 is false: a one-element array with 𝑛=1 is a countervaluation.
Certificates for implication
Entailment is semantic, so the word “valid” is not evidence. A finite certificate must exhibit either a weighted path proving the requested bound or a negative cycle proving inconsistency. A replay check verifies the named edges, their endpoints, and their integer weights.
Ramalingam, Song, Joskowicz, and Miller formulate systems 𝑥𝑖−𝑥𝑗≤𝑏𝑖𝑗, their feasibility problem, and the corresponding negative-cycle view on pp. 261–263 [RSJM99]. Their paper studies an incremental algorithm. Definition 10.6, Definition 10.6 fix a nonincremental path-or-cycle certificate; completeness is theorem 10.9.
For a context Γ, form the weighted directed multigraph 𝐺Γ. Its vertices are 𝟎 and the vertices in 𝑉(Γ). Each hypothesis 𝑟1−𝑟2≤𝑘 contributes the edge 𝑟2𝑘→𝑟1: this includes every atom in every base-type entry of ⌊Γ⌋ and every explicit guard entry, not merely the latter. For every array vertex 𝐿𝑎, add the implicit length edge 𝐿𝑎0→𝟎, which expresses 0≤𝐿𝑎. Edges carry identifiers, so duplicate constraints remain distinguishable.
If ℎ assigns integers to vertices with ℎ(𝟎)=0, then it satisfies the graph when ℎ(𝑣)≤ℎ(𝑢)+𝑘foreveryedge𝑢𝑘→𝑣. This is exactly satisfaction of the corresponding difference constraints. Because the length edges force ℎ(𝐿𝑎)≥0, every satisfying ℎ is realized by a program valuation: assign ℎ(𝑥) to integer 𝑥 and choose, independently for each 𝑎, any integer array of length ℎ(𝐿𝑎).
For a goal 𝑟1−𝑟2≤𝑘, a path certificate is a possibly empty list of edge identifiers forming a directed path from 𝑟2 to 𝑟1 whose integer weight sum is at most 𝑘. Thus the empty list certifies 𝑟1−𝑟1≤𝑘 when 0≤𝑘. A contradiction certificate is a nonempty cyclic list of edge identifiers in 𝐺Γ whose weight sum is negative.
The checker re-reads every named edge from 𝐺Γ, checks adjacency and the required endpoints, adds weights as mathematical integers, and checks the final inequality. For a conjunctive goal it accepts either one contradiction certificate for the context or one path certificate for each conjunct. Parsing, edge lookup, adjacency, and unbounded integer addition are therefore part of the trusted checker.
If the checker accepts a path certificate for Γ⊧𝖺𝗅𝗅𝑟1−𝑟2≤𝑘, then that entailment is valid. If it accepts a contradiction certificate for Γ, then Γ has no satisfying valuation.
Proof of Lemma 10.7 — Path and negative-cycle soundness
Proof. Let 𝑢0𝑘1⟶𝑢1𝑘2⟶⋯𝑘𝑚⟶𝑢𝑚 be a checked path and let ℎ satisfy the graph. Adding the 𝑚 edge inequalities cancels the intermediate potentials and gives ℎ(𝑢𝑚)−ℎ(𝑢0)≤𝑘1+⋯+𝑘𝑚. For a path from 𝑟2 to 𝑟1 with sum at most 𝑘, this is ℎ(𝑟1)−ℎ(𝑟2)≤𝑘, the goal.
For a checked cycle, 𝑢𝑚=𝑢0, so the same addition gives 0≤𝑘1+⋯+𝑘𝑚. A negative checked sum contradicts this inequality. Hence no potential, and therefore no valuation, satisfies Γ. ◻
A finite integer-weighted graph containing the distinguished vertex 𝟎 has a satisfying integer potential ℎ with ℎ(𝟎)=0 iff it has no negative directed cycle.
Proof of Lemma 10.8 — Potentials from absence of negative cycles
Proof. For the forward implication, add the potential inequalities around any directed cycle. Intermediate terms cancel and give 0≤𝑤, where 𝑤 is the cycle’s total weight; hence no negative cycle exists. For the reverse implication, assume there is no negative cycle. Shortest paths from 𝟎 alone need not reach every vertex. Add a fresh source 𝑞∗ and a zero-weight edge from 𝑞∗ to every old vertex. For each vertex 𝑣, let 𝑑(𝑣) be the least weight of a path from 𝑞∗ to 𝑣. This least integer exists: every path may have its directed cycles removed; the removed cycles have nonnegative total weight, and only finitely many simple paths remain. For every old edge 𝑢𝑘→𝑣, appending that edge to a shortest path to 𝑢 gives 𝑑(𝑣)≤𝑑(𝑢)+𝑘. Set ℎ(𝑣)=𝑑(𝑣)−𝑑(𝟎). Subtracting the same integer preserves all edge inequalities and makes ℎ(𝟎)=0. Thus ℎ is the required integer potential. ◻
For every well-formed context Γ and difference atom 𝑟1−𝑟2≤𝑘 over 𝑉(Γ)∪{𝟎}, Γ⊧𝖺𝗅𝗅𝑟1−𝑟2≤𝑘 iff the checker accepts either a negative-cycle certificate for 𝐺Γ or a path certificate from 𝑟2 to 𝑟1 of weight at most 𝑘. Consequently, the bundle checker is sound and complete for conjunctive goals.
Proof of Theorem 10.9 — Certificate checker soundness and completeness
Proof. Soundness is lemma 10.7. For completeness, first suppose 𝐺Γ has a negative cycle. Its edge list is an accepted contradiction certificate.
Now suppose it has no negative cycle. Add to it the edge 𝑟1−𝑘−1←←←←←←←←←←→𝑟2, which represents the integer negation 𝑟2−𝑟1≤−𝑘−1 of the goal. If the augmented graph had no negative cycle, lemma 10.8 would give an integer potential satisfying both Γ and the negated goal. The length edges make every array potential nonnegative, so choose arrays of those lengths; this realizes the potential as a countervaluation, contrary to the assumed entailment. Thus the augmented graph has a negative closed walk. Decompose that walk at repeated vertices into simple directed cycles. Their weights sum to the negative total, so at least one component cycle is negative. It cannot lie entirely in the old graph, which has no negative cycle; hence it contains the single new edge, exactly once. Removing that edge leaves an old path from 𝑟2 to 𝑟1, say of weight 𝑤, and negativity says 𝑤−𝑘−1<0. Since weights are integers, 𝑤≤𝑘. This path is the required certificate.
A conjunction is valid exactly when each conjunct is valid. Applying the atomic result to every conjunct gives a path bundle, unless the common context is inconsistent, in which case its single negative cycle certifies every conjunct. ◻
Proof of Corollary 10.10 — Certificate-producing decision
Proof. On the finite graph 𝐺Γ, first search for a simple negative cycle. If one exists, its edge list is a contradiction certificate. Otherwise, compute shortest paths from the goal source 𝑟2 and compare the distance to 𝑟1 with 𝑘. A path of weight at most 𝑘 is a certificate of validity. If no such path exists, add the negated-goal edge 𝑟1−𝑘−1←←←←←←←←←←→𝑟2. The augmented graph has no negative cycle: an old one was excluded, and a new one would remove to an 𝑟2-to-𝑟1 path of weight at most 𝑘. Apply lemma 10.8 to the augmented graph. Its potential satisfies the old context and the negated goal, hence is a countervaluation. Both searches terminate because a simple path or cycle uses at most the finite number of vertices. ◻
Bellman–Ford finds a negative cycle or all relevant shortest paths in 𝑂(|𝑉||𝐸|) integer relaxations; predecessor pointers reconstruct the emitted edge list. The decision theorem depends only on termination and on replay of that evidence, not on this complexity bound.
★☆☆ For hypotheses 𝑥−𝑦≤2, 𝑦−𝑧≤−4, and 𝑧−𝑥≤1, write the three graph edges, give a contradiction certificate, and reproduce the integer sum that the checker tests. Then change the last constant to 2 and give a satisfying potential with ℎ(𝟎)=0.
Base refinement subtyping is implication. For arrows, a function of the subtype must handle every argument promised by the supertype, so the domain premise is 𝑡1<:𝑠1; codomains are compared under 𝑥:𝑡1. The printed glyph is shared with the structural subtyping relation of chapter 8, but this judgment is refinement subtyping under a logical context. No rule or law of the earlier relation is imported here.
𝑧∉dom(Γ)Γ,𝑧:{𝜈:𝐵∣𝑝}⊧𝖺𝗅𝗅𝑞[𝑧/𝜈]
Γ⊢{𝜈:𝐵∣𝑝}<:{𝜈:𝐵∣𝑞}
S-Base
Γ⊢𝑡1<:𝑠1Γ,𝑥:𝑡1⊢𝑠2<:𝑡2
Γ⊢(𝑥:𝑠1→𝑠2)<:(𝑥:𝑡1→𝑡2)
S-Arrow
All conclusion types must be well formed in their respective contexts. A function at the left arrow can be applied to every 𝑠1, whereas a context using the right arrow checks its argument at 𝑡1; hence S-Arrow requires 𝑡1<:𝑠1. Because these are the only two subtyping rules, every derivation preserves outer shape: S-Base relates base refinements and S-Arrow relates arrows. Thus “same shape” means the outer constructor equality obtained by this rule inversion.
Proof of Lemma 22.16 — Shape preservation for refinement subtyping
Proof. Induct on the subtyping derivation. Rule S-Base uses one common base type 𝐵. Rule S-Arrow has arrow types in its conclusion, and its two induction hypotheses identify the corresponding domain and codomain shapes. ◻
For example, put 𝑁0={𝜈:𝗂𝗇𝗍∣𝟎−𝜈≤0},𝑁1={𝜈:𝗂𝗇𝗍∣𝟎−𝜈≤−1},𝑅𝑥={𝜈:𝗂𝗇𝗍∣𝜈−𝑥≤1∧𝑥−𝜈≤−1}. Then 𝑧:𝑁1⊧𝖺𝗅𝗅𝟎−𝑧≤0⋅⊢𝑁1<:𝑁0S−Base𝑥:𝑁1,𝑧:𝑅𝑥⊧𝖺𝗅𝗅𝟎−𝑧≤0𝑥:𝑁1⊢𝑅𝑥<:𝑁0S−Base⋅⊢(𝑥:𝑁0→𝑅𝑥)<:(𝑥:𝑁1→𝑁0)S−Arrow. The first certificate is the single edge 𝑧→𝟎 of weight −1, accepted against the weaker bound 0. In the second premise the path 𝑧→𝑥→𝟎 has weight −1+(−1)=−2≤0. This displays both contravariance and the use of the target-domain hypothesis in the codomain.
Suppose Γ⊢𝑠′<:𝑠, and suppose the suffix Δ is well formed after either Γ,𝑥:𝑠 or Γ,𝑥:𝑠′. Every valuation satisfying Γ,𝑥:𝑠′,Δ also satisfies Γ,𝑥:𝑠,Δ. Hence:
if Γ,𝑥:𝑠,Δ⊧𝖺𝗅𝗅𝑝, then Γ,𝑥:𝑠′,Δ⊧𝖺𝗅𝗅𝑝;
any type-formation or subtyping derivation under Γ,𝑥:𝑠,Δ may be narrowed to Γ,𝑥:𝑠′,Δ.
Proof of Lemma 10.12 — Context implication and narrowing
Proof. If 𝜌 satisfies Γ,𝑥:𝑠′,Δ, the premise Γ⊢𝑠′<:𝑠 implies that 𝜌(𝑥) satisfies the base refinement in 𝑠, and the unchanged entries of Δ remain true in order. For arrow types neither entry contributes a predicate. Therefore every entailment valid under 𝑥:𝑠 is valid under 𝑥:𝑠′.
For clause 2, induct on the formation or subtyping derivation. In a base formation premise, the vertex scope is unchanged by lemma 22.16. In an S-Base premise, clause 1 transports the entailment. In the representative S-Arrow case, narrow both domain and codomain premises; alpha-rename the arrow binder away from 𝑥 and append it to Δ before applying the induction hypothesis to the codomain. ◻
For well-formed types, subtyping preserves shape: if Γ⊢𝑠<:𝑡, then |𝑠|=|𝑡|. It is reflexive: Γ⊢𝑡<:𝑡. It is transitive: if Γ⊢𝑟<:𝑠 and Γ⊢𝑠<:𝑡, then Γ⊢𝑟<:𝑡.
For reflexivity, induct on 𝑡. At a base, every valuation satisfying 𝑝[𝑧/𝜈] satisfies it, so S-Base applies. At an arrow, the domain induction hypothesis gives the contravariant premise, and the codomain hypothesis gives the covariant premise in the extended context.
For transitivity, induct on the common erased shape. At a base, suppose the predicates are 𝑝,𝑞,𝑟. A valuation satisfying 𝑝 satisfies 𝑞 by the first subtyping premise and then 𝑟 by the second; S-Base gives the result. At arrows, write the three domains 𝐴0,𝐴1,𝐴2 and codomains 𝐶0,𝐶1,𝐶2. The two derivations give 𝐴1<:𝐴0,𝐴2<:𝐴1,𝐶0<:𝐶1under𝑥:𝐴1,𝐶1<:𝐶2under𝑥:𝐴2. The domain induction gives 𝐴2<:𝐴0. Narrow the first codomain derivation from 𝑥:𝐴1 to 𝑥:𝐴2 by lemma 10.12; the codomain induction then gives 𝐶0<:𝐶2 under 𝑥:𝐴2. Rule S-Arrow assembles the desired arrow subtyping. ◻
★★☆ Derive 𝑎:𝖺𝗋𝗋⊢{𝜈:𝗂𝗇𝗍∣𝜈−𝐿𝑎≤−1∧𝟎−𝜈≤0}<:{𝜈:𝗂𝗇𝗍∣𝜈−𝐿𝑎≤0}. Give the path certificate for its only nontrivial goal. Then explain why the reverse subtyping fails by giving an array and an integer countervaluation.
A tempting checking rule would permit subsumption at every syntax node: Γ⊢𝑒⇐𝑠Γ⊢𝑠<:𝑡Γ⊢𝑒⇐𝑡bad−Sub. A top-down checker would have to guess 𝑠; choosing 𝑠=𝑡 reproduces its original goal, so the search is non-structural. Exact synthesis removes that choice: an atom or computation determines 𝑠, and subtyping compares that 𝑠 with the expected type.
Atoms synthesize singleton refinements. For an integer atom 𝑎 represented by (𝑟,𝑘), the two constraints are 𝜈−𝑟≤𝑘 and 𝑟−𝜈≤−𝑘. Write 𝗋𝖾𝗉𝖨(𝑎)=(𝑟,𝑘), where (𝑥,0) represents an integer variable 𝑥 and (𝟎,𝑛) represents the literal 𝑛. For an array atom 𝑎, write 𝗋𝖾𝗉𝖫(𝑎)=(𝑟,𝑘) for its length representative, where (𝐿𝑎,0) represents an array variable and (𝟎,𝑚) represents an array literal of length 𝑚. Define 𝖤𝗊𝖨(𝑟,𝑘)={𝜈:𝗂𝗇𝗍∣𝜈−𝑟≤𝑘∧𝑟−𝜈≤−𝑘},𝖲𝗁𝗂𝖿𝗍(𝑎,𝑗)=𝖤𝗊𝖨(𝑟,𝑘+𝑗)when𝑎hasintegerrepresentative(𝑟,𝑘),𝖫𝖾𝗇𝗀𝗍𝗁(𝑎)=𝖤𝗊𝖨(𝑟,𝑘)when𝑎haslengthrepresentative(𝑟,𝑘),𝖠𝗋𝗋𝖺𝗒𝑚={𝜈:𝖺𝗋𝗋∣𝐿𝜈−𝟎≤𝑚∧𝟎−𝐿𝜈≤−𝑚}. The exact integer type of 𝑛 is 𝖤𝗊𝖨(𝟎,𝑛).
If 𝑖 has integer representative (𝑟𝑖,𝑘𝑖) and 𝑎 has length representative (𝑟𝑎,𝑘𝑎), define the bounds predicate 𝖡𝗇𝖽(𝑎,𝑖):=(𝟎−𝑟𝑖≤𝑘𝑖)∧(𝑟𝑖−𝑟𝑎≤𝑘𝑎−𝑘𝑖−1). The first conjunct is 0≤𝑖 and the second is 𝑖<𝗅𝖾𝗇(𝑎), after moving offsets to the right.
Typing is declarative but bidirectional. Atoms and computations synthesize; expressions check.
𝑥:𝑡∈Γ
Γ⊢𝑥⇒𝑡
D-Var
Γ⊢𝑛⇒𝖤𝗊𝖨(𝟎,𝑛)
D-Int
𝐴haslength𝑚
Γ⊢𝐴⇒𝖠𝗋𝗋𝖺𝗒𝑚
D-Array
Γ⊢𝑥:𝑠→𝑡𝗍𝗒𝗉𝖾Γ,𝑥:𝑠⊢𝑒⇐𝑡
Γ⊢(𝜆𝑥.𝑒:𝑥:𝑠→𝑡)⇒𝑥:𝑠→𝑡
D-Lam
Γ⊢𝑥:𝑠→𝑡𝗍𝗒𝗉𝖾𝑓,𝑥∉dom(Γ)𝑓≠𝑥Γ,𝑓:(𝑥:𝑠→𝑡),𝑥:𝑠⊢𝑒⇐𝑡
Γ⊢(𝖿𝗂𝗑𝑓(𝑥).𝑒:𝑥:𝑠→𝑡)⇒𝑥:𝑠→𝑡
D-Fix
Γ⊢𝑎⇒𝑠|𝑠|=𝗂𝗇𝗍
Γ⊢𝑎+𝑘⇒𝑐𝖲𝗁𝗂𝖿𝗍(𝑎,𝑘)
D-Shift
Γ⊢𝑎⇒𝑠|𝑠|=𝖺𝗋𝗋
Γ⊢𝗅𝖾𝗇𝑎⇒𝑐𝖫𝖾𝗇𝗀𝗍𝗁(𝑎)
D-Length
Γ⊢𝑎⇒𝑠|𝑠|=𝖺𝗋𝗋Γ⊢𝑖⇒𝑢|𝑢|=𝗂𝗇𝗍Γ⊧𝖺𝗅𝗅𝖡𝗇𝖽(𝑎,𝑖)
Γ⊢𝗀𝖾𝗍𝑎𝑖⇒𝑐𝗂𝗇𝗍
D-Get
Γ⊢𝑓⇒𝑥:𝑠→𝑡Γ⊢𝑎⇐𝑠
Γ⊢𝑓𝑎⇒𝑐𝑡[𝑎/𝑥]
D-App
Γ⊢𝑎⇒𝑠Γ⊢𝑠<:𝑡
Γ⊢𝑎⇐𝑡
D-Sub
Γ⊢𝑐⇒𝑐𝑠Γ,𝑥:𝑠⊢𝑒⇐𝑡𝑥∉dom(Γ)∪fv(𝑡)
Γ⊢𝗅𝖾𝗍𝑥=𝑐𝗂𝗇𝑒⇐𝑡
D-Let
Γ,𝛿𝖼𝗍𝗑Γ,𝛿⊢𝑒1⇐𝑡Γ,――𝛿⊢𝑒2⇐𝑡
Γ⊢𝗂𝖿𝛿𝗍𝗁𝖾𝗇𝑒1𝖾𝗅𝗌𝖾𝑒2⇐𝑡
D-If
Γ⊢𝑡𝗍𝗒𝗉𝖾
Γ⊢𝖾𝗋𝗋𝗈𝗋⇐𝑡
D-Error
Rule D-Sub is the only subsumption rule, and it applies only when a synthesized atom meets an expected type. The side condition of D-Let prevents a local name from escaping in the result type. Dependency is nevertheless useful inside the body, where the exact type synthesized for the computation becomes a hypothesis.
If Γ𝖼𝗍𝗑, then every derivation Γ⊢𝑎⇒𝑠 or Γ⊢𝑐⇒𝑐𝑠 has a conclusion type satisfying Γ⊢𝑠𝗍𝗒𝗉𝖾. Consequently the context Γ,𝑥:𝑠 in D-Let is well formed when 𝑥 is fresh.
Proof. Induct mutually on atom and computation synthesis. Variable lookup in a well-formed context gives formation of its declared type. The literal and array types are formed because their representatives use only vertices in 𝑉(Γ)∪{𝟎}. Lambda and fixpoint conclusions use their stated formation premises. Arrow formation derives Γ,𝑥:𝑠𝖼𝗍𝗑 for D-Lam; for D-Fix, the premises 𝑓,𝑥∉dom(Γ) and 𝑓≠𝑥 derive Γ,𝑓:(𝑥:𝑠→𝑡),𝑥:𝑠𝖼𝗍𝗑.
Shift and length merely change integer offsets in a well-scoped exact type, and get returns 𝗂𝗇𝗍. In the application case, inversion of formation for 𝑥:𝑠→𝑡 gives formation of 𝑡 under Γ,𝑥:𝑠. The argument check gives a well-sorted atom of shape |𝑠|. Induct on the formation derivation of 𝑡. At a base refinement, the representative of 𝑎 replaces only 𝑥 or 𝐿𝑥 and belongs to 𝑉(Γ)∪{𝟎} by the recursive synthesis hypothesis; hence the normalized predicate is still in scope. At an arrow, alpha-rename the binder away from 𝑎 and apply the induction hypothesis to its domain and codomain. Thus 𝑡[𝑎/𝑥] is formed under Γ. ◻
Let 𝜁=[𝑎/𝑥] be a well-sorted atom substitution, and normalize every result as in definition 10.3. If 𝗋𝖾𝗉(𝑎)=(𝑟𝑎,𝑘𝑎) at the relevant sort, define 𝜁∗(𝑟,𝑘)={(𝑟𝑎,𝑘+𝑘𝑎),𝑟=𝑥or𝑟=𝐿𝑥atthatsort,(𝑟,𝑘),otherwise. Then:
computing 𝗋𝖾𝗉𝖨 or 𝗋𝖾𝗉𝖫 after 𝜁 gives 𝗋𝖾𝗉(𝑏𝜁)=𝜁∗(𝗋𝖾𝗉(𝑏));
𝖲𝗁𝗂𝖿𝗍, 𝖫𝖾𝗇𝗀𝗍𝗁, and 𝖡𝗇𝖽 commute with 𝜁 (for example, 𝖡𝗇𝖽(𝑏,𝑖)𝜁=𝖡𝗇𝖽(𝑏𝜁,𝑖𝜁));
for every atomic guard 𝛿, ――𝛿𝜁=―――𝛿𝜁.
All equalities are syntactic equalities of normalized predicates or types.
Proof of Lemma 22.21 — Substitution commutes with representatives and guards
Proof. For clause 1, inspect the two atom forms. A literal has representative (𝟎,𝑛) and is unchanged. A variable other than 𝑥 is unchanged. The distinguished variable is replaced by 𝑎, so its pair becomes exactly 𝗋𝖾𝗉𝖨(𝑎), or 𝗋𝖾𝗉𝖫(𝑎) at array shape. Moving the resulting constant offset to the right is precisely the stipulated normalization.
Clause 2 follows by substituting those pairs into the defining equations of 𝖲𝗁𝗂𝖿𝗍, 𝖫𝖾𝗇𝗀𝗍𝗁, and 𝖡𝗇𝖽. For clause 3, write 𝛿=𝑟1−𝑟2≤𝑘. Substitution changes only 𝑟1 and 𝑟2, after which both orders of calculation produce 𝑟2𝜁−𝑟1𝜁≤−𝑘−1 before the same normalization. No semantic entailment is used in these commutation facts. ◻
𝖪Γ(𝑒,𝑡) either fails structurally or returns the finite sequents whose validity is equivalent to Γ⊢𝑒⇐𝑡. Each emitted verification condition (VC) is a sequent (Γ;𝑝), discharged by checking Γ⊧𝖺𝗅𝗅𝑝. Define the partial finite translation 𝖲𝗎𝖻𝖵𝖢(Γ;𝑠,𝑡) recursively. With 𝑧 fresh and arrow binders alpha-aligned, its successful clauses are 𝖲𝗎𝖻𝖵𝖢(Γ;{𝜈:𝐵∣𝑝},{𝜈:𝐵∣𝑞})={(Γ,𝑧:{𝜈:𝐵∣𝑝};𝑞[𝑧/𝜈])},𝖲𝗎𝖻𝖵𝖢(Γ;(𝑥:𝑠1→𝑠2),(𝑥:𝑡1→𝑡2))=𝖲𝗎𝖻𝖵𝖢(Γ;𝑡1,𝑠1)⊎𝖲𝗎𝖻𝖵𝖢(Γ,𝑥:𝑡1;𝑠2,𝑡2). The translation fails on a shape mismatch, an ill-formed input type, or a failed recursive call. The first arrow call compares 𝑡1 with 𝑠1, which is the contravariant premise of S-Arrow.
The executable checker uses judgments Γ⊢𝑎⇒𝑡∣C,Γ⊢𝑐⇒𝑐𝑡∣C,Γ⊢𝑒⇐𝑡∣C. The function 𝖠Γ(𝑎) returns an atom type and a VC list; 𝖢𝗈𝗆𝗉Γ(𝑐) returns a computation type and a VC list; and 𝖪Γ(𝑒,𝑡) returns the VCs for checking 𝑒 against 𝑡. The get clause emits the two conjuncts of 𝖡𝗇𝖽(𝑎,𝑖), and 𝖲𝗎𝖻𝖵𝖢 emits one implication at each base refinement. Write ⊎ for union of finite VC lists. A clause fails when its shape or well-formedness test fails. 𝖠Γ(𝑥)=(Γ(𝑥),∅),𝖠Γ(𝑛)=(𝖤𝗊𝖨(𝟎,𝑛),∅),𝖠Γ(⟨𝑛0,…,𝑛𝑚−1⟩)=(𝖠𝗋𝗋𝖺𝗒𝑚,∅),𝖠Γ(𝜆𝑥.𝑒:𝑥:𝑠→𝑡)=(𝑥:𝑠→𝑡,C)if𝖪Γ,𝑥:𝑠(𝑒,𝑡)=C,𝖠Γ(𝖿𝗂𝗑𝑓(𝑥).𝑒:𝑥:𝑠→𝑡)=(𝑥:𝑠→𝑡,C)if𝖪Γ,𝑓:(𝑥:𝑠→𝑡),𝑥:𝑠(𝑒,𝑡)=C. Put BΓ(𝑎,𝑖)={(Γ;𝛿)∣𝛿isaconjunctof𝖡𝗇𝖽(𝑎,𝑖)}. The computation clauses are 𝖢𝗈𝗆𝗉Γ(𝑎+𝑘)=(𝖲𝗁𝗂𝖿𝗍(𝑎,𝑘),C)if𝖠Γ(𝑎)=(𝑠,C),|𝑠|=𝗂𝗇𝗍,𝖢𝗈𝗆𝗉Γ(𝗅𝖾𝗇𝑎)=(𝖫𝖾𝗇𝗀𝗍𝗁(𝑎),C)if𝖠Γ(𝑎)=(𝑠,C),|𝑠|=𝖺𝗋𝗋,𝖢𝗈𝗆𝗉Γ(𝗀𝖾𝗍𝑎𝑖)=(𝗂𝗇𝗍,C𝑎⊎C𝑖⊎BΓ(𝑎,𝑖))if𝖠Γ(𝑎)=(𝑠,C𝑎),|𝑠|=𝖺𝗋𝗋,𝖠Γ(𝑖)=(𝑢,C𝑖),|𝑢|=𝗂𝗇𝗍,𝖢𝗈𝗆𝗉Γ(𝑓𝑎)=(𝑡[𝑎/𝑥],C𝑓⊎C𝑎)if𝖠Γ(𝑓)=(𝑥:𝑠→𝑡,C𝑓),𝖪Γ(𝑎,𝑠)=C𝑎. Abbreviate the conditional source expression by 𝑒𝛿. The checking clauses are 𝖪Γ(𝑎,𝑡)=C⊎𝖲𝗎𝖻𝖵𝖢(Γ;𝑠,𝑡)if𝖠Γ(𝑎)=(𝑠,C),𝖪Γ(𝗅𝖾𝗍𝑥=𝑐𝗂𝗇𝑒,𝑡)=C𝑐⊎C𝑒if𝖢𝗈𝗆𝗉Γ(𝑐)=(𝑠,C𝑐),𝑥∉dom(Γ)∪fv(𝑡),𝖪Γ,𝑥:𝑠(𝑒,𝑡)=C𝑒,𝖪Γ(𝑒𝛿,𝑡)=C1⊎C2if𝖪Γ,𝛿(𝑒1,𝑡)=C1,𝖪Γ,――𝛿(𝑒2,𝑡)=C2,𝖪Γ(𝖾𝗋𝗋𝗈𝗋,𝑡)=∅, where 𝑒𝛿=𝗂𝖿𝛿𝗍𝗁𝖾𝗇𝑒1𝖾𝗅𝗌𝖾𝑒2. Every clause first checks its context and input types for well-formedness. Arithmetic occurs only in the sequents emitted by 𝖲𝗎𝖻𝖵𝖢 and the get clause.
Proof. Induct simultaneously on the shapes of 𝑠,𝑡. At equal bases, the translation is the premise of S-Base, so the claims are identical. At arrows, the translation produces exactly the domain VCs and, under the target domain, exactly the codomain VCs. Apply the two induction hypotheses and S-Arrow. Unequal shapes admit neither a translation nor, by proposition 10.13, a subtyping derivation. ◻
Proof. Use simultaneous structural induction on atoms, computations, and checked expressions. The induction hypothesis equates each recursive declarative premise with validity of the VC list returned by its recursive call.
Variables, literals, and arrays emit no VCs, so their equations coincide with D-Var, D-Int, and D-Array. The lambda equation succeeds exactly when its annotation is well formed and the body VCs are valid; by the induction hypothesis this is the premise of D-Lam. The fixpoint equation adds the recursive function binder to that lambda calculation and gives the body premise of D-Fix under both binders.
For get, the generated list is C𝑎⊎C𝑖⊎BΓ(𝑎,𝑖). Its first two parts validate the operand typings; its last two sequents are exactly the bounds premise of D-Get. Shift and length use the same atom shape tests as their declarative rules. If 𝖠Γ(𝑓)=(𝑥:𝑠→𝑡,C𝑓) and 𝖪Γ(𝑎,𝑠)=C𝑎, the induction hypotheses give the two premises of D-App, and the computation result is 𝑡[𝑎/𝑥].
For checking, the atomic equation combines the atom equivalence with lemma 10.16; this is precisely D-Sub in each direction. For a let, the computation induction hypothesis gives its synthesized type 𝑠, and the expression hypothesis gives the continuation judgment under 𝑥:𝑠; the escape condition is exactly that of D-Let. The two conditional lists give the branch premises under 𝛿 and ――𝛿, and error requires only formation of its expected type. ◻
Each declarative judgment has a generated output of the same result type whose VCs are all valid. More precisely: Γ⊢𝑎⇒𝑠⟹∃C.𝖠Γ(𝑎)=(𝑠,C)∧𝖵𝖺𝗅𝗂𝖽(C),Γ⊢𝑐⇒𝑐𝑠⟹∃C.𝖢𝗈𝗆𝗉Γ(𝑐)=(𝑠,C)∧𝖵𝖺𝗅𝗂𝖽(C),Γ⊢𝑒⇐𝑡⟹∃C.𝖪Γ(𝑒,𝑡)=C∧𝖵𝖺𝗅𝗂𝖽(C), where 𝖵𝖺𝗅𝗂𝖽(C) means that every VC in C is valid.
Proof of Lemma 22.25 — Declarative generation completeness
Proof. Induct simultaneously on the declarative derivations. Rules D-Var, D-Int, and D-Array select their matching generation clauses, whose shape tests succeed by the conclusion type of the rule. In D-Lam and D-Fix, the formation premise makes the annotation test succeed; the induction hypothesis generates the body list under exactly the binders in the declarative premise. Rules D-Shift, D-Length, and D-Get determine the operand shapes by inversion, and the last rule’s bounds premise validates BΓ(𝑎,𝑖). In D-App, inversion of the synthesized operator type selects the application clause, and the two induction hypotheses generate its operator and argument lists.
For checking, D-Sub generates the atom list followed by 𝖲𝗎𝖻𝖵𝖢Γ(𝑠,𝑡); the induction hypothesis and lemma 10.16 validate both parts. Rule D-Let generates the computation list and then the body list under its synthesized type; its freshness and escape tests are the rule’s side conditions. Rules D-If and D-Error select their unique syntax clauses, with the branch and formation premises proving that generation succeeds. These cases cover every declarative rule and establish validity of every returned list. ◻
The three generated-output equivalences of lemma 22.24 hold, and every declarative judgment has the valid generated output stated in lemma 22.25. Consequently, generation failure means declarative failure. Successful generation—which may initially emit invalid VCs—reduces typing exactly to validation and replay of the emitted certificates.
Proof of Theorem 10.17 — VC-producing checker correctness
Proof. Generated-output exactness is lemma 22.24; the existence of an output for every declarative derivation is lemma 22.25. Finally, theorem 10.9 equates validity of each finite VC list with accepted replay evidence. ◻
The corrected last-element trace
Define the nonempty-array type 𝖭𝖤𝖠𝗋𝗋:={𝜈:𝖺𝗋𝗋∣𝟎−𝐿𝜈≤−1} and the annotated program last:=(𝜆𝑎.𝗅𝖾𝗍𝑛=𝗅𝖾𝗇𝑎𝗂𝗇𝗅𝖾𝗍𝑖=𝑛+(−1)𝗂𝗇𝗅𝖾𝗍𝑧=𝗀𝖾𝗍𝑎𝑖𝗂𝗇𝑧:𝑎:𝖭𝖤𝖠𝗋𝗋→𝗂𝗇𝗍). The checker synthesizes 𝑛=𝐿𝑎,𝑖=𝑛−1, as pairs of constraints. At the read its context graph contains, among other duplicate or weaker edges, 𝐿𝑎−1⟶𝟎,𝐿𝑎0→𝑛,𝑛0→𝐿𝑎,𝑛−1⟶𝑖,𝑖1→𝑛. The two bounds VCs and their certificates are goalpathweight𝟎−𝑖≤0𝑖→𝑛→𝐿𝑎→𝟎1+0−1=0,𝑖−𝐿𝑎≤−1𝐿𝑎→𝑛→𝑖0−1=−1. This is the decisive correction to the unguarded program: the read uses 𝑖=𝑛−1, and the nonempty precondition adds the graph edge used by the path that proves 0≤𝑖. An empty literal has 𝐿𝐴=0; checking it against 𝖭𝖤𝖠𝗋𝗋 asks for 0≤−1, so no certificate exists.
Testing the empty case explicitly yields a total function on arbitrary arrays: lastOrZero:=(𝜆𝑎.𝗅𝖾𝗍𝑛=𝗅𝖾𝗇𝑎𝗂𝗇𝗂𝖿𝑛−𝟎≤0𝗍𝗁𝖾𝗇0𝖾𝗅𝗌𝖾𝗅𝖾𝗍𝑖=𝑛+(−1)𝗂𝗇𝗅𝖾𝗍𝑧=𝗀𝖾𝗍𝑎𝑖𝗂𝗇𝑧:𝑎:𝖺𝗋𝗋→𝗂𝗇𝗍). The exact type of 0 is a subtype of 𝗂𝗇𝗍, so the then branch checks at 𝗂𝗇𝗍. In the else branch the complement is 𝟎−𝑛≤−1; length synthesis gives 𝑛=𝐿𝑎, and shift synthesis gives 𝑖=𝑛−1. The lower-bound certificate is 𝑖1→𝑛−1⟶𝟎,1+(−1)=0, and the upper-bound certificate is 𝐿𝑎0→𝑛−1⟶𝑖,0+(−1)=−1. For the D-Get premise, put Γ𝑎=𝑎:𝖺𝗋𝗋, 𝑠𝑛=𝖫𝖾𝗇𝗀𝗍𝗁(𝑎), 𝛿=(𝑛−𝟎≤0), and 𝑠𝑖=𝖲𝗁𝗂𝖿𝗍(𝑛,−1). Write Γ𝑖=Γ𝑎,𝑛:𝑠𝑛,――𝛿 and Γ𝑒=Γ𝑖,𝑖:𝑠𝑖. For the nested terms, put 𝑒𝑧=𝗅𝖾𝗍𝑧=𝗀𝖾𝗍𝑎𝑖𝗂𝗇𝑧,𝑒𝑖=𝗅𝖾𝗍𝑖=𝑛+(−1)𝗂𝗇𝑒𝑧,𝑒𝗂𝖿=𝗂𝖿𝛿𝗍𝗁𝖾𝗇0𝖾𝗅𝗌𝖾𝑒𝑖,𝑒𝑛=𝗅𝖾𝗍𝑛=𝗅𝖾𝗇𝑎𝗂𝗇𝑒𝗂𝖿. In the else context, the two paths prove the final premise of 𝑎:𝖺𝗋𝗋∈Γ𝑒Γ𝑒⊢𝑎⇒𝖺𝗋𝗋D−Var|𝖺𝗋𝗋|=𝖺𝗋𝗋𝑖:𝑠𝑖∈Γ𝑒Γ𝑒⊢𝑖⇒𝑠𝑖D−Var|𝑠𝑖|=𝗂𝗇𝗍Γ𝑒⊧𝖺𝗅𝗅𝖡𝗇𝖽(𝑎,𝑖)Γ𝑒⊢𝗀𝖾𝗍𝑎𝑖⇒𝑐𝗂𝗇𝗍D−Get. Call this D-Get derivation D𝑧. Let D𝑛 denote the D-Length derivation of Γ𝑎⊢𝗅𝖾𝗇𝑎⇒𝑐𝑠𝑛, and let D𝑖 denote the D-Shift derivation of Γ𝑖⊢𝑛+(−1)⇒𝑐𝑠𝑖. Let D0,D′𝑧 be the D-Sub checks of 0 and 𝑧 against 𝗂𝗇𝗍. These derivations combine as follows: ⋅⊢𝑎:𝖺𝗋𝗋→𝗂𝗇𝗍𝗍𝗒𝗉𝖾D𝑛Γ𝑎,𝑛:𝑠𝑛,𝛿𝖼𝗍𝗑D0:Γ𝑎,𝑛:𝑠𝑛,𝛿⊢0⇐𝗂𝗇𝗍D𝑖D𝑧D′𝑧:Γ𝑒,𝑧:𝗂𝗇𝗍⊢𝑧⇐𝗂𝗇𝗍Γ𝑒⊢𝑒𝑧⇐𝗂𝗇𝗍D−LetΓ𝑖⊢𝑒𝑖⇐𝗂𝗇𝗍D−LetΓ𝑎,𝑛:𝑠𝑛⊢𝑒𝗂𝖿⇐𝗂𝗇𝗍D−IfΓ𝑎⊢𝑒𝑛⇐𝗂𝗇𝗍D−Let⋅⊢lastOrZero⇒𝑎:𝖺𝗋𝗋→𝗂𝗇𝗍D−Lam. The two path sums prove Γ𝑒⊧𝖺𝗅𝗅𝖡𝗇𝖽(𝑎,𝑖). Length and shift synthesis emit no VCs; the two bounds sequents are therefore the only VCs in this derivation.
★☆☆ Replace the input type of last by 𝖺𝗋𝗋. Show that the second bounds VC still has a path certificate but the first does not. Give a potential for the resulting context with 𝐿𝑎=𝑛=0 and 𝑖=−1 that refutes the first VC.
Proof of Lemma 10.18 — Atom reflection and static substitution
Proof. For clause 1, invert D-Sub to obtain an exact synthesized type 𝑠0 and Γ⊢𝑠0<:{𝜈:𝐵∣𝑝}. Let 𝜌⊧Γ, let 𝑣𝑎 be the value of the closed atom obtained from 𝑎 under 𝜌, and extend 𝜌 by a fresh 𝑧 with value 𝑣𝑎. For a variable atom, satisfaction of its declaration shows that this extension satisfies 𝑧:𝑠0. For an integer or array literal, the two equality constraints in 𝖤𝗊𝖨 or 𝖠𝗋𝗋𝖺𝗒𝑚 hold by calculation. A function form cannot synthesize the required base shape. The premise Γ⊢𝑠0<:{𝜈:𝐵∣𝑝} gives 𝑝[𝑣𝑎/𝜈] by S-Base. Since 𝜌 was arbitrary, this is the required entailment.
For clause 2, induct simultaneously on formation and subtyping. At base shape, let 𝜌 satisfy Γ,Δ[𝑎/𝑥], evaluate 𝑎 in 𝜌, and extend 𝜌 by assigning that value to 𝑥. Clause 1 states that this value satisfies the base refinement in 𝑠, so the 𝑥:𝑠 entry is true. By lemma 22.8, the extension satisfies Γ,𝑥:𝑠,Δ entry by entry. At arrow shape, well-formed predicates cannot mention 𝑥, so their substitution and the context embedding are unchanged and no semantic value for a function variable is needed. Consequently every S-Base entailment survives substitution. Base formation uses the same scope-substitution calculation. Arrow formation and S-Arrow apply the induction hypotheses beneath a fresh binder. ◻
Proof. Arrow inversion gives Γ⊢𝐴<:𝐴′ and Γ,𝑦:𝐴⊢𝐶′<:𝐶. Invert the check of 𝑎: it synthesizes some 𝐴0 with 𝐴0<:𝐴. Transitivity followed by D-Sub checks 𝑎 at 𝐴′. Applying lemma 10.18(2) to the codomain comparison gives the second conclusion. ◻
Proof of Lemma 10.19 — Typing under a more precise context
Proof. Induct mutually on checking, atom synthesis, and computation synthesis. For D-Var, the distinguished variable changes its synthesized type from 𝑠 to 𝑠′, and the required comparison is the hypothesis; every other variable keeps its declared type. Literals keep their exact singleton type. Lambda and fixpoint annotations do not change: narrow their formation premises, apply the checking induction hypothesis to their bodies after freshening the binders, and return the same arrow.
Shift, length, and get apply the atom induction hypotheses. Shape preservation for subtyping retains their side conditions, while narrowing transports the bounds entailment in the get case; their synthesized result types depend on atom syntax, so they are unchanged. For application, suppose the new function type is 𝑦:𝐴′→𝐶′ and Γ,𝑥:𝑠′,Δ⊢(𝑦:𝐴′→𝐶′)<:(𝑦:𝐴→𝐶). The old argument checks at 𝐴 by the checking induction hypothesis. Lemma 22.28 checks it at 𝐴′ and compares the two instantiated codomains. Rule D-App therefore derives result type 𝐶′[𝑎/𝑦].
For D-Sub, the atom induction hypothesis gives 𝑢′<:𝑢; narrowing gives the old 𝑢<:𝑡 premise in the new context, and transitivity gives the check at 𝑡. In D-Let, computation precision gives 𝑢′<:𝑢. Apply the checking induction hypothesis to the tail under 𝑦:𝑢, then apply this lemma recursively to change that declaration to 𝑦:𝑢′ before applying the let. The two conditional branches use the induction hypotheses after the same guard has been appended; error uses narrowed formation. ◻
Assume every context and type named in the three clauses is well formed.
If a judgment of formation, subtyping, synthesis, or checking holds under Γ, it continues to hold after inserting fresh, well-formed entries whose variables are not captured.
Suppose Γ⊢𝑎⇐𝑠. Formation, subtyping, and checking judgments under Γ,𝑥:𝑠,Δ remain derivable after capture-avoiding substitution in Γ,Δ[𝑎/𝑥]. If an atom or computation synthesized 𝑢 before substitution, its substituted syntax synthesizes some 𝑢′ with Γ,Δ[𝑎/𝑥]⊢𝑢′<:𝑢[𝑎/𝑥].
Proof of Lemma 10.20 — Weakening, atom reflection, and substitution
Proof. For weakening, use mutual rule induction. Extending a context preserves every vertex scope, and a valuation satisfying the extension also satisfies its prefix. Hence formation and entailment premises remain valid. Alpha-rename a binder before extending beneath it.
For substitution, strengthen the mutual induction so that synthesis of a substituted term returns a subtype of the substituted result type. Consider an entailment premise and let 𝜌 satisfy Γ,Δ[𝑎/𝑥]. If 𝑠 is a base refinement, evaluate 𝑎 under 𝜌 and extend 𝜌 with 𝑥↦𝜌(𝑎). By lemma 10.18(1), this value satisfies 𝑠. The context clause of lemma 22.8 then gives 𝜌[𝑥↦𝜌(𝑎)]⊧Γ,𝑥:𝑠,Δ. The original entailment and normalization of literal substitution give the required entailment under Γ,Δ[𝑎/𝑥]. If 𝑠 is an arrow, scope excludes 𝑥 from every refinement, so the entailment is unchanged. For the variable 𝑥, inversion of Γ⊢𝑎⇐𝑠 gives Γ⊢𝑎⇒𝑠0 and 𝑠0<:𝑠; every other variable synthesizes its substituted declaration.
Literal, array, lambda, and fixpoint synthesis returns the substituted result type. For shift, length, and get, lemma 22.21(1–2) gives the substituted exact type and bounds predicate. In the application case, suppose the function induction hypothesis gives 𝑦:𝐴′→𝐶′<:𝑦:𝐴→𝐶. The substituted argument checks at 𝐴. Lemma 22.28 checks it at 𝐴′, so D-App synthesizes 𝐶′[𝑎0/𝑦], and supplies the required comparison 𝐶′[𝑎0/𝑦]<:𝐶[𝑎0/𝑦]. If a substituted let computation synthesizes 𝑢′<:𝑢[𝑎/𝑥], lemma 10.19 changes the tail declaration from 𝑦:𝑢[𝑎/𝑥] to 𝑦:𝑢′, after which D-Let applies. Atomic checking uses transitivity with its subtyping premise. Binders are alpha-renamed away from 𝑥. Clause 3 of lemma 22.21 gives, in D-If, ――𝛿[𝑎/𝑥]=――――𝛿[𝑎/𝑥].
For clause 3, induct on the formation derivation. Base refinements cannot mention 𝑥 when it contributes no vertex, and otherwise the hypothesis 𝑥∉fv(𝑡) removes every possible occurrence. Arrow formation applies the induction hypothesis to the domain and, after alpha-renaming its binder, to the codomain. If 𝑥∉fv(𝑡), deleting 𝑥:𝑠 preserves formation of 𝑡; this is precisely the escape condition required by D-Let. ◻
Proof of Lemma 10.21 — Canonical forms through subsumption
Proof. The last checking rule is D-Sub, so 𝑣 synthesizes some 𝑠 with 𝑠<:𝑡. By proposition 10.13, |𝑠|=|𝑡|. Inspecting the five synthesis rules gives exactly the constructor stated for each shape: integer literals are the only closed atoms synthesized at integer shape, array literals the only ones at array shape, and the two annotated function forms the only ones at arrow shape. Rule D-Var cannot conclude in the empty context. ◻
Proof. Induct on the syntax-directed checking derivation of 𝑒1. If 𝑒1 is an atom, bind composition is 𝑒2[𝑒1/𝑥], typed by lemma 10.20(2). If it is a let, bind composition retains the same computation and composes into the tail. If that computation synthesizes 𝑢, weaken the continuation from Γ,𝑥:𝑠 to Γ,𝑦:𝑢,𝑥:𝑠 after freshening 𝑦, apply the induction hypothesis under Γ,𝑦:𝑢, and apply D-Let. If it is a conditional, weaken the continuation once under Γ,𝛿 and once under Γ,――𝛿, apply the branch induction hypotheses, and apply D-If. If it is 𝖾𝗋𝗋𝗈𝗋, composition is 𝖾𝗋𝗋𝗈𝗋, typed by D-Error. ◻
Arbitrary strengthening is false: deleting a guard may destroy the very path used by a bounds proof. Preservation needs only the following exact case, where the deleted guard already follows from the remaining context. For example, in Γ=𝑎:𝖺𝗋𝗋,𝑖:𝗂𝗇𝗍, append the guard 𝛿≡𝑖−𝐿𝑎≤−1. Its graph edge 𝐿𝑎−1⟶𝑖 is itself the path certificate for the upper bound of 𝗀𝖾𝗍𝑎𝑖. Delete 𝛿, and that path disappears; the valuation 𝐿𝑎=𝑖=0 refutes the same bound. The valid-guard lemma applies only when another path in 𝐺Γ already proves 𝛿.
Suppose Γ⊧𝖺𝗅𝗅𝛿. Let Δ be any suffix well formed after Γ,𝛿; deleting the guard leaves the same vertex scope. Every one of the following judgments transports from Γ,𝛿,Δ to Γ,Δ with the same subject and type: context and type formation, subtyping, atom synthesis, computation synthesis, and expression checking. In particular, Γ,𝛿⊢𝑒⇐𝑡⟹Γ⊢𝑒⇐𝑡.
Proof. Use simultaneous induction on the five derivations, generalized over Δ. A guard contributes no vertex, so formation scopes do not change. For an S-Base premise, let 𝜌⊧Γ,Δ. Then 𝜌⊧Γ, hence 𝜌 satisfies 𝛿, and therefore 𝜌⊧Γ,𝛿,Δ; the original entailment applies. All syntactic premises retain the same lookup, shape, freshness, and escape conditions. Under an arrow, lambda, fixpoint, or let binder, extend the generalized suffix by its fresh declaration. For D-If, extend it by the selected branch guard. The induction hypotheses then give the original conclusion under Γ,Δ. ◻
Proof. Invert the checking rule and consider the reduction used.
For E-Shift, the let-bound computation has type 𝖲𝗁𝗂𝖿𝗍(𝑛,𝑘) and 𝑞=𝑛+𝑘. Directly checking the two defining constraints shows ⋅⊢𝑞⇐𝖲𝗁𝗂𝖿𝗍(𝑛,𝑘). Substitute 𝑞 for the let variable in the tail by lemma 10.20(2). The E-Len case is the same calculation with 𝑚=𝗅𝖾𝗇(𝐴) and 𝖫𝖾𝗇𝗀𝗍𝗁(𝐴).
For E-Get, the computation type is 𝗂𝗇𝗍. The selected array element 𝑛𝑖 checks at 𝗂𝗇𝗍 because its exact singleton subtype has the valid conclusion 𝗍𝗋𝗎𝖾. Substitute it into the tail.
For E-Beta, inversion of D-App and D-Lam gives 𝑥:𝑠⊢𝑒1⇐𝑡0, the closed argument ⋅⊢𝑣⇐𝑠, and a continuation typed under 𝑦:𝑡0[𝑣/𝑥]. Substitution gives ⋅⊢𝑒1[𝑣/𝑥]⇐𝑡0[𝑣/𝑥]; then lemma 10.22 types the reduct. For E-Fix, write 𝐹=𝖿𝗂𝗑𝑓(𝑥).𝑒1:𝑥:𝑠→𝑡0. Inversion of D-Fix gives 𝑓:(𝑥:𝑠→𝑡0),𝑥:𝑠⊢𝑒1⇐𝑡0. The same rule synthesizes ⋅⊢𝐹⇒𝑥:𝑠→𝑡0; reflexive subtyping and D-Sub therefore check 𝐹 at that type. Apply clause 2 of lemma 10.20 first at 𝑓, then at 𝑥. This gives 𝑥:𝑠⊢𝑒1[𝐹/𝑓]⇐𝑡0,⋅⊢𝑒1[𝐹/𝑓,𝑣/𝑥]⇐𝑡0[𝑣/𝑥]. The inverted continuation premise is 𝑦:𝑡0[𝑣/𝑥]⊢𝑒2⇐𝑡. The bind-typing lemma then types the reduct at 𝑡.
For E-IfT, the closed true atom 𝛿 is valid in the empty context, so lemma 10.23 types the selected branch. For E-IfF, the integer complement ――𝛿 is true and the same argument selects the other branch. Valid-guard discharge thus removes 𝛿, respectively ――𝛿, from the selected branch typing. ◻
Proof of Theorem 10.25 — Progress up to checked error
Proof. Proceed by the final checking rule. An atom is a value, and 𝖾𝗋𝗋𝗈𝗋 is checked error. A closed conditional test is an integer inequality, so exactly one of E-IfT and E-IfF applies.
For a let-bound shift, lemma 10.21(1) writes the operand as an integer literal, and E-Shift applies. For length, lemma 10.21(2) writes it as an array literal, and E-Len applies. For get, write the operands as 𝐴=⟨𝑛0,…,𝑛𝑚−1⟩ and 𝑖. The typing premise ⋅⊧𝖺𝗅𝗅𝖡𝗇𝖽(𝐴,𝑖) is 0≤𝑖<𝑚, which is precisely the side condition of E-Get. A closed function atom is an annotated lambda or fixpoint, so application takes E-Beta or E-Fix. Values, checked error, and these redex heads are syntactically disjoint. ◻
Let ⋅⊢𝑒⇐𝑡 and 𝑒⟼∗𝑒′. Then 𝑒′ is a value, is 𝖾𝗋𝗋𝗈𝗋, or can step; in particular it is not stuck at an out-of-bounds read. Moreover, if 𝑡={𝜈:𝐵∣𝑝} and 𝑒′ is a value, then 𝑒′ has base shape 𝐵 and satisfies 𝑝[𝑒′/𝜈].
Proof of Corollary 10.26 — Array safety and refinement soundness
Proof. Repeated preservation types 𝑒′ at 𝑡, and progress gives the first claim. An out-of-bounds get is neither a value nor error and has no successor, so it cannot occur. For the second claim, apply atom reflection lemma 10.18(1) in the empty context. This also explains the qualification “up to checked error”: D-Error permits a deliberate contract failure, but no unclassified stuck state. ◻
Refinement typing classifies every reachable state as a value, 𝖾𝗋𝗋𝗈𝗋, or a reducible term. A separate reachability proof is required to show that the distinguished checked error is never reached.
★★☆ Write the two substitutions in the E-Fix preservation case with all types displayed. Verify first that the annotated fixpoint checks at its own arrow type, then that substituting it for 𝑓 leaves the argument type 𝑠 unchanged, and finally that substituting 𝑣 for 𝑥 changes the result to 𝑡[𝑣/𝑥].
A Liquid template replaces selected base predicates by unknown conjunctions. Each unknown ranges over conjunctions drawn from a fixed finite qualifier set.
Let 𝐾 be a finite set of predicate unknowns. For each 𝜅∈𝐾, let 𝑄𝜅 be a finite set of well-scoped difference atoms. An assignment 𝜂 chooses a subset of 𝑄𝜅 and interprets 𝜅 as the conjunction of that subset. The empty subset means 𝗍𝗋𝗎𝖾. The inference procedure enumerates the finite product ∏𝜅∈𝐾P(𝑄𝜅), instantiates the annotated program, runs the VC-producing checker, and accepts the first assignment for which all VCs have checked certificates.
A fixed lexicographic order on the unknowns and on each qualifier set defines a deterministic returned assignment. The decreasing-fixed-point algorithm of Liquid Types starts every unknown at the conjunction of all its qualifiers, removes qualifiers responsible for failed VCs, and stops at a fixed point [RKJ08]. It performs at most ∑𝜅|𝑄𝜅| strict weakenings and, on success, returns the strongest assignment in the qualifier lattice. Direct enumeration performs at most 2∑𝜅|𝑄𝜅| complete checker runs.
Consider the following recursive program 𝐹, in a context 𝑎:𝖺𝗋𝗋. The notation 𝜅(𝜈,𝑎) writes the unknown 𝜅 together with the two vertices in its declared scope; the parentheses are scope annotations, not object-language predicate application: 𝐹:=(𝖿𝗂𝗑𝑓(𝑖).𝗂𝖿𝑖−𝟎≤0𝗍𝗁𝖾𝗇0𝖾𝗅𝗌𝖾𝗅𝖾𝗍𝑗=𝑖+(−1)𝗂𝗇𝗅𝖾𝗍𝑧=𝗀𝖾𝗍𝑎𝑗𝗂𝗇𝗅𝖾𝗍𝑟=𝑓𝑗𝗂𝗇𝑟:𝑖:{𝜈:𝗂𝗇𝗍∣𝜅(𝜈,𝑎)}→𝗂𝗇𝗍). It is called by the enclosing body 𝗅𝖾𝗍𝑛=𝗅𝖾𝗇𝑎𝗂𝗇𝗅𝖾𝗍𝑟=𝐹𝑛𝗂𝗇𝑟. Take 𝑄𝜅={𝟎−𝜈≤0,𝜈−𝐿𝑎≤0}. The assignment containing both qualifiers says 0≤𝑖≤𝐿𝑎. The entry call establishes it because 𝑛=𝐿𝑎 and array lengths are nonnegative. In the else branch, the complement of 𝑖≤0 is 𝟎−𝑖≤−1, hence 𝑖≥1. With 𝑗=𝑖−1, the checker obtains 0≤𝑗<𝐿𝑎 and 0≤𝑗≤𝐿𝑎, so both the read and recursive call are accepted. In fact the first qualifier is redundant for this program: the else guard itself proves 𝑗≥0. The smaller assignment 𝜅(𝜈,𝑎):=𝜈−𝐿𝑎≤0 is therefore also accepted. Dropping that upper qualifier instead loses the proof of 𝑗<𝐿𝑎. Finite search is allowed to find such a weaker invariant. Completeness therefore asserts the existence of an accepted assignment, not selection of a strongest one.
Fix a template instance 𝐼=(Γ,𝑒,𝑡,𝐾,(𝑄𝜅)𝜅∈𝐾). After applying an assignment 𝜂, a closing substitution 𝜃 is well typed for Γ𝜂 when each 𝜃(𝑥) is a closed value checking at its substituted declaration after substitution for earlier entries, and each substituted guard is true.
If 𝐾 and every 𝑄𝜅 are finite, enumeration examines every assignment in ∏𝜅∈𝐾P(𝑄𝜅) and terminates. Every returned 𝜂 satisfies Γ𝜂⊢𝑒𝜂⇐𝑡𝜂. For every well-typed closing substitution 𝜃 for Γ𝜂, the closed term (𝑒𝜂)𝜃 is array safe; in particular this holds directly when Γ𝜂=⋅. If any assignment in the product validates every generated VC, enumeration returns an assignment that does so.
Proof of Theorem 10.28 — Finite-qualifier inference
Proof. The product has ∏𝜅∈𝐾2|𝑄𝜅| elements. VC generation terminates on each finite instantiated syntax tree, and corollary 10.10 decides every emitted difference sequent. Hence enumeration terminates. If it returns 𝜂, certificate acceptance implies that every emitted sequent is valid by theorem 10.9, so theorem 10.17 gives Γ𝜂⊢𝑒𝜂⇐𝑡𝜂. Apply lemma 10.20(2) successively at the variable entries. After the preceding entries have been substituted, each guard is a closed true difference atom, hence is entailed by the empty context. Apply lemma 10.23 to each such guard. This gives ⋅⊢(𝑒𝜂)𝜃⇐(𝑡𝜂)𝜃, and corollary 10.26 gives safety. Enumeration reaches every product element, so it reaches and accepts any successful assignment. ◻
Rondon, Kawaguchi, and Jhala formulate logical qualifiers, dependent templates, and predicate-abstraction solving in Sections 2 and 4, pp. 159–166, and state inference soundness and failure completeness for a fixed qualifier set in their Theorem 2 on proceedings p. 166 [RKJ08]. Their system uses a richer background logic and an HM-shape phase. In this finite difference-logic instance, completeness ranges exactly over ∏𝜅∈𝐾P(𝑄𝜅); predicates absent from those qualifier sets are outside the theorem.
★★☆ Let 𝑄𝜅 contain only 𝟎−𝜈≤0 for the recursive program 𝐹. Exhibit the failed upper-bound VC using 𝐿𝑎=1, 𝑖=2, and 𝑗=1. Then use the two qualifiers 𝟎−𝜈≤0 and 𝜈−𝐿𝑎≤1. Show that the entry and recursive-call constraints hold but the same valuation still makes the read use index 𝐿𝑎; hence the nearby upper qualifier is insufficient. The countervaluation need not be reachable from the entry call: a VC is a semantic implication over every valuation satisfying the proposed invariant.
When a bounds sequent is not proved, it may be guarded dynamically rather than asserted as kernel truth. For 𝑝=𝛿1∧⋯∧𝛿𝑚, define within the existing language 𝗀𝗎𝖺𝗋𝖽(𝗍𝗋𝗎𝖾,𝑒)=𝑒,𝗀𝗎𝖺𝗋𝖽(𝛿∧𝑝,𝑒)=𝗂𝖿𝛿𝗍𝗁𝖾𝗇𝗀𝗎𝖺𝗋𝖽(𝑝,𝑒)𝖾𝗅𝗌𝖾𝖾𝗋𝗋𝗈𝗋.𝗀𝗎𝖺𝗋𝖽(𝑝,𝑒) checks a finite conjunction before executing the first-order array operation; it introduces no function wrapper or blame labels.
Call a closing substitution 𝜃 for Γrespecting when it maps variables to closed values of the declared shapes and its induced valuation satisfies Γ.
Proof of Theorem 10.30 — Bounds-contract insertion and certified removal
Proof. Atom synthesis weakens from Γ to Γ,𝑝. Weakening the given body derivation gives Γ,𝑝,𝑧:𝗂𝗇𝗍⊢𝑒⇐𝑡, and the assumed Γ⊢𝑡𝗍𝗒𝗉𝖾, weakening forms 𝑡 under Γ,𝑝, while 𝑧∉fv(𝑡) is exactly the escape side condition of D-Let. Under that context, entailment reflexivity proves both conjuncts of 𝖡𝗇𝖽(𝑎,𝑖), so D-Get and D-Let show that 𝐸 checks at 𝑡. Induct on the conjunct list 𝑝, allowing an arbitrary prefix context. The empty list gives 𝐸. For 𝛿∧𝑝′, the induction hypothesis types the then branch under Γ,𝛿, and D-Error types the else branch under Γ,――𝛿; D-If gives clause 1.
For clause 2, theorem 10.9 derives the semantic premise Γ⊧𝖺𝗅𝗅𝑝 from the accepted certificate, so D-Get and D-Let type 𝐸. If 𝜃 respects Γ, every conjunct of 𝑝𝜃 is true. Applying E-IfT once per conjunct removes the nested guards and reaches 𝐸𝜃. Thus 𝗀𝗎𝖺𝗋𝖽(𝑝,𝐸)𝜃⟼∗𝐸𝜃. ◻
Flanagan’s hybrid checker inserts a cast when subtyping is unknown (Section 3, Figure 6, proceedings p. 250), and proves results for that richer calculus in Section 5, pp. 252–254, and Appendix A [Fla06]. Findler and Felleisen motivate delayed checking and blame in Sections 2.2–2.4, pp. 50–52, then give the calculus, monitor semantics, compilation, and correctness results in Sections 3–6, pp. 53–57 [FF02]. The operation 𝗀𝗎𝖺𝗋𝖽(𝑝,𝐸) of definition 10.29 is the first-order specialization: it checks a finite conjunction before the array operation and returns 𝖾𝗋𝗋𝗈𝗋 when a conjunct fails.
★☆☆ Instrument the unguarded program of proposition 10.2 with 𝗀𝗎𝖺𝗋𝖽. Reduce it on ⟨7⟩ through both tests and show that it reaches 𝖾𝗋𝗋𝗈𝗋 rather than the stuck get. Identify the test that fails.
A producer sends source term 𝑒, proposed type 𝑡, and edge lists Π: (𝑒,𝑡,Π). The consumer reparses 𝑒,𝑡, recomputes the VCs, and replays Π against that recomputed list before evaluation:
parse 𝑒 and 𝑡 and check well-formedness;
recompute C by running the deterministic VC generator on ⋅⊢𝑒⇐𝑡; producer-supplied VCs are ignored;
match Π against C and replay every certificate with the checker of definition 10.6;
enable evaluation only after all checks accept.
The trusted base is the parser and well-formedness checker, VC generator, certificate replay checker with mathematical integer arithmetic, and the reduction implementation. The producer, inference search, shortest-path search, and certificate generator are untrusted.
If the consumer accepts (𝑒,𝑡,Π), then ⋅⊢𝑒⇐𝑡, and every state reachable from 𝑒 is a value, 𝖾𝗋𝗋𝗈𝗋, or can take a step. If 𝑡 is a base refinement and evaluation returns a value, that value satisfies the refinement. Conversely, whenever VC generation succeeds and all generated VCs are semantically valid, the consumer accepts some certificate bundle.
Proof. Acceptance says that replay accepted a certificate for each recomputed VC. By theorem 10.9, every recomputed VC is valid; then theorem 10.17 derives ⋅⊢𝑒⇐𝑡. Preservation and progress classify every reachable state, and lemma 10.18(1) proves that a returned base value satisfies its refinement. Conversely, theorem 10.9 assigns every valid generated sequent a path certificate or, for an inconsistent context, a negative-cycle certificate. The replay equations accept those finite edge lists. ◻
Necula separates an untrusted producer from a consumer that defines a safety policy and validates evidence (Sections 1–3, pp. 106–111) [Nec97]. Its Theorem 3.1 uses an assembly-language VC generator. Here the consumer recomputes the source-language VCs, and theorem 10.17, corollary 10.26 give the stated acceptance result for that generator.
The nondependent boundary
The arrow 𝑥:𝑠→𝑡 is dependent in a deliberately restricted sense: the codomain refinement may mention the integer value 𝑥 or the length 𝐿𝑥. The calculus as a whole is still a refinement of a nondependent simply typed language. Its erasure target has simple types 𝜏::=𝗂𝗇𝗍∣𝖺𝗋𝗋∣𝜏→𝜏 and the same A-normal term constructors and reduction rules as section 10.1, with lambda and fixpoint annotations drawn from 𝜏. Dynamic conditionals, including those introduced by 𝗀𝗎𝖺𝗋𝖽, remain executable syntax. Only logical guard entries in a typing context are erased.
Use the shape erasure |𝑡| defined in definition 10.4. Erase a context by mapping declarations to their simple shapes and dropping logical guard entries. Term erasure is homomorphic, except that a lambda or fixpoint annotation is replaced by its shape. In particular, |𝗂𝖿𝛿𝗍𝗁𝖾𝗇𝑒1𝖾𝗅𝗌𝖾𝑒2|=𝗂𝖿𝛿𝗍𝗁𝖾𝗇|𝑒1|𝖾𝗅𝗌𝖾|𝑒2|,|𝖾𝗋𝗋𝗈𝗋|=𝖾𝗋𝗋𝗈𝗋.
Write Ξ⊢0𝑎⇒𝜏, Ξ⊢0𝑐⇒𝑐𝜏, and Ξ⊢0𝑒⇐𝜏. Simple contexts contain only declarations 𝑥:𝜏. Besides the ordinary base and arrow formation rules, the target typing rules are exactly these:
𝑥:𝜏∈Ξ
Ξ⊢0𝑥⇒𝜏
ST-Var
Ξ⊢0𝑛⇒𝗂𝗇𝗍
ST-Int
Ξ⊢0𝐴⇒𝖺𝗋𝗋
ST-Array
Ξ,𝑥:𝜏⊢0𝑒⇐𝜎
Ξ⊢0(𝜆𝑥.𝑒:𝜏→𝜎)⇒𝜏→𝜎
ST-Lam
Ξ,𝑓:(𝜏→𝜎),𝑥:𝜏⊢0𝑒⇐𝜎
Ξ⊢0(𝖿𝗂𝗑𝑓(𝑥).𝑒:𝜏→𝜎)⇒𝜏→𝜎
ST-Fix
Ξ⊢0𝑎⇒𝗂𝗇𝗍
Ξ⊢0𝑎+𝑘⇒𝑐𝗂𝗇𝗍
ST-Shift
Ξ⊢0𝑎⇒𝖺𝗋𝗋
Ξ⊢0𝗅𝖾𝗇𝑎⇒𝑐𝗂𝗇𝗍
ST-Length
Ξ⊢0𝑎⇒𝖺𝗋𝗋Ξ⊢0𝑖⇒𝗂𝗇𝗍
Ξ⊢0𝗀𝖾𝗍𝑎𝑖⇒𝑐𝗂𝗇𝗍
ST-Get
Ξ⊢0𝑓⇒𝜏→𝜎Ξ⊢0𝑎⇐𝜏
Ξ⊢0𝑓𝑎⇒𝑐𝜎
ST-App
Ξ⊢0𝑎⇒𝜏
Ξ⊢0𝑎⇐𝜏
ST-Atom
Ξ⊢0𝑐⇒𝑐𝜏Ξ,𝑥:𝜏⊢0𝑒⇐𝜎
Ξ⊢0𝗅𝖾𝗍𝑥=𝑐𝗂𝗇𝑒⇐𝜎
ST-Let
𝛿over𝑉(Ξ)Ξ⊢0𝑒1⇐𝜏Ξ⊢0𝑒2⇐𝜏
Ξ⊢0𝗂𝖿𝛿𝗍𝗁𝖾𝗇𝑒1𝖾𝗅𝗌𝖾𝑒2⇐𝜏
ST-If
Ξ⊢0𝜏𝗍𝗒𝗉𝖾
Ξ⊢0𝖾𝗋𝗋𝗈𝗋⇐𝜏
ST-Error
Here 𝑉(Ξ) contains 𝑥 at integer shape and 𝐿𝑥 at array shape. There is no target subsumption judgment: the erasure of a source subtype derivation is equality of simple shapes.
A refinement changes which existing base values inhabit a type; it does not compute a type from an arbitrary program.
In particular, this syntax has no family application 𝐹𝑒, no vectors indexed by a term, no equality type, and no conversion rule comparing indices by program reduction. Predicates can mention only 𝟎, integer variables, and array-length measures, in the one form 𝑟1−𝑟2≤𝑘. Array contents and function values are invisible to the logic. Calling this a fully dependent type theory would therefore confuse a restricted logical dependency inside refinements with arbitrary terms occurring in types.
For example, the family-shaped expression 𝖵𝖾𝖼𝗂𝗇𝗍(𝑛+1) is not a type in this grammar. The property needed for an array of exactly that length is nevertheless expressible, in context 𝑛:𝗂𝗇𝗍, as {𝜈:𝖺𝗋𝗋∣𝐿𝜈−𝑛≤1∧𝑛−𝐿𝜈≤−1}. This comparison separates a missing type-family former from a relation that the deliberately small refinement logic can already state.
There is also genuine result dependency inside the admitted boundary. Put 𝖫𝖾𝗇𝖮𝖿(𝑎):={𝜈:𝗂𝗇𝗍∣𝜈−𝐿𝑎≤0∧𝐿𝑎−𝜈≤0}. Length synthesis gives 𝑎:𝖺𝗋𝗋⊢𝗅𝖾𝗇𝑎⇒𝑐𝖫𝖾𝗇𝖮𝖿(𝑎). Therefore the one-bind function (𝜆𝑎.𝗅𝖾𝗍𝑛=𝗅𝖾𝗇𝑎𝗂𝗇𝑛:𝑎:𝖺𝗋𝗋→𝖫𝖾𝗇𝖮𝖿(𝑎)) synthesizes the stated arrow type: the continuation variable already has the exact expected refinement. The codomain depends on the input’s observed length, but no type is computed by applying a family to 𝑎. This is the positive half of the boundary rather than merely a list of missing formers.
Let 𝜌 and 𝜌′ agree on every integer variable and on the length of every array variable in the scope of 𝑝. Then 𝜌⊧𝑝 iff 𝜌′⊧𝑝. In particular, no refinement in this chapter distinguishes two arrays of the same length by their elements.
Proof of Lemma 10.34 — Measure indistinguishability
Proof. The two valuations assign the same integer to 𝟎, to every integer vertex 𝑥, and to every measure vertex 𝐿𝑎. Therefore they give the same two integers to the left side of every atom 𝑟1−𝑟2≤𝑘, so they agree on the truth of that atom. Induction over the finite conjunction proves the first claim. For the second, change the elements of one array while retaining its length and apply the first claim to every refinement in which its variable occurs. ◻
This is a static limitation, not a claim that execution ignores elements: 𝗀𝖾𝗍 returns an element, but its result has the unrefined type 𝗂𝗇𝗍. Enriching that result with a predicate about the stored value would require a new observable measure and a new solver theory, followed by a new certificate and safety proof.
Refinement checking is conservative even for this small operational language. Let 𝗅𝗈𝗈𝗉:=(𝖿𝗂𝗑𝑓(𝑥).𝗅𝖾𝗍𝑦=𝑓𝑥𝗂𝗇𝑦:𝑥:𝗂𝗇𝗍→𝗂𝗇𝗍) and consider 𝗅𝖾𝗍𝑦=𝗅𝗈𝗈𝗉0𝗂𝗇𝗅𝖾𝗍𝑧=𝗀𝖾𝗍⟨7⟩1𝗂𝗇𝑧. Every finite execution prefix remains in the first recursive call, so the out-of-bounds read is never reached and the program never gets stuck. Nevertheless the checker rejects its continuation: the false bound 1<1 is still a premise of D-Get. Thus safety does not imply typability; the rules deliberately avoid termination-sensitive dead-code reasoning.
If a formation, subtyping, synthesis, or checking judgment of this chapter holds, type erasure is shape preserving and term erasure yields the corresponding well-formed or well-typed judgment of the simply typed target. If Γ⊢𝑠<:𝑡, then |𝑠|=|𝑡|. The converse fails: the closed unguarded last-index program of proposition 10.2 is simply typed but is rejected by refinement typing.
Proof. Induct over formation, subtyping, and the three typing judgments. Base formation erases to its base sort, arrow formation to simple arrow formation, and both subtyping rules relate equal erased shapes by proposition 10.13. Each atom and computation rule erases to the simple rule for the same constructor. The semantic bounds premise of D-Get has no simple counterpart. Let, conditional, lambda, fixpoint, and error use their homomorphic target rules; a logical guard in the source context is irrelevant to simple typing, while a source conditional is retained.
For the converse, let 𝐴=⟨7⟩. Simple typing assigns 𝐴 type 𝖺𝗋𝗋, 𝗅𝖾𝗇𝐴 type 𝗂𝗇𝗍, and 𝗀𝖾𝗍𝐴𝑛 type 𝗂𝗇𝗍. In the refinement checker, however, length synthesis fixes 𝑛=1 and the exact array length is also 1. The upper get VC is therefore 1<1, which is false. Inversion of D-Let and D-Get shows that this failed premise is unavoidable. ◻
The stopping point is exact. Difference refinements already demonstrate program-specific implication, proof replay, inference relative to a finite logical vocabulary, and certified removal of a dynamic check. Arbitrary term indices would additionally require a term-indexed type grammar, a conversion judgment, and metatheory for that conversion; none is assumed by the safety or PCC result proved here.
Sources.
Refinement motivation is due to Freeman and Pfenning; the difference-constraint syntax and negative-cycle criterion are from Ramalingam et al.; the finite qualifier-template method is from Rondon et al. [FP91, RSJM99, RKJ08]. Flanagan and Findler–Felleisen define the cited dynamic-check boundaries; Necula defines the producer/consumer PCC architecture [Fla06, FF02, Nec97]. Freeman and Pfenning lift finite, programmer-declared datatype-refinement lattices through function types; the calculus here instead uses arithmetic refinements. See Sections 1, 3–4, and 6 of their paper (proceedings pp. 268–275) [FP91].
Suggested first pass.
Begin with exercise 10.9 to audit the trust boundary; then use exercise 10.10 to test the exact expressive boundary of the arithmetic fragment.
★☆☆ Suppose a malicious producer sends last with the get VC omitted but includes valid certificates for every VC it reports. Explain exactly which protocol step rejects the package. Then explain why accepting the producer’s VC list without recomputation would invalidate theorem 10.32.
★★☆ Prove that no refinement {𝜈:𝗂𝗇𝗍∣𝑝} in the empty context contains exactly the even integers. Hint: after normalization, and allowing every integer constant 𝑘∈ℤ, an atom using only 𝟎 and 𝜈 is an upper bound 𝜈≤𝑘, a lower bound 𝑘≤𝜈, or a constant truth value. A finite conjunction therefore describes all of ℤ, an empty set, a finite interval of integers, or a one-sided interval, none of which is the set of even integers.
★★★Practical project.refinement-certificate-replay Build a producer and independent consumer that generate and replay every verification condition for last and lastOrZero. Recompute the VC list before checking producer certificates. Maintain the invariant that each path begins at the requested source, follows identified graph edges, ends at the requested target, and has weight no greater than the claimed bound. The run must print eight PASS lines and end with All 8 refinement-certificate corpus cases passed.; the audit must be empty. Test malicious producers that weaken a bound, skip a VC, and send a malformed certificate; the consumer must reject each package.