UNIVERSITY OF CALIFORNIA
SANTA CRUZ
EXECUTABLE REFINEMENT TYPES

arXiv:1403.3336v1 [] 13 Mar 2014

A dissertation submitted in partial satisfaction of the
requirements for the degree of
DOCTOR OF PHILOSOPHY
in
COMPUTER SCIENCE
by
Kenneth L. Knowles
March 2014

The dissertation of Kenneth L. Knowles
is approved:

Professor Cormac Flanagan, Chair

Professor Luca de Alfaro

Professor Ranjit Jhala

Tyrus Miller
Vice Provost and Dean of Graduate Studies

Copyright c by
Kenneth L. Knowles
2014

Table of Contents
vii

List of Figures
Abstract

ix

Acknowledgments
1 Introduction
1.1 Specification Disciplines . . . . .
1.1.1 Dynamic Contracts . . . .
1.1.2 Refinement Types . . . .
1.1.3 Extended Static Checking
1.2 Thesis Overview . . . . . . . . .

xii
.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

2 Preliminaries
2.1 Preparatory Readings . . . . . . . . . .
2.2 The Simply-Typed λ-Calculus . . . . . .
2.2.1 Syntax of STLC . . . . . . . . .
2.2.2 Operational Semantics of STLC .
2.2.3 The STLC Type System . . . . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

3 Executable Refinement Types
3.1 The Language λH . . . . . . . . . . . . . . . . .
3.2 Operational Semantics of λH . . . . . . . . . .
3.3 Denotation of λH types to sets of ST LC terms
3.4 The λH Type System . . . . . . . . . . . . . .
3.4.1 Typing for λH . . . . . . . . . . . . . .
3.4.2 Type well-formedness for λH . . . . . .
3.4.3 Environment well-formedness for λH . .
3.4.4 Subtyping for λH . . . . . . . . . . . . .
3.4.5 Implication and Closing Substitutions .
3.5 Type Soundness for λH . . . . . . . . . . . . .
3.6 Extensional Equivalence for λH . . . . . . . . .
iii

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.

1
3
4
6
7
8

.
.
.
.
.

13
13
14
15
16
19

.
.
.
.
.
.
.
.
.
.
.

23
25
28
30
32
33
34
35
36
39
41
47

3.7

Related Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4 Hybrid Type Checking
4.1 The Language λHTC
H . . . . . . . . . . . . . . . . . . . .
4.2 Operational Semantics of λHTC
. . . . . . . . . . . . .
H
4.3 The Type System of λHTC
.
.
.
.
. . . . . . . . . . . .
H
4.4 Hybrid Type Checking for λHTC
.
. . . . . . . . . . . .
H
4.4.1 Implication Algorithm . . . . . . . . . . . . . .
4.4.2 Subtyping Algorithm . . . . . . . . . . . . . . .
4.4.3 Cast Insertion . . . . . . . . . . . . . . . . . . .
4.4.4 Correctness of Cast Insertion . . . . . . . . . .
4.5 An Example of HTC . . . . . . . . . . . . . . . . . . .
4.6 Static Checking vs. Hybrid Checking . . . . . . . . . .
4.7 Related Work . . . . . . . . . . . . . . . . . . . . . . .
4.7.1 Static structural types, dynamic contracts . . .
4.7.2 Optimizing implied dynamic checks . . . . . .
4.7.3 Dynamic typing in a statically-typed language
5 Type Reconstruction
5.1 Type Reconstruction for λRecon
. . . . . . . .
H
5.2 Constraint Generation . . . . . . . . . . . . .
5.2.1 Constraint Generation for λRecon
terms
H
5.2.2 Constraint Generation for λRecon
types
H
5.3 Shape Reconstruction . . . . . . . . . . . . .
5.4 Strongest Refinements . . . . . . . . . . . . .
5.4.1 Free Variable Elimination . . . . . . .
5.4.2 Delayed Substitution Elimination . . .
5.4.3 Placeholder Solution . . . . . . . . . .
5.5 Type Reconstruction is Typability-Preserving
5.6 Related Work . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

56

.
.
.
.
.
.
.
.
.
.
.
.
.
.

59
64
66
68
74
75
76
78
82
84
88
95
95
96
97

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

99
102
104
105
108
113
120
123
125
127
130
131

6 Compositional and Decidable Checking
6.1 Compositional Reasoning . . . . . . . . . . . . . . . . . . . . . . .
6.1.1 Dependent Types Perform Non-compositional Reasoning . .
6.1.2 Compositional Reasoning for Executable Refinement Types
6.1.3 Expressiveness and Exactness . . . . . . . . . . . . . . . . .
6.1.4 Decidable Type Checking . . . . . . . . . . . . . . . . . . .
6.2 The Language λComp
. . . . . . . . . . . . . . . . . . . . . . . . . .
H
6.3 Operational Semantics of λComp
. . . . . . . . . . . . . . . . . . . .
H
Comp
6.4 Denotation of λH types to sets of ST LC terms . . . . . . . . . .
6.5 The λComp
Type System . . . . . . . . . . . . . . . . . . . . . . . .
H
6.5.1 Typing for λComp
. . . . . . . . . . . . . . . . . . . . . . . .
H
6.5.2 Type well-formedness for λComp
. . . . . . . . . . . . . . . .
H

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

134
136
138
141
142
143
144
146
146
148
149
152

iv

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

6.5.3 Subtyping for λComp
. . . . . . . . . . . .
H
6.5.4 Non-dependent Function Application . . .
6.5.5 Self Types . . . . . . . . . . . . . . . . . .
6.6 Extensional equivalence for λComp
. . . . . . . . .
H
6.7 The λComp
Type
System
is
Exact
.
. . . . . . . .
H
Comp
6.8 Type Soundness for λH
. . . . . . . . . . . . .
6.9 A Type-checking Algorithm for λComp
. . . . . . .
H
6.9.1 Algorithmic typing for λComp
. . . . . . .
H
6.9.2 Algorithmic type-wellformedness for λComp
H
6.9.3 Algorithmic subtyping for λComp
. . . . .
H
6.10 Example: Binary Search Trees in λComp
. . . . .
H
6.11 Related Work . . . . . . . . . . . . . . . . . . . .
6.11.1 Indexed Types . . . . . . . . . . . . . . .
6.11.2 Existential Types . . . . . . . . . . . . . .
6.11.3 Liquid Types . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

7 Sage: An Implementation of Executable Refinement
7.1 Overview of Sage features . . . . . . . . . . . . . . . .
7.1.1 Type Dynamic . . . . . . . . . . . . . . . . . .
7.1.2 Executable Refinement Types . . . . . . . . . .
7.1.3 First-Class Types . . . . . . . . . . . . . . . . .
7.2 Examples of Sage programs . . . . . . . . . . . . . . .
7.2.1 Binary Search Trees . . . . . . . . . . . . . . .
7.2.2 Regular Expressions . . . . . . . . . . . . . . .
7.2.3 Printf . . . . . . . . . . . . . . . . . . . . . . .
7.3 The Sage Core Language . . . . . . . . . . . . . . . .
7.4 Operational Semantics of Sage Core . . . . . . . . . .
7.5 The Sage Core Type System . . . . . . . . . . . . . .
7.5.1 Typing for Sage Core . . . . . . . . . . . . . .
7.5.2 Subtyping for Sage Core . . . . . . . . . . . .
7.5.3 Validity for Sage Core . . . . . . . . . . . . . .
7.6 Hybrid Type Checking for Sage . . . . . . . . . . . .
7.6.1 Cast Insertion and Checking . . . . . . . . . .
7.6.2 Cast Insertion and Synthesis . . . . . . . . . .
7.6.3 Algorithmic Subtyping . . . . . . . . . . . . . .
7.6.4 Walkthrough of hybrid type checking in Sage .
7.7 The Sage Architecture . . . . . . . . . . . . . . . . .
7.7.1 Theorem Prover . . . . . . . . . . . . . . . . .
7.7.2 Counter-Example Database . . . . . . . . . . .
7.8 Experimental Results . . . . . . . . . . . . . . . . . . .

v

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

Types
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

153
154
157
158
159
164
166
169
172
172
175
178
179
182
184

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

185
187
188
189
189
190
191
195
198
200
203
208
209
213
215
216
217
218
221
226
227
227
229
230

8 Contemporaneous Related Work
8.1 Dynamic Contracts . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.1.1 Foundations for untyped higher-order contracts and blame .
8.1.2 Higher-order contracts for lazy, typed languages . . . . . .
8.1.3 Parametric polymorphism in contracts . . . . . . . . . . . .
8.2 Static Contract Checking . . . . . . . . . . . . . . . . . . . . . . .
8.2.1 ESC/Haskell . . . . . . . . . . . . . . . . . . . . . . . . . .
8.2.2 Liquid Types . . . . . . . . . . . . . . . . . . . . . . . . . .
8.2.3 Other work . . . . . . . . . . . . . . . . . . . . . . . . . . .
8.3 Dependent/Refinement Types . . . . . . . . . . . . . . . . . . . . .
8.4 Gradual Typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

233
233
234
237
238
239
239
240
242
242
244

9 Conclusion
9.1 Summary . . . . . . . . . . . . . . . . . . . . . . . . . .
9.2 Open Avenues . . . . . . . . . . . . . . . . . . . . . . .
9.2.1 Parametric Polymorphism . . . . . . . . . . . . .
9.2.2 Multiple Representations For Specifications . . .
9.2.3 Contract Properties . . . . . . . . . . . . . . . .
9.2.4 Compositionality . . . . . . . . . . . . . . . . . .
9.2.5 Improving hybrid type checking . . . . . . . . . .
9.2.6 Side Effects . . . . . . . . . . . . . . . . . . . . .
9.2.7 Error messages, Testing, and Counterexamples .
9.2.8 Large-Scale Implementation for Empirical Work
9.3 Closing Remarks . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

248
248
250
251
251
252
252
253
253
254
254
255

Bibliography

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.

255

vi

List of Figures
2.1
2.2
2.3

Syntax of STLC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Operational Semantics of STLC . . . . . . . . . . . . . . . . . . . . . . .
Typing Rules for STLC . . . . . . . . . . . . . . . . . . . . . . . . . . .

15
17
20

3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
3.9
3.10

Syntax of λH . . . . . . . . . . . . . . . . . . . .
Operational Semantics of λH . . . . . . . . . . .
Shape of λH types and terms . . . . . . . . . . .
Denotation from λH types to sets STLC terms .
Typing Rules for λH . . . . . . . . . . . . . . . .
Type Well-formedness for λH . . . . . . . . . . .
Environment Well-formedness for λH . . . . . . .
Subtyping for λH . . . . . . . . . . . . . . . . . .
Implication for λH . . . . . . . . . . . . . . . . .
Extensional Equivalence Under Reduction for λH

4.1
4.2
4.3
4.4
4.5
4.6
4.7
4.8

Behavior of hybrid type checking for various
Syntax for λHTC
. . . . . . . . . . . . . . . .
H
Operation Semantics of λHTC
. . . . . . . .
H
HTC
Type rules for λH . . . . . . . . . . . . . .
Algorithmic Subtyping for λHTC
. . . . . . .
H
Cast insertion and Checking for λHTC
. . . .
H
HTC
Cast insertion and synthesis for λH terms
Cast insertion for λHTC
types . . . . . . . .
H

5.1
5.2
5.3
5.4
5.5
5.6
5.7
5.8

Syntax for λRecon
with type variables . . . . . . . . . . . . . .
H
Syntax for λRecon
constraint generation . . . . . . . . . . . . .
H
Constraint Generation Rules for λRecon
terms . . . . . . . . .
H
Constraint Generation Rules for λRecon
types . . . . . . . . . .
H
Recon
Syntax for λH
shape reconstruction . . . . . . . . . . . . .
Shape Reconstruction Algorithm . . . . . . . . . . . . . . . .
Syntax for λRecon
with strongest refinements . . . . . . . . . .
H
Additional Redex Reduction for Parallel Logical Connectives
vii

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

categories
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

26
29
30
31
33
35
36
37
39
49

of programs
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

61
65
66
69
77
79
81
81

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

103
106
107
109
115
117
122
122

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

5.9

Shorthands for λRecon
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
H

6.1
6.2
6.3
6.4
6.5
6.6
6.7
6.8
6.9
6.10
6.11
6.12
6.13
6.14

Syntax of λComp
. . . . . . . . . . . . . . . . . . . . .
H
Redex Evaluation for λComp
. . . . . . . . . . . . . .
H
Shape of λComp
types
and
terms
. . . . . . . . . . . .
H
Comp
Denotation from λH types to sets STLC terms . .
Type Rules for λComp
. . . . . . . . . . . . . . . . . .
H
Definition of self . . . . . . . . . . . . . . . . . . . .
Type well-formedness for for λComp
. . . . . . . . . .
H
Subtyping for λComp
.
.
.
.
.
.
.
.
. . . . . . . . . .
H
Extensional Equivalence Under Reduction for λComp
H
Syntax for Algorithmic Typing of λComp
. . . . . . .
H
Shorthand for pushing case expressions inside types .
Algorithmic Typing for λComp
. . . . . . . . . . . . .
H
Algorithmic Type Well-formedness for λComp
. . . . .
H
Comp
Algorithmic Subtyping for λH
. . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

145
147
148
149
150
153
154
155
160
167
168
171
172
173

7.1
7.2
7.3
7.4
7.5
7.6
7.7
7.8
7.9
7.10
7.11
7.12
7.13

Binary Search Trees in Sage . . . . . . . .
Regular Expressions in Sage . . . . . . . .
Syntax of Sage Core . . . . . . . . . . . . .
Sage Shorthands . . . . . . . . . . . . . . .
Operational Semantics for Sage Core . . .
Operational Semantics for Sage Casts . . .
Type Rules for Sage Core . . . . . . . . . .
Example types of Sage constants . . . . . .
Subtyping Rules for Sage Core . . . . . . .
Cast Insertion and Checking for Sage Core
Cast Insertion for Sage Core . . . . . . . .
Algorithmic Subtyping for Sage Core . . .
Sage Architecture . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

194
195
201
203
205
206
210
211
214
218
219
223
228

viii

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

Abstract
Executable Refinement Types
by
Kenneth L. Knowles
Precise specifications are integral to effective programming practice. Existing specification disciplines such as structural type systems, dynamic contracts, and extended
static checking all suffer from limitations such as imprecision, false positives, false negatives, or excessive manual proof burden. New ways of expressing and enforcing program
specifications are needed.
Towards that end, this dissertation introduces executable refinement types
and establishes their metatheory and accompanying implementation techniques. Executable refinement types enrich structural type systems with basic types refined by
semi-decidable predicates. Through the lens of executable refinement types, we also address the broader problem of theory and implementation for undecidable type systems.
To establish a firm foundation for the study of executable refinement types,
this dissertation presents a full formal account of their metatheory. Type checking for
executable refinement types is undecidable. Nonetheless, they fulfill standard metatheoretical correctness criteria including type soundness and extensional equivalence.
To perform type checking for executable refinement types we introduce hybrid
type checking, a type enforcement strategy broadly applicable to undecidable type systems. Hybrid type checking enforces specifications via static analysis where possible and
ix

dynamic type casts where necessary. We prove that for any decidable approximation of
executable refinement types, either: (1) Hybrid type checking catches some errors statically which the decidable approximation would miss, or (2) the decidable approximation
rejects some correct program which hybrid type checking would accept.
To perform type reconstruction for executable refinement types, we radically
revise the usual notion of type reconstruction. Typeability is undecidable because it
subsumes type checking. Instead, we propose a more precise definition of type reconstruction as a typeability-preserving transformation. For decidable type systems, our
definition coincides with the previous. Using our generalized notion of type reconstruction, we demonstrate that type reconstruction for executable refinement types is
decidable even though type checking is not! We show this by providing a syntactic type
reconstruction algorithm reminiscent of strongest postcondition calculation.
To enlarge the class of programs for which type checking is decidable, we
formalize the notion of compositional reasoning for types systems. Because standard
dependent types perform non-compositional reasoning, type checking is undecidable
even when all types appearing in a program fall in a decidable specification language.
We present a variant of dependent types which uses existential types to achieve compositional reasoning. Even restricted to compositional reasoning, our type system is
exact: It can give any term a type that completely classifies that term up to contextual
equivalence. When reasoning compositionally, if all the annotations in a program fall
into a decidable language, then type checking is decidable. We show this with a type
checking algorithm for such programs.
x

Atop these theoretical foundations we implement Sage, a language blending
executable refinement types, dynamic typing, and first-class types. Sage’s implementation includes standard type-checking machinery, compile-time computation, automatic
theorem proving, dynamic contract checking, and a database of run-time failures which
inform the hybrid type checker for future runs. Preliminary experiments indicate that
Sage is effective at verifying many common examples statically in a reasonable amount
of time. Moreover, every run-time failure in Sage can occur at most once: From then
onwards it becomes a compile time failure.

xi

Acknowledgments
I entered graduate study in search of a community of intellectuals, and I found it at UC
Santa Cruz. I have never encountered such a spirited and surprising group of scholars,
nor any with as broad an engagement with mathematics, computing, and philosophy,
as I found at UCSC. For my years with them, I am immeasurably grateful.
Particularly for their helpful attention to this work, I thank my advisor, Cormac Flanagan, and my reading committee members, Luca de Alfaro and Ranjit Jhala.
Chapters of this dissertation include expository reorganizations of work by the
author and colleagues, updated to unify notation and theory, engage in more explanation and discussion than possible in a terse conference paper, provide greater detail in
proofs, and survey subsequent related work. I would like to thank my coauthors Cormac
Flanagan, Stephen Freund, Aaron Tomb, and Jessica Gronski for their contributions to
Gronski et al. [2006], Knowles and Flanagan [2007], Knowles and Flanagan [2009], and
Knowles and Flanagan [2010].
Beyond my the intellectual engagement of my colleagues, I also owe completion
of this dissertation to support offered by my family and friends, so I would like to
especially thank my mother, my father, and Meg.

xii

xiii

Summary of Metavariables

e, d, f, g, q, p

term

x, y, z

variable

k

primitive constant or function

c

constructor supporting pattern matching

b

boolean constant

m, n

natural number or natural number constant

δ

semantic function for constants

S, T, U

type

B

basic type

Q, R

restricted type with limited predicate language

E, F

augmented type with covariant existentials
√
three-valued certainty (one of ×, , ?)

a, b
Γ

typing environment

∆

typing environment with augmented types

θ

capture-avoiding substitution function

σ, γ

closing substitution

ζ

approximation function to decidable refinements

ψ

term placeholder

π

non-capture-avoiding type replacement

ρ

non-capture-avoiding placeholder replacement

C

term context

D

type context

E

call-by-value evaluation context

S

unfolding evaluation context

W

head-normal form evaluation context

xiv

Summary of Notations

λx : T. e

function term

f e

function application

case e of c Z⇒ f

case discrimination term

hT ⊳ Si

type cast

hT i

single-type cast

h{x : B | p}, q, ki

cast in progress

S→T

function type

x:S → T

dependent function type

{x : B | e}

refinement type

∃x : S. T

existential type

⌊T ⌋, ⌊e⌋

shape of type or term

[[T ]]

denotation of type

×,

√

,?

certainty values of “no”, “yes”, “maybe”

[x 7→ e]

substitution mapping x to e

[x 7→ e : T ]

substitution mapping x to e with type annotation

θ·α

delayed substitution applied to a type variable

θ·ψ

delayed substitution applied to a placeholder

C[e], D[e], E [e], S[e], W[e]

context application

xv

Summary of Relations

single-step redex reduction

e1 y e2
e1

e2 , S

e1

+

e1

∗

e2 , S
e2 , S

single-step term or type reduction

T
+

∗

T

T

transitive closure of single-step reduction
reflexive-transitive closure of single-step reduction

e1 ! e2

equivalence closure of single-step reduction

e1 ≡ e2

contextual equivalence of terms

SfT

wedge product of types

σfγ

wedge product of closing substitutions

xvi

Summary of Judgments

Γ⊢e : T

typing

Γ⊢T

type well-formedness

⊢Γ

typing environment well-formedness

Γ ⊢wf θ

delayed substution well-formedness

Γ ⊢ S <: T

subtyping

⊢σ : Γ

closing substitution typing

Γ ⊢ e1 ∼ e2 : T

extensional equivalence of terms

⊢σ ∼ γ:Γ

extensional equivalence of closing substitutions

Γ⊢e∈T

semantic typing

Γ⊢S⊆T

semantic subtyping

Γ ⊢ e1 ⇒ e2

implication

Γ |= p

validity

Γ  d ֒→ e : T

cast insertion

Γ  d ֒→ e ↓ T

cast insertion and checking

Γ  S ֒→ T

cast insertion in types

Γ a S <: T

three-valued algorithmic subtyping

Γ a S ⇒ T

three-valued algorithmic implication

Γe : E

decidable algorithmic typing

ΓR

decidable algorithmic type well-formedness

Γ  R <: E

decidable algorithmic subtyping

xvii

Chapter 1
Introduction

A program is, of necessity, a completely unambiguous document. To manage
the enormous detail that this entails, Parnas [1972a] articulates a modular development
strategy based on information hiding where trusted and precisely specified abstractions
of the sections of a program – now known as interfaces – reduce the amount of detail
that must be considered at one time, enabling cooperation amongst programmers over
space and time.
In practice, however, interfaces are often only informally or partially specified. Consider the following increasingly-precise specifications for the argument to a
procedure for inverting a matrix:

1. The argument can be any value expressible in the programming language.
2. The argument must be an array of arrays of numbers.
3. The argument must be a matrix, that is, a rectangular (non-ragged) array of arrays
1

of numbers.
4. The argument must be a square matrix.
5. The argument must be a square matrix with nonzero determinant.

All of these specifications may be useful for various purposes, levels of assurance, and moments in the development of a program. Simpler specifications are common
during rapid prototyping, at the cost of proliferation of partial functions (functions which
are undefined on portions of their purported domain). More precise specifications provide stronger correctness guarantees and better documentation, and simplify function
invocation by reducing or removing the possibility of passing a “bad” input.
The first specification corresponds approximately to what are called dynamically typed (also unityped or single-sorted) programming languages. The second item
is a likely specification in statically typed languages such as Java, C++, C#, OCaml,
or Haskell, which automatically distinguish sorts of program values. The last three
specifications remain the stuff of research, including this dissertation. Often such specifications, if they are checked at all, are dynamically checked by ad-hoc hand-written
methods as part of a program’s execution.
As the precision afforded by a specification language – its expressiveness – increases, so does the computational complexity of determining whether a program meets
a specification. Specifications written in less expressive languages, such as traditional
type systems (or no language at all in the case of unityped languages), can be checked
statically: There is an algorithm which, given the text of a program, either accepts that
2

the program obeys the interface discipline or rejects the program as ill-formed. In other
words, the question of whether a program meets a specification is decidable.
More precise specifications, such as those written in formal logics including
recursion/induction, often cannot be checked statically: They are written in a language
expressive enough that no algorithm can exist that correctly accepts or rejects all programs. This does not preclude the existence of useful approximate algorithms, but
every one must include false negatives (programs which do not meet their specification
but are accepted) or false positives (programs which do meet their specification but are
rejected) or both.1

1.1

Specification Disciplines
Generally, static checking reasons about the text of a program prior to its

execution, thus implicitly reasons about all possible inputs. Dynamic checking avoids
this universal quantification by reasoning only about the values that arise during a
particular execution. Many properties that are difficult to establish statically are easy
or trivial to ensure dynamically, for example that a pointer is non-null or that a binary
tree is balanced.
1

The distinction between whether acceptance or rejection is the positive case is arbitrary. In program
verification spurious errors are usually considered false positives, while programs that may crash are
considered false negatives.

3

1.1.1

Dynamic Contracts
Dynamic contracts are a programming construct for organizing and composing

hand-written routines that dynamically enforce interface specifications and for isolating
the root cause of a violation during program execution. Many common specifications
can be expressed as executable contracts,2 such as:
- Subranges: The function squareRoot requires its argument to be a non-negative
number.
- Aliasing restrictions: The function swap requires that its arguments are distinct
reference cells.
- Ordering restrictions: The function binarySearch requires that its argument is a
sorted array.
- Size specifications: The function serializeMatrix takes as input a matrix of size
n by m, and returns a one-dimensional array of size n × m.
- Arbitrary executable predicates: an interpreter (or code generator) for a typed
language (or intermediate representation [Tarditi et al. 1996]) might naturally require that its input be well-typed, i.e., that it satisfies the programmer-written
(and likely optimized, hence obfuscated) predicate wellTyped : Expr → Bool.
Contracts have been incorporated into many languages, including Turing [Holt and Cordy
1988], Anna [Luckham 1990], Eiffel [Meyer 1992], Sather [Stoutamire and Omohundro
2

Notable classes of properties that are not easily checked dynamically are concurrency properties
such as race-freedom or atomicity and properties requiring (potentially) infinite quantification such as
associativity or injectivity.

4

1996], Blue [Kölling and Rosenberg 1996], Scheme [Findler and Felleisen 2002], Spec#
[Barnett et al. 2005], JML [Burdy et al. 2005; Leavens and Cheon 2006], and Haskell [Hinze et al.
2006; Chitil 2012]. In these languages, functions may have precondition checks (executed before the body of the function is evaluated), postcondition checks (executed after
the result of the function has been computed), and invariants (executed when a data
structure is modified, to ensure its integrity).
However, dynamic checking suffers from two limitations. First, it incurs a
computational cost during execution, often running checks repeatedly and redundantly,
and in the worst case is expensive enough that it is feasible only during testing. For
example constraining a binary tree by the contract isBinarySearchTree will require a
full traversal of the tree with every insertion, changing the complexity of the operation
from O(log n) to Ω(n).3 More seriously, the very property of dynamic checking that
makes it simple – that specifications are only checked on data values and code paths
of actual executions – often results in incomplete and late (possibly post-deployment)
detection of defects. For example, consider the function abs for computing the absolute
value of an integer, subject to the contract Int → NonNeg which states that abs requires
an integer input and returns a non-negative result. If we mistakenly simply return the
input as the output, then the function violates its contract for all negative inputs. Yet
executions such as (abs 4) will leave this defect undiscovered.
3

The “solution” to this problem is to write only those contracts which can be efficiently executed,
and to make sure they are only checked when necessary, via clever programming.

5

1.1.2

Refinement Types
The term refinement type 4 is not an agreed-upon mathematical term, but an

intuitive label applied to type systems where the basic types of a language may be
“refined” to classify a more precise subset of the terms of the language. For example,
if the type of linked lists in a structural type system were List, then one may consider
refining this type to carry information about the length of the list, writing List n to
describe lists of length n. This style of refinement type, now commonplace, is called an
indexed type where n is the index a refinement type.
Dependent ML [Xi and Pfenning 1999] includes a type system with indexed
types that can express and enforce properties involving linear inequalities between integer variables, and by this express subranges, ordering restrictions, and array size constraints. Other refinement type systems, each enhancing a structural type system with
some decidable logic, include Hayashi [1993], Denney [1998], Xi and Pfenning [1999], Xi
[2000], and Davies and Pfenning [2000], Mandelbaum et al. [2003], and Dunfield and Pfenning
[2004].
Refinement types improve on dynamic contract checking in terms of completeness, because once a program is accepted by the type system, it is certain to satisfy
all preconditions and postconditions for all possible executions, but the specification
language is intentionally restricted to ensure that specifications can always be checked
statically and efficiently so that the analysis can be incorporated into an iterative work4

The term refinement type originated with Freeman and Pfenning [1991] but is now used much more
broadly.

6

flow.
In addition to limitations to ensure decidability, properties enforced by indexed
types are expressed in an indirect style. A more direct way of expressing List n using
standard set comprehension notation is {x : List | length(x) = n}. More complex types
are even less direct, for example the type of lists of length greater than k in indexed
style is
∃n > k. List n
where a more direct transliteration of the specification would be
{x : List | length(x) > k}
For more sophisticated data structures and specifications, such as red-black trees, the
indices and methods for expressing the desired specification are even more complex.

1.1.3

Extended Static Checking
If expressivity is more desirable than efficiency but complete checking is still de-

sired, as it was for Parnas [1972b] and Dijkstra [1976], then extended static checking may
be attempted via a tool such as PVS [Rushby et al. 1998] or ESC/Java [Flanagan et al.
2002]. Extended static checking refers to static verification of specifications written
in a very expressive language of preconditions and postconditions. This combines the
benefits of complete checking and expressive specifications, but results in large numbers of false positives – programs where all specifications are satisfied, but the analysis
cannot verify that this is so. To direct the analysis towards successful verification, the
7

programmer must add many complex annotations or, in the extreme, provide a manual
proof of correctness.

1.2

Thesis Overview
Synthesizing the properties of the specification systems discussed so far, some

desirable properties of a specification system are:
- No false positives: Programs which meet their specification are accepted.
- No false negatives: Programs which do not meet their specification are rejected.
(This subsumes complete checking, in which a program must meet its specification
for all inputs or it is rejected.)
- Efficient checking: It can be efficiently and automatically determined whether a
program meets its specification. (This subsumes decidable checking and often
reduces to to efficient specification inclusion, in which it can be automatically
determined whether one specification is strictly more precise than another.)
- Low proof burden: A human need not manually prove complex properties.
- Specification reconstruction: When some specifications are omitted, they can be
automatically deduced. (This is similar to “low proof burden” if one considers
programming-supplied specifications as a lightweight form of manual proof.)
- Compositionality: The specification of a program term is a sufficient summary for
all further analyses.
8

- Readable specifications: The specification of a program is a self-explanatory mathematical statement about the program.
- Expressivity: A given set of program values can be described in the specification
language.
The open (and open-ended) problem that this dissertation addresses is the
search for practical combinations of complete checking and expressive specifications that
nonetheless enjoy as many of these desirable properties as may be achieved.
In particular, we narrow the search by working with the theoretical machinery
of type systems, while removing any restrictions to ensure decidability or efficiency of
type checking as conventionally construed. To illustrate the key idea common to these
type systems, consider the following type rule for function application with subtyping.
f : T →U

e : S

S <: T

f e : U
The antecedent f : T → U states that the program term f is a function that takes any
value of type T as input and returns a value of type U . The antecedent e : S states
that e is a program term of type S. The question of whether f may be applied to e
hinges on whether a member of type S is necessarily also a member of type T , formally
posed as “is S a subtype of T ?” and written S <: T . If the type checker can prove this
subtyping relation, then this application is accepted. Conversely, if the type checker
can prove that this subtyping relation does not hold, then the program is rejected. In
a conventional, decidable type system, one of these two cases always holds.
9

However, for type languages that are sufficiently expressive that type checking
is not decidable, the type checker will encounter situations where its algorithms can
neither prove nor refute the subtype judgment S <: T (particularly within the time
bounds imposed by interactive compilation). When subtyping is undecidable, then type
checing is undecidable, and type reconstruction as conventionally defined is undecidable.
How shall one proceed?
This dissertation defines such a type system: executable refinement types. Executable refinement types use the language of dynamic contracts, so that any predicate
that can be written in a programming language may refine a type. Thus in addition to
traditional types such as Int, Bool, and Int → Bool, the type system includes types
such as {x : Int | x > 0} (the type of positive integers) and x : Bool → {y : Bool | x ⇔ y}
(the type of functions from booleans to booleans where the return value is true if and
only if the input value is true). The techniques gleaned from a close engagement with
executable refinement types are broadly applicable, because executable refinement types
include all semidecidable specifications.
The contributions of this dissertation are organized into chapters as follows.

- Chapter 2 provides references to material containing adequate background to contextualize its contribution to the field of programming languages.
- Chapter 3 presents a core calculus λH which will form the theoretical basis of the
following chapters and establishes its basic correctness properties using a simple,
but novel, application of denotational semantics to provide a foundation for our
10

refinement type system. In addition to the usual soundness criteria, this chapter
introduces an original variation of the technique of logical relations to establish
the correctness of our notion of “function” as well as provide a powerful tool for
proving program equivalences.
- Chapter 4 introduces hybrid type checking (HTC), a general technique for combining a static type system with dynamic checks, and describes the correctness
properties any hybrid type checking system should have. We then illustrate hybrid
type checking for the core calculus of Chapter 3 and prove its correctness using
the proof tools of the prior chapter. Finally, we compare hybrid type checking
with static type checking, concluding that any decidable restriction always rejects
a well-typed program which HTC accepts or accepts an ill-typed program which
HTC rejects.
- Chapter 5 presents an original generalization of the type reconstruction problem
that is applicable to undecidable type systems, then presents a decision procedure
for full type reconstruction of λH .
- Chapter 6 examines the special case where all specifications are written in a decidable language, uncovering a critical flaw in the unrestricted use of dependent
types in this context. This flaw is resolved by the use of standard type-theoretic
techniques, and a decision procedure for type checking is provided and proved
sound.

11

- Chapter 7 presents Sage, an implementation of executable refinement types featuring hybrid type checking. In addition to refinement types, Sage includes dynamic types and first-class computation over types, demonstrating the breadth of
interesting undecidable type systems to which our techniques apply. This chapter
presents some preliminary empirical validation that hybrid type checking decides
most type checking queries statically.
- Chapter 8 surveys related work developed in parallel or subsequent to the material
presented in this dissertation, recapitulating citations from substantive chapters
into coherent research trends.
- Chapter 9 concludes and discusses future directions.

Chapters 1-3 should be read prior to any other chapters, but then all of Chapters 4-7 may be read mostly independently before the concluding discussion of Chapters 8 and 9.

12

Chapter 2
Preliminaries

This dissertation is best enjoyed with a strong background in programming
languages, especially type systems. The references and mathematical content of this
chapter are intended to prepare most readers for the remainder of the dissertation, or
at least to allow a reader to ascertain the degree of overlap between their experience
and the content herein. Please feel free to skip this chapter on first reading and return
to it as needed.
As it does not constitute part of the novel contribution of this dissertation (except as the particulars of presentation may differ from others) the material is presented
somewhat abruptly and without extensive motivation or discussion.

2.1

Preparatory Readings
The fundamentals of programming language theory that form the basic math-

ematical setting for this dissertation include: operational semantics, denotational se13

mantics, axiomatic semantics, type systems (particularly dependent type systems), and
logical relations.
For operational semantics and type systems, Pierce [2002] provides a thorough
introduction in a textbook setting, using notation and terminology similar to that of
this dissertation. Its sequel, Pierce [2004], covers advanced topics such as dependent
types and logical relations.
From axiomatic semantics [Floyd 1967; Hoare 1969; Dijkstra 1976] we draw
upon many descriptive terms for predicates that may be true of program fragments
such as preconditions (predicates that must hold before a procedure is begun), postconditions (predicates that must hold after a procedure completes), invariants (predicates
that must always hold), weakest preconditions (the weakest predicate that allows a fragment to complete successfully), and strongest postconditions (the strongest predicate a
fragment ensures). Winskel [1993] is a suitable textbook for a unified presentation.

2.2

The Simply-Typed λ-Calculus
The starting point of our metatheoretical work is the simply-typed λ-calculus

[Church 1940] augmented with built-in types such as Int and Bool, and primitives such
as boolean constants (true and false), integer constants (1, 4, −12, etc), conditionals
(if ), and recursion (in the form of a fixed-point operator fixT for each type T ) We call
this variant STLC.1 The material of this section is thoroughly treated by any introductory text, such as Pierce [2002], but we present it here because later developments refer
1

This system may also be considered a variant of PCF. [Scott 1969]

14

Figure 2.1: Syntax of STLC

e, d, f, g, q, p ::=
k
x
λx : S. e
f e

Terms:
primitive
variable
abstraction
application

v ::=

Values: (⊂ Terms)
primitive
abstraction

k
λx : S. e
S, T, U ::=
S→T
B

Types:
function type
basic type

directly to specific details. This section also may serve to familiarize the reader with
our notational conventions.

2.2.1

Syntax of STLC
The syntax for STLC described in this section is summarized via recursive

grammars in Figure 2.1 using our notation and metavariables. Many metavariables are
reserved for terms and types so that they may be varied for legibility.

- The type S → T denotes the type of functions from S to T .
- The type B stands for any of the built-in basic types. We freely assume that
these include familiar types such as Bool, Int, and Unit (the type with just one
element), while introducing others as necessary.
15

- The term k denotes any of the built-in primitives, which may be functions. We
freely assume that these include primitives for arithmetic (e.g. 0, 3, +, ×, >=),
conditionals (e.g. true, false, ifT for each type T ), and recursion (fixT for each
type T ).
- The term λx : S. e denotes a function taking a parameter x of type S and having a result calculated according to the body of the function e. The particular
name chosen for the parameter x is of no consequence. For example, the term
λx : Int. x + 3 is considered equal to λy : Int. y + 3 by definition. Variable binding
and renaming (α-equivalence) is covered thoroughly by the preparatory reading
of Section 2.1.
- The term x denotes a variable reference – variable references are called bound if
they occur within the scope of a λ-abstraction of the same variable, and otherwise
are called free. The set of free variables of a term e is written f v(e). Again, a
thorough discussion of free and bound variables is relegated to the background
reading of Section 2.1.
- The term f e denotes the application of the term f to the term e. Note that f and
e are simply metavariables chosen for legibility; either the function or argument
may be any term.

2.2.2

Operational Semantics of STLC

16

Figure 2.2: Operational Semantics of STLC

Redex Evaluation

dye

(λx : S. e) d y [x 7→ d] e
k v y δ(k, v)

Contextual Evaluation
C[d]

[E-β]
[E-δ]

d
C[e]

e

if d y e [E-Ctx]
C, D

Contexts
C ::= • | C e | e C | λx : S. C

Figure 2.2 gives formal meaning to STLC terms via a standard small-step
operational semantics. The relation d y e expresses evaluation of redexes, short for
“reducible expressions”. It means that by definition we consider d and e to be equivalent terms, and the notation is chosen to correspond to the intuition that a program
represented by d may be transformed to a program represented by e in a “single step”
of computation. What constitutes a step of computation here is not suitable for algorithmic analysis, but only as a formal tool for discussing equivalent programs.
The two redex evaluation rules in Figure 2.2 concern function application for
λ-abstractions and primitive functions:

[E-β]:

An application of a function λx : S. e to argument d is reduced by replacing the
formal parameter x by the actual parameter d. The notation [x 7→ d] denotes
17

the standard capture-avoiding substitution of x for d; it is not a program term
but the following operation carried out (by induction) on the syntax of e:
[x 7→ d] k

def

[x 7→ d] y

def

[x 7→ d] x

def

=

d

[x 7→ d] (f e)

def

=

([x 7→ d] f ) ([x 7→ d] e)

[x 7→ d] (λy : S. e)

def

λy : S. [x 7→ d] e

=

=

=

k
y

(y 6= x)

(y 6= x and y not free in d)

In the last equation, the bound variable y may always be chosen (via αequivalence) to satisfy the side condition. Capture-avoiding substitutions may
be composed like any other (meta-)function and are named using metavariables
θ, σ, and γ.
[E-δ]:

Application of a primitive function is resolved by a presumed partial function
δ : Prim × Term ⇀ Term
which is parameter of the system constrained (Requirement 2.1 on page 21) to
be total for appropriately typed arguments and to map them to appropriately
typed results.
We assume standard meanings for boolean and arithmetic connectives, conditionals, and the fixed point operator. For example, the following equations

18

(based on presumed primitives) should all hold for any definition of δ.
δ(not, true) = false
δ(not, false) = true
δ(δ(+, 3), 7) = 10
δ(ifT , true) = λx : T. λy : T. x
δ(ifT , false) = λx : T. λy : T. y
δ(fixT , e) = e (fixT e)
The single-step evaluation relation d

e denotes redex evaluation of an arbi-

trary subterm of d. Specifically, redex evaluation takes place within a context C that
represents a term with exactly one occurrence of the symbol • (a “hole”) where a term
may be placed, such as (λx : S. f •) d. The notation C[e] denotes the resulting term when
the e is placed in the unique hole in C. This operation is not capture-avoiding; e may
have free variables that become bound once it is placed in C. The reflexive-transitive
closure of

is written

∗

and may be read intuitively as “running the program for an

arbitrary number of steps”.

2.2.3

The STLC Type System
Figure 2.3 presents the typing rules for STLC in full, along with the definition

of typing environments Γ which bind variables to their declared types. Bindings in the
environments are ordered but environments may be freely treated as partial functions
from variables to types or sets of bindings when order is insignificant. The typing

19

Figure 2.3: Typing Rules for STLC

Γ ::=

Environments:
empty environment
environment extension

∅
Γ, x : T

Γ ⊢stlc e : T

STLC Typing
(x : T ) ∈ Γ

[STLC-Var]

[STLC-Prim]

Γ ⊢stlc x : T
Γ, x : S ⊢stlc e : T

Γ ⊢stlc k : ty(k)

[STLC-Fun]

Γ ⊢stlc f : S

Γ ⊢stlc λx : S. e : S → T

Γ ⊢stlc e : S → T

[STLC-App]

Γ ⊢stlc f e : T

judgment
Γ ⊢stlc e : T
reads “Assuming the bindings in Γ, the term e may be assigned STLC type T .” As with
the syntax and semantics, these typing rules are completely standard, and the following
narrative descriptions are provided as a reference for the notation.

[STLC-Var]: A variable x is assigned its bound type in the environment Γ.

[STLC-Prim]: A primitive k is given a type by the function ty, which is constrained

to maintain type soundness along with the δ function. Any definition of ty

20

includes such mappings as:
true
false
⇔
not
n
+
ifT
fixT

:
:
:
:
:
:
:
:

Bool
Bool
Bool → Bool → Bool
Bool → Bool
Int
Int → Int → Int
Bool → T → T → T
(T → T ) → T

Primitives with function types may be called primitive functions and primitives
of basic type may be called primitive constants.
[STLC-Fun]: For a λ-abstraction (λx : S. e) the body e of the function is checked in an

environment extended by x bound with type S.
[STLC-App]: For an application (f e), if the term f in function position has the function

type S → T and is applied to an argument of type S, then the resulting type
of the application is T .

The soundness of this calculus is well-established and we do not present the
proofs here, but introduce the requirement on primitives upon which this system is
parameterized, and state the classical results.

Requirement 2.1 (Types of STLC Primitives)
For each k ∈ Prim, if k is a primitive function then it cannot get stuck and
its behavior is compatible with its type, i.e. if ∅ ⊢stlc k v : T then δ(k, v) is defined and
∅ ⊢stlc δ(k, v) : T .

21

The preservation lemma proved upon Requirement 2.1 ensures that for any
well-typed term e that in any evaluation sequence proceeding
e

e1

e2

···

each ei is well-typed with the same type as e.
Lemma 2.2 (Preservation for STLC) For any term e, if Γ ⊢stlc e : T and e

e′ ,

then Γ ⊢stlc e′ : T .
Since terms that reduce to or from each other are definitionally equivalent, this
indicates that typing is well-defined for the full equivalence class. However, preservation
may be vacuous if no such reduction sequence exists, such as for terms like 3 (4 − +)
(deliberately constructed to be nonsensical). If the type system assigned this term
type Bool, preservation would still hold, so the progress lemma proves that the only
well-typed terms with empty evaluation sequences are those in the known set of values.
Lemma 2.3 (Progress for STLC) For any term e, if ∅ ⊢stlc e : T then either e is a
value or there exists e′ such that e

e′ .

Type soundness is such a simple corrollary of preservation and progress that
it is often not even stated, but since the remaining chapters prove type soundness in
a few different ways, we state the full result to complete the example metatheoretical
development.
Theorem 2.4 (Type Soundness for STLC) For any term e, if ∅ ⊢stlc e : T then
either e is nonterminating or e evaluates to some value v such that ∅ ⊢stlc v : T
22

Chapter 3
Executable Refinement Types

This chapter describes the metatheoretical core of this dissertation: executable
refinement types. This is a type system with the expressivity of dynamic contracts. Basic
built-in types such as Int and Bool may be refined by arbitrary predicates written in the
programming language at hand. These refined base types are combined via dependent
function types. Because these types share their design with higher-order dependent
contracts, they can be enforced at run-time using the same techniques.
Before delving into formal definitions, consider these illustrative examples. The
type of integers greater than zero may be written as
{x : Int | x > 0}
and the type of binary search trees may be written as
{x : Tree | isBinarySearchTree(x)}
where isBinarySearchTree is a hand-coded integrity checking routine. The operation
23

to add an integer x to a set y may be given the type
x : Int → Set → {z : Set | x ∈ z}
Any semi-decidable predicate over base types is expressible, including undecidable predicates. One such undecidable predicate is “integers representing the binary
encoding of a turing machine which halts on empty input” which might appear as
{x : Int | haltsOnEmptyInput(x)}. While such precise types make type checking undecidable, the type system still enjoys the usual notion of type soundness (“well typed
programs don’t go wrong”). Thus, we can use it as a reference point from which to
explore pragmatic techniques for working with expressive types. Rather than building
a full programming language suitable for general use, the type system is presented as
part of a small calculus, in order to focus on the theoretical aspects of the type system.

Chapter Outline
The metatheoretical contributions of this chapter are organized as follows:

- Section 3.1 presents the constructs of the calculus λH .
- Section 3.2 gives formal meaning to the terms of λH via a small-step operational
semantics.
- Section 3.3 formalizes the intuition that refinement types express subsets of types
of the simply-typed lambda calculus, providing a semantic foundation for the
syntactic formalisms of the λH type system.
24

- Section 3.4 presents the type system of λH as a set of inference rules for assigning
types to terms and proving subtyping between λH types.
- Section 3.5 establishes type soundness for λH (any well-typed term is either nonterminating or results in a value of the same type) via the syntactic approach of
proving lemmas for progress (any well-typed term is either a value or can evaluate
further) and preservation (a term’s type remains unchanged by evaluation).
- Section 3.6 establishes that λH terms respect extensionality (functions which map
equal inputs to equal output are observably equivalent) via a new logical relation [Statman 1985]. This provides a powerful technique for proving the observable
equivalence of λH terms.
- Section 3.7 discusses other systems with similar approaches to specifying or reasoning about programs.

3.1

The Language λH
This section defines our calculus λH , an enhancement of STLC with more

precise types to model programming languages with types that can express the same
specifications as dynamic contracts. Figure 3.1 summarizes the syntax of λH for reference. The grammar of terms is unchanged from STLC; it is the type language of λH
that forms the core of this dissertation: Basic types refined by executable predicates
and dependent function types.

25

Figure 3.1: Syntax of λH

e, d, f, g, q, p ::=
k
x
λx : S. e
f e

Terms:
constant
variable
abstraction
application

v ::=

Values: (⊂ Terms)
constant
abstraction

k
λx : S. e
S, T, U ::=
x:S → T
{x : B | p}

Types:
dependent function type
refined basic type

- A dependent function type x : S → T classifies functions of domain S and codomain
T .1 However, x may appear in T , so the type of the result depends on the input.
If it does not occur free in T , yielding the simple function type syntax S → T .
- The basic types B of STLC are fairly coarse and cannot denotes types of interest that are common in contracts, such as integer subranges. To overcome this
limitation, we introduce executable refinement types of the form {x : B | p}. Here,
the variable x (of basic type B) can occur within the boolean term p. Informally, this refinement type denotes the set of primitive constants k of type B that
satisfy the predicate p. Thus, {x : B | p} denotes a refined subtype of B. Occasionally, the predicate p will not contain any free occurrences of x, and we will
1
We use the notation from Cayenne [Augustsson 1998] x : S → T in preference to the equivalent
syntax Πx : S. T for dependent function types. The use of Π indicates that Πx : S.T may be considered
to be the type of S-indexed products of types T [x].

26

write {B | p}. We also write simply B as an abbreviation for the trivial refinement
type {B | true}. These refinement types are inspired by prior work on decidable
refinement type systems discussed in Section 1.1.2. However, our refinement predicates are arbitrary boolean expressions, so every recursively enumerable subset
of the integers is actually a λH type.
To illustrate the precision of this type language, let us adjust the ty function
for λH to give primitives more precise types, such as
true : {b : Bool | b}
false : {b : Bool | not b}
⇔ : b1 : Bool → b2 : Bool → {b : Bool | b ⇔ (b1 ⇔ b2 )}
not : b : Bool → {b′ : Bool | b ⇔ not b}
n : {m : Int | m = n}
+ : n : Int → m : Int → {z : Int | z = n + m}
ifT : Bool → T → T → T
fixT : (T → T ) → T
In particular, we assume that each primitive constant of boolean and integer type is
assigned a singleton type that denotes exactly that constant. For example, the type of
an integer n denotes the singleton set {n}. This reflects the fact that a literal constant
itself, being present in the code, need not be abstracted to any larger type.
The types for primitive functions may also be quite precise: The type
+ : n : Int → m : Int → {z : Int | z = n + m}
27

exactly specifies that this function performs addition. That is, the term n + m has the
type {z : Int | z = n + m} denoting the singleton set {n + m}. Note that even though
the type of “+” is defined in terms of “+” itself, this does not cause any problems in
our technical development, since the semantics of + do not depend on its type.2
The constant fixT is the fixpoint constructor of type T , and enables the definition of recursive functions. For example, the factorial function can be defined as:

fixInt→Int
λf : (Int → Int).
λn : Int.
ifInt (n = 0)
1
(n ∗ (f (n − 1)))

The types of λH can express many other precise specifications, such as the
following (where we assume that Unit, Array, and RefInt are additional basic types,
and the primitive function sorted : Array → Bool identifies sorted arrays.)
- printDigit : {x : Int | 0 ≤ x ∧ x ≤ 9} → Unit.
- swap : x : RefInt → {y : RefInt | x 6= y} → Bool.
- binarySearch : {a : Array | sorted a} → Int → Bool.

3.2

Operational Semantics of λH
We next give formal meaning to λH terms via small-step operational semantics,

as we did for STLC in Section 2.2.2. In fact, redex evaluation for λH is identical to that
2

The definition of a primitive in terms of itself is, however, unsatisfying. In the case of functions, we
may hope that the type is an abstraction of the value. In Chapter 6 we explore some of the consequences
of giving such precise types to functions.

28

Figure 3.2: Operational Semantics of λH

Redex Evaluation

dye

(λx : S. d) e y [x 7→ e] d
k v y δ(k, v)

Contextual Evaluation
C[d]
D[d]

[E-β]
[E-δ]

d
C[e] if d y e
D[e] if d y e

e, S

T

[E-Compat]
[E-TyCompat]

C, D

Contexts
C ::= • | C e | e C | λx : S. C | λx : D. e
D ::= x : D → T | x : S → D | {x : B | C}

of STLC. However, λH types may contain terms, so we extend evaluation to types via
additional contexts in a natural way. Figure 3.2 presents the operational semantics in
full, for easy reference.
Since types contain terms, evaluation is also defined for types. When a term
d reduces to another term e, then the two terms are definitionally equivalent, so the
corresponding types D[d] and D[e] should also be equivalent. This is proven directly
once the appropriate notion of type equivalence is defined. (Lemma 3.2 on page 38)

29

Figure 3.3: Shape of λH types and terms
⌊{x : B | p}⌋

=

B

⌊x : S → T ⌋

def

=

⌊S⌋ → ⌊T ⌋

⌊k⌋

def

k

⌊x⌋
⌊λx : S. e⌋
⌊f e⌋

3.3

def

=

def

=

x

def

λx : ⌊S⌋. ⌊e⌋

=

def

=

⌊f ⌋ ⌊e⌋

Denotation of λH types to sets of ST LC terms
Intuitively, λH types describe subsets of the basic types of ST LC and functions

upon those subsets. To validate this intuition and provide a solid foundation for λH , as
well as demonstrate how to do so for future systems, we “bootstrap” the type system
by using a denotational interpretation into STLC. This corresponds with the intuition –
also supported by the identical redex evaluation rules – that a program with executable
refinement types is essentially an STLC program with a more precise specification.
Underlying every λH type T and term e is an STLC type or term, respectively,
which we call its shape and write ⌊T ⌋ or ⌊e⌋. Figure 3.3 shows the formal definition of
the function ⌊−⌋ : λH → ST LC.3
We give each type T an interpretation [[T ]] (defined by induction on ⌊T ⌋) that
is the set of terms e such that ⌊e⌋ is of type ⌊T ⌋ and e obeys the contractual aspect of
3

This mapping is similar in spirit to erasure which maps a term of the typed λ-calculus to the
corresponding term of the untyped λ-calculus, but “shape” is more concise than “refinement erasure”.

30

Figure 3.4: Denotation from λH types to sets STLC terms

[[{x : B | p}]]

def

=

{e | ⊢stlc ⌊e⌋ : B ∧ (e

[[x : S → T ]]

def

{f | ⊢stlc ⌊f ⌋ : ⌊S⌋ → ⌊T ⌋ ∧ ∀e ∈ [[S]], f e ∈ [[[x 7→ e] T ]]}

=

∗

k implies [x 7→ k] p

∗

true)}

T . Figure 3.4 contains the formal definition.

- A refined basic type {x : B | p} is interpreted as the set of closed terms e of type
B for which the predicate p holds, in the sense that whenever e
constant k, then [x 7→ k] p
requirement that [x 7→ e] p

∗
∗

∗

k for some

true. Note that we cannot insist on the simpler
true because that would forbid assigning types

to divergent terms, but we intend that any type T is populated by at least the
divergent term fixT (λx : T. x). In other terminology, our types specify partial
correctness, not total correctness.4
- A dependent function type x : S → T is interpreted as the set of closed terms of
simple type ⌊S⌋ → ⌊T ⌋ that give output in [[[x 7→ e] T ]] whenever their input e is
in [[S]].

With this denotation defined, we can proceed to define the λH type system.
4

An alternate fomulation used by Xu et al. [2009] also included divergent terms in each type but
only required for convergent e that [x 7→ e] p 6 ∗ false. In other words, the semantics of a predicate
included all terms e for which membership in {x : B | p} cannot be effectively refuted. The treatment of
the case where [x 7→ k] p diverges is subtle and is concerned primarily with blame for dynamic contracts.
See Dimoulas et al. [2011] for a detailed discussion.

31

3.4

The λH Type System
Figure 3.5 and Figure 3.9 present the type system of λH via rules defining

the following collection of judgments, where a typing environment Γ maps variables to
types.

- The typing judgment Γ ⊢ e : T states that, assuming the bindings in environment
Γ, term e has type T . (Figure 3.5)
- The type well-formedness judgment Γ ⊢ T states that T is a well formed type
under the assumptions in environment Γ. (Figure 3.6)
- The environment well-formedness judgment ⊢ Γ states that Γ is a well formed
environment. (Figure 3.7)
- The subtyping judgment Γ ⊢ S <: T states that S is a subtype of T under the
assumptions in environment Γ. (Figure 3.8)
- The implication judgment Γ ⊢ p ⇒ q states that the predicate p implies q under
the assumptions in Γ. (Figure 3.9)
- The closing substitution judgment ⊢ σ : Γ states that the substitution σ is consistent with environment Γ. Defining this appropriately is somewhat subtle and
is the subject of Section 3.4.5. (Figure 3.9)

32

Figure 3.5: Typing Rules for λH

Γ⊢e : T

Type rules
(x : T ) ∈ Γ

[T-Var]

[T-Prim]

Γ⊢x : T
Γ⊢S

Γ, x : S ⊢ e : T

Γ ⊢ k : ty(k)
Γ ⊢ f : (x : S → T )

[T-Fun]

Γ ⊢ (λx : S. e) : (x : S → T )
Γ⊢e : S

Γ⊢e : S

[T-App]

Γ ⊢ f e : [x 7→ e] T
Γ ⊢ S <: T

Γ⊢T

[T-Sub]

Γ⊢e : T

3.4.1

Typing for λH
The inference rules in Figure 3.5 defining the typing judgment
Γ⊢e : T

are analogous to those for STLC with the addition of a subsumption rule ([T-Sub])
that allows subtyping to be applied at any point in a typing derivation. As will always
be the case, we assume that variables are bound at most once in an environment and
implicitly utilize α-renaming of bound variables to maintain this assumption and to
ensure substitutions are capture-avoiding.

[T-Var]: A variable x is assigned the type it is bound to in the environment Γ.

33

[T-Prim]: A constant k is given a type by the function ty, as updated for λH in

Section 3.1.
[T-Fun]: For a λ-abstraction (λx : S. e), if the domain type S is well-formed and the

body e has type T under the additional assumption x : S, then the function is
given the dependent function type x : S → T . Note again that x may appear
free in T .
[T-App]: For an application (f e), if the term f in function position has the dependent

function type x : S → T , then the resulting type of the application is [x 7→ e] T ,
where free occurences of the formal argument x in T have been replaced by the
actual argument e.
[T-Sub]: Whenever a term e has type S and S is a subtype of T the that term also has

type T .

3.4.2

Type well-formedness for λH
In STLC any syntactically valid type is well-formed, but in λH types may

contain terms (predicates) that may themselves be ill-typed. The type well-formedness
judgment
Γ⊢T
ensures that each predicate contained anywhere within a type has type Bool.
[WT-Arrow]: A function type x : S → T is well-formed if the domain type is well-formed

and the range type is well-formed with the added assumption of x : S.
34

Figure 3.6: Type Well-formedness for λH

Γ⊢T

Well-formed types
Γ⊢S

Γ, x : S ⊢ T

Γ, x : B ⊢ p : Bool

[WT-Arrow]

Γ ⊢ x:S → T

[WT-Base]

Γ ⊢ {x : B | p}

[WT-Base]: A refined basic type {x : B | p} is well-formed if the predicate p is a well-

typed boolean term under the additional assumption x : B.

3.4.3

Environment well-formedness for λH
As types may be ill-formed, so an arbitrary environment built from the syn-

tactic definition may contain ill-formed types. The judgment
⊢Γ
ensures that an environment is well-formed by checking that each type is well-formed
using only variables bound prior to that types introduction into the environment. The
typing rules maintain well-formedness of the environment, but when discussing an arbitrary environment it is necessary to be able to distinguish those that make sense from
those that do not.

35

Figure 3.7: Environment Well-formedness for λH

⊢Γ

Well-formed environment
⊢Γ

[WE-Empty]

⊢∅

3.4.4

Γ⊢T

[WE-Ext]

⊢ Γ, x : T

Subtyping for λH
The subtyping judgment
Γ ⊢ S <: T

defines when terms of a type S may be considered to have type T . The judgment has
no analogue in STLC because STLC does not support subtyping but requires types
to match exactly.5 This judgment may be invoked via occurrences of the typing rule
[T-Sub] at any point within a typing derivation.

[S-Arrow]: Subtyping for function types is completely standard, adjusted for dependent

types: A dependent function type x : S1 → S2 is a subtype of x : T1 → T2 if
the T1 <: S1 (contravariant, as always) and the result types are subtypes in a
context with x bound to T1 . Note that T1 must be used in preference to the
coarser argument type S1 because T2 may not be well-formed in an environment
binding x to S1 .
5
Equivalently, one may consider STLC a system with a degenerate subtyping relation that relates
each type only to itself, and thus the subsumption rule is simply elided.

36

Figure 3.8: Subtyping for λH

Γ ⊢ S <: T

Subtyping
Γ ⊢ T1 <: S1

Γ, x : T1 ⊢ S2 <: T2

[S-Arrow]

Γ ⊢ (x : S1 → S2 ) <: (x : T1 → T2 )
Γ, x : B ⊢ p ⇒ q

[S-Base]

Γ ⊢ {x : B | p} <: {x : B | q}

[S-Base]: Since a refined basic type {x : B | p} roughly denotes the set of constants k of

type B for which [x 7→ k] p is valid, subtyping between refinement types reduces
to implication between their refinement predicates. As an example, the rule
[S-Base] states that the subtyping judgment

∅ ⊢ {x : Int | x > 7} <: {x : Int | x > 0}
follows from the validity of the implication:
x : Int ⊢ (x > 7) ⇒ (x > 0)
Basic properties that should hold of any subtyping relationship can now be
stated, though their proofs depend on further formalisms from Section 3.5. The first
property is that subtyping should be reflexive – the narrative reading of the subtyping
judgment Γ ⊢ T <: T is the tautology “any term of type T has type T ”. Secondly
37

subtyping should be transitive, both to respect the intuition that it is an enriched form
of containment and also since two applications of the rule [T-Sub] express transitivity
in another way.
Lemma 3.1 (Subtyping is a Preorder)
1. If Γ ⊢ T then Γ ⊢ T <: T
2. If Γ ⊢ S <: T and Γ ⊢ T <: U then Γ ⊢ S <: U
Proof:
1. By induction on the derivation of Γ ⊢ T , since implication is a preorder as well
(Lemma 3.3 on page 41).
2. By induction on the derivation of Γ ⊢ S <: T (followed by inversion on Γ ⊢
T <: U ) using Lemma 3.9 for the bindings in function types, and the transitivity
of implication (Lemma 3.3 on page 41). 
This establishes that subtyping for λH obeys the expected properties of any
subtyping system. But since terms appear within λH types, the more novel condition
of type invariant under reduction is particular to dependent type systems.
Lemma 3.2 (Type Equivalence under Reduction) If Γ ⊢ S and S

T then Γ ⊢

S <: T and Γ ⊢ T <: S.
Proof: By induction on the derivation of S. In the base case of [WT-Base] invoking
the definition of implication. 
38

Figure 3.9: Implication for λH

Γ⊢p⇒q

Implication
∀σ. if ⊢ σ : Γ and σ(p)

∗

true then σ(q)

∗

true

Γ⊢p⇒q

[Imp]

⊢σ : Γ

Closing Substitution
∀(x : T ) ∈ Γ, σ(x) ∈ [[σ(T )]]
⊢σ : Γ

[Subst]

Just as confluence of reduction ensures that reduction produces well-defined
equivalence classes of terms, type equivalence under reduction ensures the same for types
and that these equivalence classes correspond to the equivalence closure of subtyping.

3.4.5

Implication and Closing Substitutions
Subtyping for λH is determined by the implication judgment
Γ⊢p⇒q

defined by the single rule [Imp] which informally reads “for all closing substitutions
consistent with the types in the environment, if the now-closed predicate p holds, then
so must q”. However, defining closing substitutions in such a way as to yield a consistent
implication relation is new to this research, and merits discussion.

39

One approach taken by Flanagan [2006]6 is to define closing substitutions in
terms of the typing judgment, as follows:
∀x ∈ dom(Γ), ⊢ σ(x) : Γ(x)
⊢σ : Γ
This approach leads to mutual recursion between the typing, subtyping, implication, and closing substitution judgments. Unfortunately, the implication rule [Imp]
from Figure 3.5 refers to the closing substitution relation in a contravariant position, so
standard monotonicity arguments are not sufficient to show that the resulting collection
of mutually recursive inference rules converge to a fixed point, and it is not obvious if
there are interesting type systems that satisfy these rules.
An alternative approach to defining implication is to axiomatize its logic [Denney
1998; Ou et al. 2004; Gronski et al. 2006], but the underlying problem remains, in that it
is still not obvious if there are interesting implication relations, and hence type systems,
that satisfy the axioms.
A third approach, taken by Belo et al. [2011], is to restrict refinements by
insisting they be well-typed predicates in an empty environment, so that closing substitutions are not necessary. Unfortunately, refinements then cannot express even simple
facts such as that the integer stored in y is greater than the integer stored in x!
Instead, in rule [Subst] we have leveraged the denotational interpretation of λH
types from Section 3.3 to define closing substitutions in a direct manner with no circularity problems: A substitution σ : V ar → T erm is a closing substition for environment
6

Corrected by Knowles and Flanagan [2010] to use the technique described here.

40

Γ if for all bindings (x : T ) ∈ Γ we have σ(x) ∈ [[σ(T )]].
Lemma 3.3 (Implication is a Preorder)

1. For any Γ and p we have Γ ⊢ p ⇒ p.
2. If Γ ⊢ p1 ⇒ p2 and Γ ⊢ p2 <: p3 then Γ ⊢ p1 <: p2
Proof: Both of these statements reduce to tautologies by inverting [Imp].

3.5

Type Soundness for λH
We prove soundness via the usual syntactic “progress” and “preservation”

lemmas. However, due to the semantic nature of implication, many of the steps along
the way are new, thus routine lemmas usually omitted from a formal development
(because practitioners have seen them so many times) are less obvious and are included
here. Throughout this section, environments are assumed to be well formed, to elide
many uninteresting antecedents.
As a preliminary measure, let us formally state additional expectations on the
relationship between a primitives’s operational semantics and the type assigned to it,
analogous to Requirement 2.1 for STLC primitives.

Requirement 3.4 (Types of λH Primitives)
For each primitive k:
1. The type of k is closed and well-formed, i.e. ∅ ⊢ ty(k).
41

2. If k is a primitive function then it cannot get stuck and its behavior is compatible
with its type, i.e. if ∅ ⊢ k v : T then δ(k, v) is defined and ∅ ⊢ δ(k, v) : T
3. If k is a primitive constant then it is a member of its type, which is a singleton type,
i.e.if ty(k) = {x : B | p} then [x 7→ k] p

∗

true and ∀k′ 6= k. [x 7→ k′ ] p 6

∗

true.

Because we define closing substitutions semantically, many of our proofs connect the semantic relation with our syntactic type system. First, we define the semantic
subtyping relation Γ ⊢ S ⊆ T and the semantic typing relation Γ ⊢ e ∈ T induced by
our semantic notion of types, following Frisch et al. [2008]:
Γ⊢e∈T

def

=

∀σ, ⊢ σ : Γ implies σ(e) ∈ [[σ(T )]]

Γ⊢S⊆T

def

∀σ, ⊢ σ : Γ implies [[σ(S)]] ⊆ [[σ(T )]]

=

Our formal subtype system is sound with respect to this model:
Lemma 3.5 (Semantic typing soundness)
1. If Γ ⊢ S <: T then Γ ⊢ S ⊆ T
2. If Γ ⊢ e : T then Γ ⊢ e ∈ T
Proof:
1. Proceed by induction on the derivation of Γ ⊢ S <: T , considering the final rule
applied. If S and T are refined basic types, then semantic and formal subtyping
have identical definitions. If S and T are function types, then the result follows
by induction.
42

2. Proceed by induction on the derivation of Γ ⊢ e : T considering the final rule
applied. If the final rule is [T-Sub] then part 1 maps subtyping to semantic
sutyping. 
In fact, semantic soundness is essentially a form of type soundness that leverages soundness of STLC: Because of type soundness of STLC, a well-typed type is either
nonterminating or terminates in a value of the proper shape, and the additional semantic
conditions on the denotational definition ensure that the value satisfies its refinements.
However, to illustrate other desirable properties of the syntactic typing discipline, we
proceed with syntactic proofs.
The semantic substitution properties for these relations mirror the usual syntactic properties, but have a completely different basis. Note, for example, the contravariance of the substitution lemma for closing substitutions. In the following proofs,
we use juxtoposition of substitutions to indicate composition or application in order to
reduce the number of extraneous symbols; no ambiguity arises.
Lemma 3.6 (Semantic Substitution)
Suppose Γ ⊢ d ∈ S. Let θ = [x 7→ d], and σΓ represent σ limited to the domain of Γ.
1. If ⊢ σ : Γ, θΓ′ then ⊢ σΓ θσΓ′ : Γ, x : S, Γ′
2. If Γ, x : S, Γ′ ⊢ S ⊆ T then Γ, θΓ′ ⊢ θS ⊆ θT
3. If Γ, x : S, Γ′ ⊢ e ∈ T then Γ, θΓ′ ⊢ θe ∈ θT
Proof:
43

1. By definition of Γ ⊢ d ∈ S we know σΓ (d) ∈ [[σΓ (S)]]
2 and 3. For each lemma, fix a closing substitution σ such that ⊢ σ : Γ, θΓ′ . Then
⊢ σΓ θσΓ′ : Γ, x : S, Γ′ by part 1. Since σS = σΓ θσΓ′ S and σT = σΓ θσΓ′ T and
σe = σΓ θσΓ′ e each conclusion follows by set-theoretic calculation.
Corollary 3.7 All the conclusions of Lemma 3.6 hold if Γ ⊢ d : S.
A key notion of health for any inference system is that additional assumptions
should never lessen the conclusions that may be drawn. The act of adding additional
assumptions to the environment is known as weakening.
Lemma 3.8 (Weakening)
Let Γ = Γ1 , Γ2 and Γ′ = Γ1 , x : U, Γ2 .
1. If Γ ⊢ p : Bool and Γ ⊢ q : Bool and Γ ⊢ p ⇒ q then Γ′ ⊢ p ⇒ q
2. If Γ ⊢ T and Γ ⊢ S and Γ ⊢ S <: T then Γ′ ⊢ S <: T .
3. If Γ ⊢ e : T then Γ′ ⊢ e : T .
4. If Γ ⊢ T then Γ′ ⊢ T .
Proof:
1. The quantification over closed terms after weakening is the same as the
quantification before weakening, as the new binding is irrelevant.
2. By induction on the derivation of Γ ⊢ S <: T , using part 1 in the case for
rule [S-Base].
44

3 and 4. By mutual induction on the derivations of Γ ⊢ e : T and Γ ⊢ T , using
part 2 in the case of rule [T-Sub] 

Likewise, shrinking the domain of a quantification should only increase the
available conclusions, as there are fewer potential counterexamples to any conclusion.
For a subtyping system, this operation of narrowing indicates that subtyping behaves
according to this intuition.

Lemma 3.9 (Narrowing) Suppose Γ ⊢ S <: T . Then
1. If Γ, x : T, Γ′ ⊢ p ⇒ q then Γ, x : S, Γ′ ⊢ p ⇒ q.
2. If Γ, x : T, Γ′ ⊢ U1 <: U2 then Γ, x : S, Γ′ ⊢ U1 <: U2 .
3. If Γ, x : T, Γ′ ⊢ e : U then Γ, x : S, Γ′ ⊢ e : U .
Proof:
1. By Lemma 3.5 (Soundness of Semantic Typing) we know Γ ⊢ S ⊆ T so the quantification over closed terms in the conclusion is contained in that of the premise. 
2. By induction over the derivation of Γ, x : T, Γ′ ⊢ U1 <: U1 using part 1.
3. By induction over the derivation of Γ, x : T, Γ′ ⊢ e : U , using part 2 as needed and
applying subsumption immediately when x is pulled from the environment. 
Via Lemma 3.8 and Lemma 3.9 the reflexivity and transitivity of subtyping may be established. The reader interested in formalism may now wish to revisit
Section 3.4.4 and review the these properties and their proofs.

45

The central lemma for proving preservation is the so-called substitution lemma,
which states that a variable of a certain type may be soundly replaced by any term of
the same type. Each of the judgments of the λH enjoys a substitution property.
Lemma 3.10 (Substitution) Suppose Γ ⊢ e : S,
1. If Γ, x : S, Γ′ ⊢ p ⇒ q then Γ, [x 7→ e] Γ′ ⊢ [x 7→ e] p ⇒ [x 7→ e] q
2. If Γ, x : S, Γ′ ⊢ T <: U then Γ, [x 7→ e] Γ′ ⊢ [x 7→ e] T <: [x 7→ e] U
3. If Γ, x : S, Γ′ ⊢ d : T then Γ, [x 7→ e] Γ′ ⊢ [x 7→ e] d : [x 7→ e] T
4. If Γ, x : S, Γ′ ⊢ T then Γ, [x 7→ e] Γ′ ⊢ [x 7→ e] T
Proof:

1. Immediately follows from Lemma 3.6 (Semantic Substitution).
2. By induction on the derivation of Γ, x : S, Γ′ ⊢ T <: U . In the case of [S-Base]
invoking part 1.
3. and 4. By mutual induction on the derivations of Γ, x : S, Γ′ ⊢ d : T and
Γ, x : S, Γ′ ⊢ T , invoking part 2 in the case of [T-Sub].
Leveraging these lemmas, progress and preservation are routine and ensure
type soundness for λH in the usual way.
Lemma 3.11 (Preservation) If ⊢ Γ and Γ ⊢ d : T and d

46

e then Γ ⊢ e : T

Proof: By induction on the typing derivation Γ ⊢ d : T , invoking Lemma 3.10 when
evaluation proceeds via [E-β] and Requirement 3.4 when evaluation proceeds via [E-δ]. 
Lemma 3.12 (Progress) If ∅ ⊢ e : T then either e is a value, or there exists some e′
such that e

e′ .

Proof: Proceed by induction on the derivation of ⊢ e : T , considering the final rule
applied. The entire proof is standard and routine 
Theorem 3.13 (Type Soundness for λH ) For any term e, if ∅ ⊢ e : T then either
e is nonterminating or e evaluates to some value v such that ∅ ⊢ v : T
Proof: This is a corollary of progress and preservation.

3.6

Extensional Equivalence for λH
Type soundness assures that the λH type system relates to the semantics in the

intended manner, but is not particularly useful for reasoning about meaning of terms.
The most fundamental relationship between terms is that of equality, and the defining
trait of equality is substitutability: If a term e is equal to d, then replacing e with d
never affects any conclusion. Relative to the semantics of a calculus, the authoritative definition of equivalence between terms is contextual equivalence (sometimes called
observable equivalence) which states that no program context can distinguish the two
terms.
Definition 3.14 (Contextual Equivalence)
47

Two terms e1 and e2 are contextually equivalent, written e1 ≡ e2 , if and only if:
Given any context C such that ∅ ⊢ C[e1 ] : B and ∅ ⊢ C[e2 ] : B,
C[e1 ]

∗

k ⇔ C[e2 ]

∗

k

Reasoning about contextual equivalence between terms is notoriously difficult.
The equivalence closure of reduction is much weaker than contextual equivalence, so
establishing equality between terms that are not obviously syntactically related often
involves complex syntactic techniques such as bisimulation (of which many varities exist)
which are extremely sensitive to the form (not content) of a calculus.
In contrast, when reasoning about mathematical functions, extensional equivalence – that equal functions map equal arguments to equal results – is the definition
of equality. For a computational calculus, this sort of equality is known as a logical relation [Statman 1985]. That such a form of reasoning is applicable confirms that what
we call a “function” in a calculus does, indeed, define a function relative to contextual
equivalence.
The logical relation we establish is extensional equivalence of terms of a particular type under reduction, written Γ ⊢ e1 ∼ e2 : T and defined in Figure 3.10. Two
terms e1 and e2 of basic type are (extensionally) equivalent if, for any closing substitution σ, whenever σ(e1 ) evaluates to a constant k so does σ(e2 ), and vice versa. Two
functions f1 and f2 of type x : S → T are equivalent if they yield equivalent output
when given equivalent arguments e1 and e2 .

48

Figure 3.10: Extensional Equivalence Under Reduction for λH

Γ ⊢ e1 ∼ e2 : T

(defined by induction on ⌊T ⌋)

Γ ⊢ e1 ∼ e2 : {x : B | p}

def

⇔ Γ ⊢ e1 ∈ {x : B | p}
Γ ⊢ e2 ∈ {x : B | p}
∀σ such that ⊢ σ : Γ, (σ(e1 )

Γ ⊢ f1 ∼ f2 : (x : S → T )

∗

k) ⇔ (σ(e2 )

def

⇔ ∀e1 , e2 such that Γ ⊢ e1 ∼ e2 : S,
Γ ⊢ f1 e1 ∼ f2 e2 : [x 7→ e1 ] T f [x 7→ e2 ] T

SfT

{x : B | p} f {x : B | q}

def

=

{x : B | p ∧ q}

(x : S1 → S2 ) f (x : T1 → T2 )

def

x : (S1 f T1 ) → (S2 f T2 )

=

49

∗

k)

A question that immediately arises is whether these two applications f1 e1 and
f2 e2 should have type [x 7→ e1 ] T or [x 7→ e2 ] T . In some sense it does not matter, since
e1 and e2 are extensionally equivalent by assumption. But this assumes the premise:
that λH operational semantics respects extensional equivalence. So, since this is not yet
demonstrated, the definition requires that f1 e1 and f2 e2 are extensionally equivalent
at type
[x 7→ e1 ] T f [x 7→ e2 ] T
where we combine both types via the wedge product (f), also defined in Figure 3.10.
For basic types, wedge product is type intersection, i.e., conjunction of refinement predicates. For function types, the wedge product is covariant in both the function domain
and the range. This covariance is an illusion: we we only ever combine equivalent types
with the wedge product, so co- or contravariance is a formality: If the wedge product
were made more “intuitively” contravariant the [T-App] case of Theorem 3.19 would
not be possible to prove as stated. Since extensional equivalence is uncommon in type
systems research exposition, we present the following proofs in great detail.
To work fluidly with this wedge product, we note that it is a commutative
and associative operator with respect to the equivalence closure of subtyping, and it
distributes through subtyping.

Lemma 3.15 Assuming the underlying shapes of all mentioned types are equal, and
each type is well-formed in the environment,
1. Γ ⊢ S f T <: T f S
50

2. Γ ⊢ S f (T f U ) <: (S f T ) f U
3. If Γ ⊢ S1 <: T1 and Γ ⊢ S2 <: T2 then Γ ⊢ S1 f S2 <: T1 f T2
Proof: By induction on the underlying shape of the types. 
We require that primitive functions do not violate extensionality, and so each
primitive function k is extensionally equivalent to itself.
Requirement 3.16
For any primitive k,
∅ ⊢ k ∼ k : ty(k)
Equivalence is preserved under subtyping, allowing free manipulation of f in
the type assigned to an equivalence.
Lemma 3.17 (Extensional equivalence under subtyping)
If Γ ⊢ e1 ∼ e2 : S and Γ ⊢ S <: T then Γ ⊢ e1 ∼ e2 : T
Proof: By induction on ⌊S⌋ (which equals ⌊T ⌋), with proof cases elaborated by inversion of subtyping rules.
Case S = {x : B | p} : Then we have a derivation
Γ⊢p⇒q

[S-Base]

Γ ⊢ {x : B | p} <: {x : B | q}
By definition of Γ ⊢ e1 ∼ e2 : {x : B | p}, both e1 and e2 are semantically
members of {x : B | p} hence also semantic members of {x : B | q}, which yields
Γ ⊢ e1 ∼ e2 : {x : B | q}.
51

Case S = x : S1 → S2 : Then we have a derivation
Γ ⊢ T1 <: S1

Γ, x : T1 ⊢ S2 <: T2

[S-Arrow]

Γ ⊢ x : S1 → S2 <: x : T1 → T2
Fix d1 and d2 such that Γ ⊢ d1 ∼ d2 : T1 . By induction, Γ ⊢ d1 ∼ d2 : S1 .
By definition of e1 and e2 being extensionally equivalent,
Γ ⊢ e1 d1 ∼ e2 d2 : [x 7→ d1 ] S2 f [x 7→ d2 ] S2
By Lemma 3.10 (Substitution),
Γ ⊢ [x 7→ d1 ] S2 <: [x 7→ d1 ] T2

and Γ ⊢ [x 7→ d2 ] S2 <: [x 7→ d2 ] T2

By Lemma 3.15(3) the above two subtyping relationship combine with the
wedge product to yield
Γ ⊢ [x 7→ d1 ] S2 f [x 7→ d2 ] S2 <: [x 7→ d1 ] T2 f [x 7→ d2 ] T2
By applying induction to the above unfolding of the definition of extensional
equivalence,
Γ ⊢ e1 d1 ∼ e2 d2 : [x 7→ d1 ] T2 f [x 7→ d2 ] T2
Hence Γ ⊢ e1 ∼ e2 : (x : T1 → T2 ). 
Next, we show that extensional equivalence of reducts implies extensional
equivalence of the original terms.

Lemma 3.18 (Extensional equivalence under reduction)
If Γ ⊢ e′1 ∼ e′2 : U and e1

∗

e′1 and e2

Proof: By induction on ⌊U ⌋.
52

∗

e′2 then Γ ⊢ e1 ∼ e2 : U

Case {x : B | p}: If e′1

∗

∗

k then e1

e′1

∗

k; likewise for e2 and e′2 .

Case x : S → T : Fix d1 , d2 such that
Γ ⊢ d1 ∼ d2 : S.
By definition
Γ ⊢ e′1 d1 ∼ e′2 d2 : [x 7→ d1 ] T f [x 7→ d2 ] T
By applying an evaluation context,
e1 d1

∗

e′1 d1

and e2 d2

∗

e′2 d2

By induction,
Γ ⊢ e1 d1 ∼ e2 d2 : [x 7→ d1 ] T f [x 7→ d2 ] T
Hence, Γ ⊢ e1 ∼ e2 : (x : S → T ). 
To prove the fundamental soundness theorem for extensional equivalence, we
extend the notion of extensional equivalence to closing substitutions.
⊢σ ∼ γ:Γ

def

=

∀x ∈ dom(Γ), ∅ ⊢ σ(x) ∼ γ(x) : (σ f γ)(Γ(x))

For convenience, we overload the wedge product operator for closing substitutions, so
(σ f γ) maps a type T to the wedge product of all possible choices of which variables to
use from σ and which from γ. Note that we are only interested in closing substitutions
for the same environment, so they always have the same domain.

(σ f γ)(T )

def

=

k
A∈P(dom(σ))

53

(σ|A ◦ γ|dom(σ)\A )(T )

Lemma 3.19 (Extensional equivalence under substitution)
If Γ ⊢ e : T then for any closing substitutions σ and γ such that ⊢ σ ∼ γ : Γ,
we have
∅ ⊢ σ(e) ∼ γ(e) : (σ f γ)(T )
Proof: By induction on the height of the derivation of Γ ⊢ e : T , rebinding metavariables for each case.

Case [T-Var]: Then (x : T ) ∈ Γ, so by definition of equivalent substitutions ∅ ⊢
σ(x) ∼ γ(x) : (σ f γ)(T )
Case [T-Prim]: Given by Requirement 3.16.
Case [T-App]: Then the derivation concludes with
Γ ⊢ f : x:S → T

Γ⊢e : S

Γ ⊢ f e : [x 7→ e] T
Fix σ and γ. By induction,
∅ ⊢ σ(f ) ∼ γ(f ) : (σ f γ)(x : S → T )
∅ ⊢ σ(e) ∼ γ(e) : (σ f γ)(S)
Applying the definition of extensional equivalence,
∅ ⊢ σ(f ) σ(e) ∼ γ(f ) γ(e)

:

[x 7→ σ(e)] (σ f γ)(T )

f [x 7→ γ(e)] (σ f γ)(T )
which simplifies to the conclusion for this case:
∅ ⊢ σ(f e) ∼ γ(f e) : (σ f γ)([x 7→ e] T )

54

Case [T-Lam]: The the derivation concludes with
Γ, x : S ⊢ e : T
Γ ⊢ λx : S. e : x : S → T
Fix σ and γ, then fix d1 and d2 such that ∅ ⊢ d1 ∼ d2 : (σ f γ)(S). The
definition of extensional equivalence of substitution yields
⊢ (σ ◦ [x 7→ d1 ]) ∼ (γ ◦ [x 7→ d2 ]) : (Γ, x : S)
By induction,
∅ ⊢ (σ ◦ [x 7→ d1 ]) e ∼ (γ ◦ [x 7→ d2 ]) e : ((σ ◦ [x 7→ d1 ]) f (γ ◦ [x 7→ d2 ]))(T )
By Lemma 3.18, we perform β-expansion and rearrange some substitutions to
yield
∅ ⊢ σ(λx : S. e) d1 ∼ γ(λx : S. e) d2

:

[x 7→ d1 ] (σ f γ)(T )

f [x 7→ d2 ] (σ f γ)(T )
which simplifies to the conclusion of this case,
∅ ⊢ σ(λx : S. e) ∼ γ(λx : S. e) : (σ f γ)(x : S → T )
Case [T-Sub]: The derivation concludes with
Γ⊢e : S

Γ ⊢ S <: T

Γ⊢e : T
Fix σ and γ. By induction,
∅ ⊢ σ(e) ∼ γ(e) : (σ f γ)(S)
Applying the substitution lemma and distributing f through the subtyping,
∅ ⊢ (σ f γ)(S) <: (σ f γ)(T )

55

Thus by Lemma 3.17 we conclude with
∅ ⊢ σ(e) ∼ γ(e) : (σ f γ)(T )
This concludes the case-by-case analysis. 
As a corollary, we have contextual equivalence of related terms.
Theorem 3.20 (Soundness of extensional equivalence) If Γ ⊢ d ∼ e : T then
d ≡ e.
Proof:

From the definition of d ≡ e, fix any C such that ∅ ⊢ C[d] : B and

∅ ⊢ C[e] : B. Apply Lemma 3.19 to the typing x : T ⊢ C[x] : B with the related
substitutions ⊢ [x 7→ d] ∼ [x 7→ e] : (x : T ) to yield ∅ ⊢ C[d] ∼ C[e] : B, which is exactly
the definition of d ≡ e.

3.7

Related Work
Research on refinement type systems discussed in Section 1.1.2 has influenced

our choice of how to express program invariants, with the major difference that none of
the referenced systems include arbitrary executable refinement predicates.
Liquid types [Rondon et al. 2008] also refines basic types with predicates, but
the research agenda is more aligned with the question “how can we develop usable
and effective static verification of modern higher-order programs?” rather than ours
of “how shall we proceed into the realm of undecidable type systems?”. Thus the
metatheory of Liquid Types is a model of an implementation for OCaml (subsequently
for C [Rondon et al. 2012], Javascript [Chugh et al. 2012a], and Haskell [Vazou et al.
56

2013]) rather than a foundational calculus, resulting in different choices for formalization: The meaning of validity is given via a direct mapping to the The refinement predicates of Liquid Types, while written in the programming language at hand, are given
meaning by a mapping to the logic of Satisfiability Modulo Theories (SMT). Evaluation
is that of the host language (call-by-value for most implementations, and call-by-need
for Haskell) rather than a general theory of β-equivalence, thus proof techniques for
program equivalence are largely out of scope.
ESC/Haskell [Xu 2006; Xu et al. 2009] features a similar language for expressing specifications, but is concerned with the practicalities of statically checking contracts
for Haskell (hence the name analogous to its predecessor ESC/Java) and takes a different formal approach not primarily concerned with providing a foundational calculus. A
program meeting its specification is defined by symbolic execution, versus our presentation as a type system later demonstrated sound by relating it to the semantics of a
program. However, ESC/Haskell gives a much more thorough treatment to the notion
of blame, which is integral to a complete contract system.
Manifest contracts [Greenberg et al. 2012] is a term synonymous with executable refinement types, chosen to emphasize that predicates from contracts are
made “manifest” in the type system, versus being only “latent” properties of the program as it executes. The work shares with this chapter a foundation built upon from
Knowles and Flanagan [2010], hence uses a nearly identical approach to defining subtyping between contracts. Subsequent to this research, Belo et al. [2011] extended manifest
to include polymorphism and apply refinements to any type, not just basic types. How57

ever as a foundation it is somewhat lacking due to the restriction that refinements may
only refer to the value being refined and may not have other free variables to relate
different portions of a program. It also does not include subtyping, but simply ensures
types are compatible and then defers contract enforcement to runtime, much like gradual typing. Subtyping between function types is the principle remaining challenge for
allowing refinements of arbitrary types.
None of the above systems provide a robust method for reasoning about extensional equivalence of terms.

58

Chapter 4
Hybrid Type Checking

With λH as a sound foundation for the study of executable refinement type
systems, this chapter presents hybrid type checking, a new technique for combining static
type checking with dynamic contract checking.
To illustrate the key idea of hybrid type checking in general, before developing a
system specialized to λH , consider again this possible type rule for function application:
Γ⊢f : T →U

Γ⊢e : S

Γ ⊢ S <: T

Γ⊢f e : U
The situation we focus on is when the antecedent Γ ⊢ S <: T can be neither
definitively proven nor refuted.

- Statically rejecting such programs would cause the compiler to reject some programs that, on deeper analysis, could be shown to be well-typed. Flanagan et al.
[2002] illustrates that this approach is brittle in practice because there are many

59

such programs and it is difficult to predict which programs the compiler would
accept.
- Statically accepting such programs (based on the optimistic assumption that the
unproven subtype relations actually hold) may result in specifications being violated at run time.

Instead, hybrid type checking is the general approach of accepting such programs on
a provisional basis, but inserting sufficient dynamic checks to ensure that specification
violations never occur at run time. Of course, checking that Γ ⊢ S <: T at run time
is still a difficult problem and would violate the principle of phase distinction [Cardelli
1988]. Instead, our hybrid type checking approach transforms the above application
into the code
f (hT ⊳ Si e)
where the additional cast (or coercion [Breazu-Tannen et al. 1991]) hT ⊳ Si dynamically
enforces that e adheres to T .
Figure 4.1 illustrates the behavior of hybrid type checking on various kinds of
programs. Every program is either ill-typed or well-typed, but it is not always possible
to make this classification statically. However, a particular algorithm may still identify
some (hopefully many) clearly ill-typed programs, which are rejected, and similarly can
identify some clearly well-typed programs, which are accepted unchanged.
For the remaining programs, dynamic type casts are inserted to check any
unverified correctness properties at run time. We refer to this category of programs as
60

Figure 4.1: Behavior of hybrid type checking for various categories of programs

Ill-typed programs
Clearly ill-typed
Rejected by type checker

Well-typed programs
Subtle programs
Clearly well-typed
Accepted
Accepted without casts
with casts
Casts
Casts
may
never
fail
fail

subtle.

Definition 4.1 A program is called subtle relative to a hybrid type checker if the
checker cannot statically prove or refute its well-typedness.

If the original program is actually well-typed, these casts are redundant and
will never fail. Conversely, if the original program is ill-typed in a manner that is not
demonstrable at compile time, the inserted casts may fail. As static analysis technology
improves, the category of subtle programs in Figure 4.1 will shrink, as more ill-typed
programs are rejected and more well-typed programs are fully verified at compile time.
Hybrid type checking may facilitate the evolution and adoption of advanced
static analyses, by allowing software engineers to experiment with sophisticated specification strategies that cannot (yet) be verified statically or efficiently. Such experiments
can then motivate and direct static analysis research.

61

For example, if a hybrid type checker fails to verify or refute a subtyping query,
it could send that query back to the compiler writer. Similarly, if a cast hT ⊳ Si v fails,
the value v is a witness that refutes an undecided subtyping query S <: T , and such
witnesses could also be sent back to the compiler writer. This information would provide
concrete and quantifiable motivation for subsequent improvements in the type checker’s
analysis. In Chapter 7 we explore the design of such an implementation.
Just as different compilers (or different configurations of a single compiler) for
the same language may yield object code of varying quality, we might imagine a variety
of hybrid type checkers with different trade-offs between static and dynamic checks.
Fast interactive hybrid compilers might perform only limited static analysis to detect
obvious type errors, while production compilers could perform deeper analyses to detect
more defects statically and to generate improved code with fewer dynamic checks.
Hybrid type checking is inspired by prior work on soft typing [Fagan 1991;
Wright and Cartwright 1994; Aiken et al. 1994; Flanagan et al. 1996], but it extends
soft typing by rejecting many ill-typed programs, in the spirit of static type checkers.
The interaction between static typing and dynamic checks has also been studied in the
context of type systems with the type Dynamic [Thatte 1990; Abadi et al. 1991], and in
systems that combine dynamic checks with dependent types [Ou et al. 2004]. Hybrid
type checking extends these ideas to support more precise specifications.
The general approach of hybrid type checking appears applicable to a variety of
programming languages and specification languages. This chapter develops the design
and correctness criteria for such a system using λH as a representative core calculus.
62

The combination of λH and HTC enjoys the following benefits:
1. It supports precise interface specifications, which facilitate modular development
of reliable software.
2. As many defects as possible and practical are detected at compile time (and we
expect this set will increase as static analysis technology evolves).
3. All well-typed programs are accepted by the checker, and their semantics are
unchanged.
4. Due to decidability limitations, the hybrid type checker may statically accept some
subtly ill-typed programs, but it will insert sufficient dynamic casts to guarantee
that specification violations are always detected, either statically or dynamically.
5. The output of the hybrid type checker is always a well-typed program (and so, for
example, type-directed optimizations are applicable).
6. If the source program is well-typed, then the inserted casts are guaranteed to
succeed, and so the source and output programs are behaviorally equivalent.
7. If the subtyping algorithm can decide all reflexive queries, then cast insertion is
idempotent: All necessary casts are inserted in a single pass.

Chapter Outline
The remainder of this chapter is organized as follows
- Section 4.1 introduces the extensions to λH to support hybrid type checking.
63

- Section 4.2 describes the operational semantics of dynamic casts.
- Section 4.3 presents the new typing rules for casts.
- Section 4.4 formally defines hybrid type checking.
- Section 4.5 walks through a sizable example in depth.
- Section 4.4.4 introduces the correctness properties of HTC beyond those of traditional type systems, and proves them for our context.
- Section 4.7 surveys work most closely related to HTC.

4.1

The Language λHTC
H
In order to perform hybrid type checking augment λH with terms for casts,

yielding λHTC
H . The extension includes two new forms, one for casts and one for refinement checks in progress:
e ::= · · · | hT ⊳ Si | h{x : B | p}, q, ki
For easy reference, Figure 4.2 includes the full syntax of the augmented language with
new forms highlighted.
- The term hT ⊳ Si denotes a cast from S to T as described in the introduction to
this chapter.
- The term h{x : B | p}, q, ki denotes a cast-in-progress where a determination of
whether k has type {x : B | p} is underway. If q eventually evaluates to true, then
64

Figure 4.2: Syntax for λHTC
H

e, d, f, g, q, p ::=
k
x
λx : S. e
f e
hT ⊳ Si
h{x : B | p}, q, ki

Terms:
constant
variable
abstraction
application
type cast
cast in progress

v ::=

Values: (⊂ Terms)
constant
abstraction
type cast

k
λx : S. e
hT ⊳ Si
S, T, U ::=
x:S → T
{x : B | p}

Types:
dependent function type
refined basic type

65

Figure 4.3: Operation Semantics of λHTC
H

Redex Evaluation

dye

(λx : S. e) d y [x 7→ d] e
k v y δ(k, v)

hx : T1 → T2 ⊳ x : S1 → S2 i f

[E-β]
[E-δ]

y

[E-Cast-F]

λx : T1 . hT2 ⊳ [x 7→ hS1 ⊳ T1 i x] S2 i (f (hS1 ⊳ T1 i x))
h{x : B | p} ⊳ {x : B | q}i k y h{x : B | p}, [x 7→ k] p, ki
h{x : B | q}, true, ki y k

[E-Cast-End]

C, D

Contexts

C

[E-Cast-Begin]

::= • | C e | e C | λx : S. C | λx : D. e | hT, C, ki | hD, e, ki | hT ⊳ Di | hD ⊳ Si

D ::= x : D → T | x : S → D | {x : B | C}

the cast will succeed.

4.2

Operational Semantics of λHTC
H
Figure 4.3 recapitulates the redex reduction rules and evaluation contexts for

λHTC
with additions to include casts used in hybrid type checking. The new evaluation
H
rules governing the meaning of casts are:
66

[E-Cast-F] Casting a function f of type x : S1 → S2 to the type x : T1 → T2 yields a

new function
λx : T1 . hT2 ⊳ [x 7→ hS1 ⊳ T1 i x] S2 i (f (hS1 ⊳ T1 i x))
Though large, this term of type x : T1 → T2 is actually simple: Consider
applying it to an argument e of type T1 , and β-reduce to the following:
h[x 7→ e] T2 ⊳ [x 7→ hS1 ⊳ T1 i e] S2 i (f (hS1 ⊳ T1 i e))
First the argument e is cast to type S1 , then it is passed to the original function f , yielding a result of type [x 7→ hS1 ⊳ T1 i e] S2 ; call it e′ . The cast inserted
into the return type is necessary as e may not actually have type S1 , so substituting it directly would make the resulting type ill-formed and could cause
actual errors not caught by run-time checks. Then the result e′ is cast to type
[x 7→ e] T2 as required. Thus, higher-order casts are performed a lazy fashion
– the new casts hT2 ⊳ S2 i and hT2 ⊳ T1 i are performed at every application of
the resulting function, in a manner reminiscent higher-order coercions [Thatte
1990] or higher-order contracts [Findler and Felleisen 2002].1
[E-Cast-Begin] and [E-Cast-End] A basic constant k is cast to a base refinement type

{x : B | p} via multiple evaluation steps. Via rule [E-Cast-Begin] a cast application h{x : B | p} ⊳ {x : B | q}i k evaluates to a cast-in-progress h{x : B | p}, [x 7→ k] p, ki.
The instantiated predicate [x 7→ k] t then evaluates via the closure rule [E-Ctx],
1

We ignore the issue of blame assignment in the event of a run-time cast failure – see
[Gronski and Flanagan 2007] for a detailed analysis.

67

either diverging or terminating in a value v. If v is true then the cast-inprogress evaluates to k, as it has been dynamically verified that k has type
{x : B | p}, otherwise the cast-in-progress is “stuck” and we call it a failed cast.
Definition 4.2 (Failed Cast) A failed cast is a term of the form h{x : B | p}, q, ki
where q cannot evaluate further, yet q 6= true.
Note that these casts involve only familiar dynamic operations: tag checks,
predicate checks, and creating checking wrappers for functions. Thus, our approach
adheres to the principle of phase separation [Cardelli 1988], in that there is no type
checking of actual program syntax at run time.

4.3

The Type System of λHTC
H
The typing rules for casts are shown in Figure 4.4. Together with the collective

typing rules of λH (Figure 3.5, Figure 3.6, Figure 3.8, Figure 3.7, Figure 3.9, pages 33
- 39), these define the type system of λHTC
H .
[T-Cast] A cast hT ⊳ Si is simply assigned the function type S → T provided both S

and T are well-formed types of the same shape.
[T-Checking] A cast-in-progress h{x : B | p}, q, ki requires that the refinement type

{x : B | p} is well-formed and that the predicate q is a boolean term. Moreover, since this cast succeeds whenever q evaluates to true, we require that q
actually implies the property being checked, [x 7→ k] p. In practice, this always
68

Figure 4.4: Type rules for λHTC
H

E⊢e : T

Additional Type rules
Γ⊢S

Γ⊢T

⌊S⌋ = ⌊T ⌋

[T-Cast]

Γ ⊢ hT ⊳ Si : S → T
Γ ⊢ {x : B | p}

Γ⊢k : B

Γ ⊢ q : Bool

Γ ⊢ h{x : B | p}, q, ki : {x : B | p}

69

Γ ⊢ q ⇒ [x 7→ k] p

[T-Checking]

holds because the evaluation rules ensure that q is arrived at by evaluating
[x 7→ k] p.
A cast in a program may be considered unnecessary in two different ways: via
the operational semantics or via the type system. Operationally, a cast is not useful if
it can never fail. The type system indicates a cast hT ⊳ Si is redundant if S <: T . We
take the term redundant to formally indicate this latter.

Definition 4.3 (Redundant Cast) A redundant cast, or upcast, is a cast hT ⊳ Si in
a term C[hT ⊳ Si] such that Γ ⊢ C[hT ⊳ Si] : U by a derivation
..
.
Γ′ ⊢ hT ⊳ Si : S → T
..
.
Γ ⊢ C[hT ⊳ Si] : U
and Γ′ ⊢ S <: T .
The fundamental correctness property for casts is that any redundant cast can
never fail. In a higher-order setting, where casts are evaluated lazily, defining “can never
fail” is an interesting problem with multiple approaches.
Wadler and Findler [2009] present a syntactic approach to this definition for a
simpler calculus without dependent type or refinement types, but with careful tracking
of blame for failed casts. Through the blame-based analogues of “progress” (if a term
e cannot blame a principal A then it is not stuck blaming A) and “preservation” (if
70

a term e cannot blame A and it evaluates one step to e′ then neither can e′ blame
A). Together these prove that an upcast (in their case, a contract with blame labels
mediating between a simply-typed language and a dynamically-typed language) can
never blame a principal that provides well-typed input.
Instead of attempting a translation of their results to our dependently-typed
setting, we prove a stronger result using the extensional definition of equivalence: A
redundant cast is always contextually equivalent to the identity function.2
The definition of extensional equivalence for λH in Figure 3.10 on page 49 applies unmodified to λH with hybrid type checking, and all the theorems surrounding it
extend without difficulty. Thus correctness of the operational semantics of casts reduces
to the following theorem:

Theorem 4.4 If Γ ⊢ S <: T then Γ ⊢ hT ⊳ Si ∼ (λx : S. x) : (S → T )
Proof: Proceed by induction on the shape of S (which is necessarily also the shape of
T ) proceeding by considering the outermost syntactic form.

Case S = {x : B | p} and T = {x : B | q}
To test equivalence of h{x : B | q} ⊳ {x : B | p}i and (λx : {B | q}. x), we apply
them to two terms d and e such that Γ ⊢ d ∼ e : {x : B | q}. For any closing
substitution σ, we are guaranteed that σ(d)

∗

k if and only if σ(e)

∗

k, so it

suffices to consider applying the cast to such a k. By the semantic definition of
implication and the assumption that constants are assigned appropriate types,
2

This result was actually proved prior to the publication of their work.

71

σ([x 7→ k] q)

∗

true so the cast succeeds, behaving as the identity function

on k.
Case S = x : S1 → S2 and T = x : T1 → T2
To show that this cast is equivalent to the identity function, fix a term f and
apply the cast, yielding
λx : T1 . hT2 ⊳ [x 7→ hS1 ⊳ T1 i x] S2 i (f (hS1 ⊳ T1 i x))
which must be shown equivalent to f . So now fix an argument an argument e
of type T1 and β-reduce to
h[x 7→ e] T2 ⊳ [x 7→ hS1 ⊳ T1 i e] S2 i (f (hS1 ⊳ T1 i e))
By induction, hS1 ⊳ T1 i e is equivalent to e, so the whole term is equivalent to
h[x 7→ e] T2 ⊳ [x 7→ e] S2 i (f e)
Again by induction (and the substitution property of subtyping) this is equivalent to (f e). 
Corollary 4.5 For a derivation
..
.
[T-Cast]

Γ ⊢ hT ⊳ Si : S → T
..
.
∅ ⊢ C[hT ⊳ Si e] : U
if the cast leads to a failure (a formal definition involving labels is beyond the scope of
this chapter) then Γ 6⊢ S <: T and e is a counterexample, a term such that Γ ⊢ e : S
but Γ 6⊢ e : T .
72

To conclude this section, before treating the hybrid type checking algorithm,
we note that all the lemmas of the preceding chapter extend in routine manner to casts,
and we review just the key lemmas of progress and preservation. The preservation
lemma holds as stated, with four new cases to consider.

Lemma 4.6 (Preservation) If Γ ⊢ d : T and d

e then Γ ⊢ e : T .

Proof: The proof is by straightforward induction as before; we discuss only the cases
involving casts. Note the free rebinding of metavariables, which is immaterial to the
argument.

Case [T-Cast]: Evaluation within types does not affect typing.
Case [T-App] for a function cast: We have
hx : T1 → T2 ⊳ x : S1 → S2 i f
evaluating to
λx : T1 . hT2 ⊳ [x 7→ hS1 ⊳ T1 i x] S2 i (f (hS1 ⊳ T1 i x))
and we invite the reader to verify that preservation holds in this case.
Case [T-App] for a cast to basic type: We have
h{x : B | q} ⊳ {x : B | p}i k
evaluating to
h{x : B | q}, [x 7→ k] q, ki
All the antecedents of [T-Checking] are satisfied obviously since the implication
judgment is reflexive.
73

Case [T-Checking] still in progress: Evaluation of the predicate does not affect the
implication judgment.
Case [T-Checking] completed: The implication antecedent of [T-Checking] ensures
that k can be assigned the necessary type by subsumption. 

In this setting, the Progress theorem holds only modulo failed casts; a term
may be “justifiably” stuck because a cast has failed.

Lemma 4.7 (Progress) If ∅ ⊢ e : T then either e is a value, e contains a failed cast,
or there is some e′ such that e

e′ .

Proof: By induction on the derivation of ∅ ⊢ e : T as before. 

4.4

Hybrid Type Checking for λHTC
H
We now describe how to perform hybrid type checking. We work in the specific

context of the language λHTC
H , but have also demonstrated that the general approach
extends to other languages with similarly expressive type systems, such as Sage of
Chapter 7.
The key judgments constituting our hybrid type checking system utilize a
√
three-element lattice of certainty, where “ ” means a judgement certainly holds, “×”
means a judgment has been refuted, and “?” means that a judgment can be neither
√
√
proven nor refuted. In the following, a ∈ { , ×, ?} and × < ? < .

74

- The implication algorithm Γ a p ⇒ q soundly approximates implication, yield
a certainty a about whether the implication holds. (This is a parameter to the
system, hence not in any figure)
- The subtyping algorithm Γ a S <: T soundly approximates subtyping. (Figure 4.5)
- Cast insertion and checking Γ  d ֒→ e ↓ T inserts casts as into d as necessary
to enforce type T , producing a new term e. (Figure 4.6)
- Cast insertion and synthesis Γ  d ֒→ e : T traverses d to produce a new term
e and its type T . (Figure 4.7)
- Cast insertion on types Γ  S ֒→ T traverses the type S and inserts necessary
casts to ensure that T is a well-formed type. (Figure 4.8)

The difference between the two cast insertion judgments of one of “polarity”,
i.e. which arguments of the judgment are considered inputs and which are considered
outputs. The defining rules for these judgments make the distinction clear and rigorous.
Each judgment is discussed in depth below.

4.4.1

Implication Algorithm
The implication algorithm is a parameter of the hybrid type checking system.

Since implication is undecidable, it is intended that our theories adapt to embrace the
latest technology in best-effort automated proving, so it is most effective to provide
the specification to which any such implementation must adhere. For any implication

75

Γ ⊢ p ⇒ q, the algorithmic judgment
Γ a p ⇒ q
√
always must hold with a ∈ { , ×, ?}:
√

- The judgment Γ  p ⇒ q means the algorithm finds a proof that Γ ⊢ p ⇒ q.
- The judgment Γ × p ⇒ q means the algorithm finds a proof that Γ 6⊢ p ⇒ q.
- The judgment Γ ? p ⇒ q means the algorithm terminates without either discovering a proof of either Γ ⊢ p ⇒ q or Γ 6⊢ p ⇒ q.
The requirement placed upon this result is the subject of Requirement 4.8:
Requirement 4.8 (Soundness of Γ a p ⇒ q)
√

1. If Γ  p ⇒ q then Γ ⊢ p ⇒ q.
2. If Γ × p ⇒ q then Γ 6⊢ p ⇒ q.
This algorithm cannot ever be complete. An extremely naive algorithm could
simply return Γ ? p ⇒ q in all cases.

4.4.2

Subtyping Algorithm
The implication-checking algorithm Γ a p ⇒ q naturally induces a 3-valued

algorithmic subtyping judgment:
Γ a S <: T
as shown in Figure 4.5.
76

Figure 4.5: Algorithmic Subtyping for λHTC
H

Γ a S <: T

Subtyping Algorithm
Γ a T1 <: S1

Γ, x : T1 b S2 <: T2
[SA-Arrow]

Γ a⊓b (x : S1 → S2 ) <: (x : T1 → T2 )
Γ, x : B a p ⇒ q

[SA-Base]

a

Γ  {x : B | p} <: {x : B | q}

[SA-Base]: Algorithmic subtyping between refined basic types {x : B | p} and {x : B | q}

reduces to a corresponding three-valued implication judgment between p and
q.
[SA-Arrow]: Subtyping between function types reduces to subtyping between corre-

sponding contravariant domain and covariant range types. This rule uses the
standard meet (greater lower bound) operation ⊓ on the three-element lattice
of possible implication results:
√

?

×

√

√

?

×

?

?

?

×

×

× × ×

⊓

If the subtyping relation can be proven between the domain and range com77

ponents then the subtyping relation holds between the function types. If the
appropriate subtyping relation does not hold between either the domain or
range components, then the subtyping relation does not hold between the function types. Otherwise, in the uncertain case, subtyping may hold between the
function types.

A consequence of the required soundness of the implication algorithm is that
the algorithmic subtyping judgment Γ a S <: T is also sound.
Lemma 1 (Soundness of Γ a S <: T ) Suppose ⊢ Γ.
√

1. If Γ  S <: T then Γ ⊢ S <: T .
2. If Γ × S <: T then Γ 6⊢ S <: T .
Proof: By induction on the derivation of Γ a S <: T using Requirement 4.8. 
Lemma 1 should also be read as a requirement for future hybrid systems: It
may not always be that algorithmic subtyping statically reduces to algorithmic implication. From a broader perspective, the subtyping algorithm itself is a parameter to
hybrid type checking, but this fact should still hold of the algorithmic approximation
to subtyping.

4.4.3

Cast Insertion
Hybrid type checking uses this subtyping algorithm to type check the source

program, and to simultaneously insert dynamic casts to compensate for any indefinite
78

Figure 4.6: Cast insertion and Checking for λHTC
H

Γ  d ֒→ e ↓ T

Cast insertion and checking
Γ  d ֒→ e : S

√

Γ  S <: T
[CC-Ok]

Γ  d ֒→ e ↓ T
Γ  d ֒→ e : S

Γ ? S <: T
[CC-Chk]

Γ  d ֒→ hT ⊳ Si e ↓ T

answers returned by the subtyping algorithm. The judgment which translates subtyping
results to appropriate casts is the cast insertion and checking judgment
Γ  d ֒→ e ↓ T
which intuitively takes Γ, d and T as inputs and provides e either with casts or without.
There are only two defining rules, presented in Figure 4.6, which are mutually recursive
with the cast insertion and synthesis judgment described next:

[CC-Ok]: If the subtyping algorithm succeeds in proving that S is a subtype of T (i.e.,

Γ 

√

S <: T ), then e is clearly of the desired type T , and so is returned

unchanged.
[CC-Chk]: In the uncertain case where Γ ? S <: T , the cast hT ⊳ Si is inserted to

dynamically ensure that e is actually of the desired type T .
79

Note that if Γ × S <: T then no rule applies with inputs Γ, d, and T , so the
term d is rejected.
The cast insertion and synthesis judgment,
Γ  d ֒→ e : T
take Γ and d as inputs and produces e and T as outputs, where T is the type of the
resulting term e. Since types contain terms, we extend this cast insertion process to
types via the judgment
Γ  S ֒→ T
which serves only to traverse types and insert casts to the terms contained therein. The
rules defining each of these this judgments are presented in Figure 4.7; Most of the rules
are straightforward enhancements of the typing rules:

[C-Var] and [C-Const]: Variable references and constants do not require additional

casts.
[C-Fun]: An abstraction (λx : S1 . d) by processed by first inserting casts into the type

S1 to yield T1 and then processing d to yield a term e of type T2 ; the resulting
abstraction λx : T1 . e has type x : T1 → T2 .
[C-Cast]: If a cast hT ⊳ Si occurs in a program, then either of S or T may contain

terms, so casts are inserted into each of these types. In an implementation of
hybrid type checking, one might simply forbid casts in input programs, but we
include this rule for completeness and to discuss idempotence of cast insertion.
80

Figure 4.7: Cast insertion and synthesis for λHTC
terms
H

Cast insertion and synthesis
(x : T ) ∈ Γ

Γ  d ֒→ e : T

[C-Var]

[C-Const]

Γ  x ֒→ x : T

Γ  S1 ֒→ T1

Γ  k ֒→ k : ty(k)

Γ, x : T1 ,  d ֒→ e : T2
[C-Fun]

Γ  (λx : S1 . d) ֒→ (λx : T1 . e) : (x : T1 → T2 )
Γ  f ֒→ g : (x : T1 → T2 )

Γ  d ֒→ e ↓ T1

[C-App]

Γ  f d ֒→ g e : [x 7→ e] T2

Figure 4.8: Cast insertion for λHTC
types
H

Cast insertion on types

Γ  S ֒→ T

Γ  S1 ֒→ T1

Γ, x : T1  S2 ֒→ T2
[C-Arrow]

Γ  (x : S1 → S2 ) ֒→ (x : T1 → T2 )
Γ, x : B  p ֒→ q ↓ Bool
Γ  {x : B | p} ֒→ {x : B | q}

81

[C-Base]

[C-App]: For an application (f d), first casts are inserted into f , yielding g of type

x : T1 → T2 . Then invokes the cast insertion and checking judgment to convert
the argument d into a term e of the appropriate argument type T1 .
[C-Arrow]: Casts are inserted into the domain and codomain of a function type x : S →

T by induction.
[C-Base]: The refinement predicate p of a refined base type is checked against type

Bool.

4.4.4

Correctness of Cast Insertion
Since hybrid type checking relies on necessarily incomplete algorithms for sub-

typing and implication, this section contributes a clear specification for hybrid type
checking in general and proves that our description of hybrid type checking for λHTC
H
meets that specification.
A modern typed compiler relies on type safety for a variety of factors, including
memory layout and optimizations. So it is important that during hybrid type checking
while the input program may be subtly ill-typed, the output program is well-typed.
Thus any terms that would violate guarantees a compiler relied on are safely wrapped.

Theorem 4.9 (Cast insertion produces well-typed terms) Suppose ⊢ Γ.
1. If Γ  e ֒→ e′ : T then Γ ⊢ e′ : T .
2. If Γ  e ֒→ e′ ↓ T and Γ ⊢ T then Γ ⊢ e′ : T .
82

3. If Γ  T ֒→ T ′ then Γ ⊢ T ′ .
Proof: Proceeds directly by mutual induction on cast insertion derivations. 
This fact ensures that the cast insertion rules actually do their job of inserting
casts everywhere that they are needed. Furthermore, the cast insertion process is idempotent provided algorithmic subtyping meets the most basic requirement of reflexivity.
Theorem 4.10 (Idempotence of Cast Insertion)
Suppose that for any Γ and T such that Γ ⊢ T , the subtyping algorithm also
√

determines Γ  T <: T .
1. If Γ  d ֒→ e ↓ T then Γ  e ֒→ e ↓ T .
2. If Γ  d ֒→ e : T then Γ  e ֒→ e : T
Proof: By mutual induction on the derivations. 
Converse to the fact that hybrid type checking always produces a well-typed
program, it also accepts all well-typed programs. The program may be subtle and thus
have some casts added, but these casts are all redundant.
Theorem 4.11 (Cast insertion is the identity on well-typed terms)
1. If Γ ⊢ d : T then there exists a term e and type S such that Γ  d ֒→ e : S and
Γ ⊢ S <: T and Γ ⊢ d ∼ e : T .
2. If Γ ⊢ d : S then for any T such that Γ ⊢ S <: T there exists a term e such that
Γ  d ֒→ e ↓ T and Γ ⊢ d ∼ e : T .
83

3. If Γ ⊢ S then there exists T such that Γ  S ֒→ T and Γ ⊢ S <: T and Γ ⊢
S <: T .

Proof: By mutual induction on the derivations of Γ ⊢ e : T and Γ ⊢ T . Because
algorithmic subtyping can only return ? or

√
, the output of cast insertion is simply

the input with redundant casts inserted. By Theorem 4.4 these are all equivalent to
identity functions. 

4.5

An Example of HTC
To illustrate the behavior of the cast insertion algorithm, consider a function

serializeMatrix that serializes an n by m matrix into an array of size n × m. We
extend the language λHTC
with two additional base types:
H
- Array, the type of one dimensional arrays containing integers.
- Matrix, the type of two dimensional matrices, again containing integers.
The following primitive functions return the size of an array; create a new array of the
given size; and return the width and height of a matrix, respectively:
asize : a : Array → Int
newArray : n : Int → {a : Array | asize a} = n
matrixWidth : a : Matrix → Int
matrixHeight : a : Matrix → Int

84

We introduce the following type abbreviations to denote arrays of size n and matrices
of size n by m:
Arrayn
Matrixn,m

def

=

def

=

{a : Array | (asize a = n)}



{a : Matrix | 




matrixWidth a = n 
}

∧ matrixHeight a = m

The shorthand e as T ensures that the term e has type T by passing e as an argument
to the identity function of type T → T :
def

e as T = (λx : T. x) e
We now define the function serializeMatrix as:




λn : Int. λm : Int. λa : Matrixn,m . 
 as T



let r = newArray e in . . . ; r

The elided term . . . initializes the new array r with the contents of the matrix a, and we
will consider several possibilities for the size expression e. The type T is the specification
of serializeMatrix:
T

def

=

(n : Int → m : Int → Matrixn,m → Arrayn×m )

For this declaration to type check, the inferred type Arraye of the function’s
body must be a subtype of the declared return type:
n : Int, m : Int ⊢ Arraye <: Arrayn×m
Checking this subtype relation reduces to checking the implication:
n : Int, m : Int, r : Array ⊢

(asize r = e)
⇒ (asize r = (n × m))

85

which in turn reduces to checking the equality:
∀n, m ∈ Int. e = n × m
The implication checking algorithm might use an automatic theorem prover to verify or
refute such conjectured equalities.
We now consider three possibilities for the expression e.
1. If e is the expression n × m, the equality is trivially true, and no additional casts
are inserted (even when using a rather weak theorem prover).
2. If e is m × n (i.e., the order of the multiplicands is reversed), and the underlying
theorem prover can verify
∀n, m ∈ Int. m × n = n × m
then again no casts are necessary. Note that a theorem prover that is not complete
for arbitrary multiplication might still have a specific axiom about the commutativity of multiplication.
If the theorem prover is too limited to verify this equality, the hybrid type checker
will still accept this program. However, to compensate for the limitations of the
theorem prover, the hybrid type checker will insert a redundant cast, yielding the
function (where we have elided the source type of the cast):





λn : Int. λm : Int. λa : Matrixn,m . 
hT ⊳ . . .i 
 as T



let r = newArray e in . . . ; r
86

This term can be optimized, via [E-Beta] and [E-Cast-Fn] steps and via removal
of clearly redundant hInt ⊳ Inti casts, to:
λn : Int. λm : Int. λa : Matrixn,m .
let r = newArray (m × n) in
... ;
hArrayn×m ⊳ Arraym×n i r
The remaining cast checks that the result value r is of the declared return type
Arrayn×m , which reduces to dynamically checking that the predicate
asize r = n × m
evaluates to true, which it does.
3. Finally, if e is erroneously m × m, the function is ill-typed. By performing random
or directed [Godefroid et al. 2005] testing of several values for n and m until it finds
a counterexample, the theorem prover might reasonably refute the conjectured
equality:
∀n, m ∈ Int. m × m = n × m
In this case, the hybrid type checker reports a static type error.
Conversely, if the theorem prover is too limited to refute the conjectured equality,

87

then the hybrid type checker will produce (after optimization) the program:
λn : Int. λm : Int. λa : Matrixn,m .
let r = newArray (m × m) in
... ;
hArrayn×m ⊳ Arraym×m i r
If this function is ever called with arguments for which m × m 6= n × m, then the
cast will detect the type error.
Note that prior work on practical dependent types [Xi and Pfenning 1999]
could not handle these cases, since the type T uses non-linear arithmetic expressions.
In contrast, case 2 of this example demonstrates that even fairly partial techniques for
reasoning about complex specifications (e.g., commutativity of multiplication, random
testing of equalities) can facilitate static detection of defects. Furthermore, even though
catching errors at compile time is ideal, catching errors at run time (as in case 3) is still
an improvement over not detecting these errors at all, and getting subsequent crashes
or incorrect results.

4.6

Static Checking vs. Hybrid Checking
Given the proven benefits of traditional, purely-static type systems, an impor-

tant question that arises is how hybrid type checkers relate to static type checkers. To
study this question theoretically, suppose we are given a static type checker that targets
a restricted subset of λHTC
for which type checking is statically decidable. Specifically,
H
88

suppose D is a subset of Term such that for all p, q ∈ D and for all singleton environments
x : B, the judgment x : B ⊢ p ⇒ q is decidable. We introduce a statically-decidable
language λS that is obtained from λHTC
by only permitting D predicates in refinement
H
types. We also assume all types in λS are closed, to avoid the complications of substituting arbitrary terms into refinement predicates via the rule [T-App] (and hence the
above environment x : B suffices). It then follows that subtyping and type checking for
λS are decidable, and we denote this type checking judgment as Γ ⊢S e : T .
As an extreme, we could take D = {true}, in which case the λS type language
is essentially that of STLC. However, to yield a more general argument, we assume only
that D is a subset of Term for which implication is decidable.
Clearly, the hybrid implication algorithm can give precise answers on (decidable) D-terms, and so we assume that for all p, q ∈ D and for all environments x : B, the
√
judgment x : B a p ⇒ q holds for some a ∈ { , ×}. Under this assumption, hybrid
type checking behaves identically to static type checking on (well-typed or ill-typed) λS
programs, formalized as:

Theorem 4.12 For all λS terms e, λS environments Γ, and λS types T , the following
three statements are equivalent:
1. Γ ⊢S e : T
2. Γ ⊢ e : T
3. Γ ⊢ e ֒→ e : T

89

Proof: The hybrid implication algorithm is complete on D-terms, and hence the hybrid subtyping algorithm is complete for λS types. The proof then follows by induction
on typing derivations. 
Thus, to a λS programmer, a hybrid type checker behaves exactly like a traditional
static type checker.
We now compare static and hybrid type checking from the perspective of a
λHTC
programmer. To enable this comparison, we need to map expressive λHTC
types
H
H
into the more restrictive λS types, and in particular to map arbitrary boolean predicates
into D predicates. We assume the computable function
ζ : Term → D
performs this mapping. The function erase then maps λHTC
refinement types to λS
H
refinement types by using ζ to abstract boolean terms:
def

erase({x : B | p}) = {x : B | ζ(p)}
We extend erase in a compatible manner to map λHTC
types, terms, and environments
H
to corresponding λS types, terms, and environments. Thus, for any λHTC
program e,
H
this function yields the corresponding λS program erase(e).
As might be expected, the erase function must lose information, and we now
explore the consequences of this information loss for programs with complex specifications. As an extreme example, let Halt n,m denote a closed formula that encodes “Turing
machine m eventually halts on input n”. Suppose that ζ maps Halt n,m to false for all
90

n and m, and consider the collection of programs Pn,m , which includes both well-typed
and ill-typed programs:
Pn,m

def

=

(λx : {x : Int | Halt n,m }. x) 1

Decidability arguments show that the hybrid type checker will accept some ill-typed
program Pn,m based on its inability to statically prove that Halt n,m = false. In
contrast, the static type checker will reject (the erased version of) Pn,m . Hence:
The static type checker statically rejects (the erased version of ) an ill-typed
program that the hybrid type checker accepts.

Thus, the static type checker performs better in this situation.
However, this particular static type checker will also reject (the erased version
of) many well-typed programs Pn,m that the hybrid type checker accepts. The following
theorem generalizes this argument, and shows that for any computable mapping ζ there
exists some program P such that hybrid type checking of P performs better than static
type checking of erase(P ).

Theorem 4.13 For any computable mapping ζ either:
1. the static type checker rejects the erased version of some well-typed λHTC
program,
H
or
2. the static type checker accepts the erased version of some ill-typed λHTC
program
H
for which the hybrid type checker would statically detect the error.

Proof: Let Γ be the environment x : Int.
91

By reduction from the halting problem, the judgment Γ ⊢ p ⇒ false for
arbitrary boolean terms p is undecidable. However, the implication judgment Γ ⊢
ζ(p) ⇒ ζ(false) is decidable. Hence these two judgments are not equivalent, i.e.:
{t | (Γ ⊢ p ⇒ false)} =
6
{t | (Γ ⊢ ζ(p) ⇒ ζ(false))}
It follows that there must exists some witness w that is in one of these sets but not the
other, and so one of the following two cases must hold.
1. Suppose:
Γ ⊢ w ⇒ false
Γ 6⊢ ζ(w) ⇒ ζ(false)
We construct as a counter-example the program P1 :
P1

def

=

λx : {x : Int | w}. (x as {x : Int | false})

From the assumption Γ ⊢ w ⇒ false the subtyping judgment
∅ ⊢ {x : Int | w} <: {x : Int | false}
holds. Hence, P1 is well-typed, and (by Theorem 4.11 is accepted by the hybrid
type checker. However, from the assumption Γ 6⊢ ζ(w) ⇒ ζ(false) the erased
version of the subtyping judgment does not hold:

∅ 6⊢ erase({x : Int | w}) <: erase({x : Int | false})
Hence erase(P1 ) is ill-typed and rejected by the static type checker.

92

2. Conversely, suppose:
Γ 6⊢ w ⇒ false
Γ ⊢ ζ(w) ⇒ ζ(false)
From the first supposition and by the definition of the implication judgment, there
exists integers n and m such that
[x 7→ n] w

m

true

We now construct as a counter-example the program P2 :
P2

def

λx : {x : Int | w}. (x as {x : Int | false ∧ (n = m)})

=

In the program P2 , the term n = m has no semantic meaning since it is conjoined
with false. The purpose of this term is to serve only as a “hint” to the following rule for refuting implications (which we assume is included in the reasoning
performed by the implication algorithm). In this rule, the integers a and b serve
as hints, and take the place of randomly generated values for testing if p ever
evaluates to true.
[x 7→ a] p

b

true

Γ × p ⇒ (false ∧ a = b)
This rule enables the implication algorithm to conclude that:
Γ × w ⇒ false ∧ (n = m)
Hence, the subtyping algorithm can conclude:
× {x : Int | w} <: {x : Int | false ∧ (n = m)}
93

Therefore, the hybrid type checker correctly rejects P2 , which by Theorem 4.11 is
therefore ill-typed.
We next consider how the static type checker behaves on the program erase(P2 ).
We consider two cases, depending on whether the following implication judgment
holds:
Γ ⊢ ζ(false) ⇒ ζ(false ∧ (n = m))
(a) If this judgment holds then by the transitivity of implication and the assumption Γ ⊢ ζ(w) ⇒ ζ(false) we have that:
Γ ⊢ ζ(w) ⇒ ζ(false ∧ (n = m))
Hence the subtyping judgment
∅ ⊢ {x : Int | ζ(w)} <: {x : Int | ζ(false ∧ (n = m))}
holds and the program erase(P2 ) is incorrectly accepted by the static type
checker:
∅ ⊢ erase(P2 ) : {x : Int | ζ(w)} → {x : Int | ζ(false ∧ (n = m))}
(b) If the above judgment does not hold then consider as a counter-example the
program P3 :
def

P3 = λx : {x : Int | false}. (x as {x : Int | false ∧ (n = m)})
This program is well-typed, from the subtype judgment:
∅ ⊢ {x : Int | false} <: {x : Int | false ∧ (n = m)}
94

However, the erased version of this subtype judgment does not hold:
∅ 6⊢ erase({x : Int | false}) <: erase({x : Int | false ∧(n = m)})
Hence, erase(P3 ) is rejected by the static type checker. 

4.7

Related Work
Many systems feature a combination of static and dynamic checking of speci-

fications. The details of what languages the specifications are written in (if any), which
checks are performed during which phase of a program’s lifecycle, and how this decision
is made, differ considerably.

4.7.1

Static structural types, dynamic contracts
Widespread languages such as Java and C# enforce structural types statically,

while enforcing the implicit contracts on array bounds dynamically. The programming
language Eiffel [Meyer 1988] also includes a structural type system while making dynamic contracts explicit; the languages of statically checked specifications and dynamically checked specifications are completely different.
All implementations of “contracts as a library” such as Hinze et al. [2006],
Chitil [2012] fall into the same classification as Eiffel, with types forming the language
of statically checked specifications and boolean terms of the language at hand expressing
dynamically checked specifications.
Having multiple specification languages is somewhat awkward since it requires
95

the programmer to factor each specification into its static and dynamic components.
Furthermore, the factoring is too rigid, since the specification needs to be manually
refactored to exploit improvements in static checking technology. Contracts implemented as a library have the additional drawback that there is not possible to take
advantage of any formal correspondence between the static and dynamic specifications
languages.

4.7.2

Optimizing implied dynamic checks
The limitations of purely-static and purely-dynamic approaches have also mo-

tivated other work on hybrid analyses. For example, CCured [Necula et al. 2005] is a
sophisticated hybrid analysis for preventing the ubiquitous array bounds violations in
the C programming language. Unlike our proposed approach, it does not detect errors
statically - instead, the static analysis is used to optimize the run-time analysis.
Soft typing Fagan [1991] performs best-effort static type checking in order to
eliminate casts which are implicit in the semantics of any dynamically typed programming language. Henglein [1994] characterized the completion process of inserting the
necessary coercions, and presented a rewriting system for generating minimal completions Thatte [1990] developed a similar system in which the necessary casts are implicit.
Any of these analyses could be viewed as hybrid type systems where the subtyping algorithm never rejects a query. Rather, they start from dynamically typed
languages which are equivalent to a subtyping algorithm that always returns “?” and
√
enhance it to sometimes return “ ”. Optionally, they may be enhanced to determine
96

that “×” is the correct answer, but will only issue warnings since rejection of a program
is not within their purview.

4.7.3

Dynamic typing in a statically-typed language
Many authors have proposed systems that combine static structural type sys-

tems and dynamic typing, using casts to mediate between the dynamically typed portions of a program and the statically typed portions. Abadi et al. [1991] extended a
static type system with a type Dynamic that could be explicitly cast to and from any
other type (with appropriate run-time checks). Siek and Taha [2006] independently developed a system dubbed gradual typing that combines dynamic and static typing via
casts similar to ours. However, they do not address dependent types or refinement predicates, so it remains to be investigated whether the approaches from gradual typing can
shed additional light on our more advanced setting. A longer discussion of subsequent
work on gradual typing is deferred to Chapter 8.
Ou et al. [2004] developed a type system similar to λHTC
H , adding an interface
between untyped code and code with refinement and dependent types. Unlike the
hybrid type checking approach, their type system restricts refinement predicates to
ensure decidability and it supports mutable data. In addition, whereas hybrid type
checking uses dynamic checks to circumvent decidability limitations, their system uses
dynamic checks to permit interoperability between precisely typed code fragments (that
use refinement types) and more coarsely typed code fragments (that do not), and so it
permits the introduction of refinement predicates into a large program in an incremental
97

manner.
These systems are intended to support looser type specifications, blending classic simple type systems with a dynamically typed programming style. In contrast, our
work uses similar, automatically-inserted casts to support more precise type specifications. Chapter 7 combines both approaches to support a large range of specifications,
from Dynamic at one end to precise hybrid-checked specifications at the other.

98

Chapter 5
Type Reconstruction

We have addressed the problem of type-checking for a language like λH , given
complete type annotation by the programmer. But, even for small examples, writing
explicitly typed terms can be tedious and would become truly onerous for larger programs. To reduce the annotation burden, many typed languages – such as ML, Haskell,
and their variants – perform type reconstruction,1 often stated as: Given a program
containing type variables, find a replacement for those variables such that the resulting
program is well typed. If there exists such a replacement, the program is said to be
typeable. Under this definition, type reconstruction subsumes type checking. Hence,
for expressive and undecidable type systems, such as that of λH , type reconstruction is
clearly undecidable.
Instead of surrendering to undecidability, we separate type reconstruction from
1
Type reconstruction is sometimes referred to as “type inference”, especially in mainstream programming practice where it is often an inextricable part of a type checker. But type checking itself is
an inference process, and has also been called “type inference”, so for clarity and precision we carefully
distinguish the terminology.

99

type checking, and define the type reconstruction problem as: Given a program containing type variables, find a replacement for those variables such that typeability is
preserved. In a decidable type system, this definition coincides with the previous one,
since the type checker can decide if the resulting explicitly typed program is well typed.
Our generalized definition also extends to undecidable type systems, since alternative
techniques, such as hybrid type checking of Chapter 4, can be applied to the resulting
program. In particular, type reconstruction for λH is decidable!
Our approach to inferring refinement predicates is inspired by techniques from
axiomatic semantics, most notably the strongest postcondition (SP) transformation [Back
1988]. This transformation supports arbitrary predicates in some specification logic, and
computes the most precise correctness predicate for each program point. It is essentially
syntactic in nature, deferring all semantic reasoning to a subsequent theorem-proving
phase. For example, looping constructs in the program are expressed simply as fixed
point operations in the specification logic.
In the richer setting of λH , which includes higher order functions with dependent types, we must infer both the structural shape of types and also any refinement
predicates they contain. We solve the former using traditional type reconstruction techniques and the latter using a syntactic, SP-like, transformation. Like SP, our algorithm
infers the most precise predicates possible. The calculated higher-order strongest postconditions may contain complex terms that cannot be feasibly checked dynamically due
to fixed-point operations, existential quantification, and parallel boolean connectives,
that are only partially executable, but such execution will never be necessary as the
100

predicates are all correct by construction.
Though the most obvious novelty in type reconstruction for λH lies in higherorder strongest postcondition calculation, we present an end-to-end solution for the full
type reconstruction problem, including pieces that resemble traditional type reconstruction. There are two reasons for this:

1. This full presentation demonstrates exactly how ordinary type reconstruction is
separable from the higher-order postcondition calculation.
2. The metatheoretical approach is of key importance for guiding future work in how
to provide generalized type reconstruction in other contexts.

Chapter Outline
The organization of this chapter is as follows

- Section 5.1 formally defines our generalized notion of type reconstruction.
- Section 5.2 describes the first phase of type reconstruction, constraint generation,
which results in a provisional typing derivation and a set of subtyping constraints
of the form Γ ⊢ S <: T (the same as the subtyping judgment) that must be
satisfied for this typing derivation to be valid.
- Section 5.3 describes the second phase of type reconstruction, shape reconstruction, which converts the subtyping constraints to implication constraints, each of
the form Γ ⊢ p ⇒ q (the same as the implication judgment).
101

- Section 5.4 describes the final phase of type reconstruction, the calculation of
strongest refinements (SR), which solves all remaining implication constraints.
- Section 5.5 establishes the end-to-end correctness of the phases of type reconstruction.
- Section 5.6 surveys closely related work.

5.1

Type Reconstruction for λRecon
H
To explore type reconstruction for λH we work with an extended calculus λRecon
,
H

which the sections of this chapter will extend as needed to present further aspects of
type reconstructions. The input to type reconstruction is described by the syntax of
Figure 5.1, which extends λH with type variables α, representing types omitted in an
input program.
T ::= · · · | α
A type variable may appear anywhere a type is expected. A candidate solution to type
reconstruction is a type replacement π that maps type variables to types. Application
of a type replacement is lifted compatibly to all syntactic sorts, and is not capture
avoiding.

Definition 5.1 (Typeability) A closed term e containing type variables in place of
some or all type annotations is typeable if there is some type T and type replacement
π from type variables to types such that ∅ ⊢ π(e) : π(T ).
102

Figure 5.1: Syntax for λRecon
with type variables
H

e, d, f, g, q, p ::=
k
x
λx : S. e
f e

Terms:
constant
variable
abstraction
application

v ::=

Values: (⊂ Terms)
constant
abstraction

k
λx : S. e
R, S, T, U ::=
x:S → T
{x : B | p}
α

Types:
dependent function type
refined basic type
type variable

Definition 5.2 (The Type Reconstruction Problem) Given a term e containing
type variables in place of some or all type annotations, find a type replacement π such
that e is typeable if and only if π(e) is typeable, and π(e) contains no type variables.

2

Note that for the type reconstruction problem, we consider only the basic
language of λH , omitting casts from Chapter 4 since they add little interest to type
reconstruction.
2

Note that the condition requiring the replacement of all type variables could be relaxed to allow
partial type reconstruction, in the event that even this generalized notion of type reconstruction were
not decidable or feasible.

103

5.2

Constraint Generation
The first phase of type reconstruction is constraint generation, which essen-

tially mirrors type checking but collects subtyping constraints instead of enforcing them.
Figure 5.2 presents the syntax of constraints and extensions to λRecon
necessary.
H
- A subtyping constraint has the same form as the subtyping judgment, Γ ⊢ S <: T .
Constraint generation results in a set of such constraints that essentially record
the flow of data through the program.
- A type well-formedness constraint has the same form as the type well-formedness
judgment, Γ ⊢ T . Constraint generation results in type well-formedness constraints that record the environment in which a type variable must be well-formed,
constraining the variables that may appear free in T .
Definition 5.3 (Constraint Satisfaction) A type replacement π satisfies a subtyping
constraint Γ ⊢ S <: T (respectively, type well-formedness constraint Γ ⊢ T ) if πΓ ⊢
πS <: πT holds as a subtyping judgment (respectively πΓ ⊢ πS holds as a type wellformedness judgment). A type replacement π satisfies a constraint set C if it satisfies
all the constraints in C.
To facilitate our development, we require that the type language be closed
under substitution. But a substitution cannot immediately be applied to a type variable,
so each type variable α is equipped with a delayed substitution θ.
T ::= · · · | θ · α
104

If the substitution θ is empty, then θ·α may simply be written as α. The usual definition
of capture-avoiding substitution is then extended to type variables, which simply delay
that substitution:

[x 7→ e : T ] (θ · α) = ([x 7→ e : T ] ◦ θ) · α
Here we use the notation [x 7→ e : T ] for a delayed substitution, adding a type annotation that we use for later syntactic processing – it does not affect the semantics of
substitution. When a type replacement is applied to a type variable α with a delayed
substitution θ, the substitution π(θ) is immediately applied to π(α):
π(θ · α) = π(θ)(π(α))
Constraint generation is defined by the judgments in Figure 5.3. Each rule
is derived from the corresponding type rule in Figure 3.5 (page 33) with subsumption
distributed throughout the derivation to make the rules syntax-directed.
- The judgment Γ  e : T & C states that under environment Γ the term e has
type T , subject to constraint set C. (Figure 5.3)
- The judgment Γ  T & C states that under environment Γ the type T is wellformed, subject to constraint set C. (Figure 5.4)

5.2.1

Constraint Generation for λRecon
terms
H
The constraint generation judgment for λRecon
terms,
H
Γe : T &C
105

Figure 5.2: Syntax for λRecon
constraint generation
H

e, d, f, g, q, p ::=
k
x
λx : S. e
f e

Terms:
constant
variable
abstraction
application

v ::=

Values: (⊂ Terms)
constant
abstraction

k
λx : S. e
R, S, T, U ::=
x:S → T
{x : B | p}
θ·α

Types:
dependent function type
refined basic type
type variable with substitution

θ ::=

Delayed substitution
empty substitution
substitution extension

[]
[x 7→ e : T ] ◦ θ
Γ ⊢ S <: T

Subtyping constraint

Γ⊢T

Type well-formedness constraint

106

Figure 5.3: Constraint Generation Rules for λRecon
terms
H

Constraint Generation rules
(x : T ) ∈ Γ

Γe : T &C

[CG-Var]

Γx : T &∅
Γ  S & C1

[CG-Const]

Γ  k : ty(k) & ∅
Γ, x : S  e : T & C2
[CG-Fun]

Γ  (λx : S. e) : (x : S → T ) & C1 ∪ C2
Γ  f : T & C1

Γ  e : S & C2

α fresh
[CG-App]

Γ  f e : [x 7→ e : S] · α & C1 ∪ C2 ∪ {Γ, x : S ⊢ α, Γ ⊢ T <: (x : S → α)}

107

is derived from the typing judgment but, like cast insertion, is made syntax-directed by
removing the subsumption rule. The use of subtyping is replaced by the emission of
subtyping constraints which, when satisfied, ensure that the term is well typed.

[CG-Var]: A variable x is assigned the type to which it is bound in the environment Γ

with no constraints.
[CG-Const]: A primitive k is assigned a type by ty with no constraints.

[CG-Fun]: For a λ-abstraction λx : S. e we first generate constraint sets C1 and C2 from

its type annotation S and body e, respectively, and assign it the usual type
under the union of their constraints, C1 ∪ C2 .
[CG-App]: Most of the interest lies in this rule for function applications f e. Given

that f has type T , it may be that T is a type variable, so we cannot extract
its domain type. Instead we constrain the lower bound of its domain to be S,
the type of e, and its return type to be a fresh unknown type variable α. Thus
the return type of the application include a delayed substitution [x 7→ e] · α.
Since any solution for α must be well-formed in the environment Γ, x : S, this
type-wellformedness constraint is emitted as well.

5.2.2

Constraint Generation for λRecon
types
H
The constraint generation judgment
ΓT &C

108

Figure 5.4: Constraint Generation Rules for λRecon
types
H

Well-formed Type Constraint Generation
Γ  S & C1

ΓT &C

Γ, x : S  T & C2
[WTC-Arrow]

Γ  x : S → T & C1 ∪ C2
Γ, x : B  e : Bool & C
[WTC-Base]

Γ  {x : B | e} & C

[WTC-Var]

Γ  α & {Γ ⊢ α}

emits in C any constraints which must be satisfied in order for T to be a well formed
type in environment Γ.

[WTC-Arrow] and [WTC-Base]: Well-formedness of types is subject to those con-

straints that come from checking the terms which they contain.
[WTC-Var]: Type variables are well-formed subject to a new type well-formedness con-

straint defining the variables that may appear free in any solution. Furthermore, only type variables without a delayed substitution are allowed in the
source program. This constraint could probably be relaxed but this reflects the
reality that delayed substitutions are a formality not present in the statement

109

of the type reconstruction problem itself.

The constraint set C establishes a firm interface between this phase and the
remaining phases; remaining phases need not inspect the original program at all, but
merely satisfy the constraints: When applied to a typeable λRecon
program, the conH
straint generation rules emit a satisfiable constraint set. Conversely, if the constraint
set derived from a program is satisfiable, then that program is typeable.

Lemma 5.4 (Constraint Generation is Sound and Complete)

1. For any environment Γ and term e:

∃π, T. πΓ ⊢ πe : πT

⇐⇒

∃π ′ , S, C.




 Γe : S &C


 π ′ satisfies C

2. For any environment Γ and type T ,

∃π. πΓ ⊢ πT

⇐⇒

∃π ′ , C.




 ΓT &C



 π ′ satisfies C

Proof: We examine the details of the proof for terms only.

(⇐)

Soundness of constraint generation is simple: let π = π ′ and let T = S.

(⇒)

To demonstrate completeness of constraint generation – that every typeable
term generates a satisfiable constraint set – we will prove a stronger statement
in order to strengthen our induction hypothesis to deal with new type variables

110

arising during constraint generation:

∃π, T. πΓ ⊢ πe : πT

=⇒

∃π ′ , S, C.





Γe : S &C







 ππ ′ satisfies C




πΓ ⊢ ππ ′ S <: πT







 π ′ and π have disjoint domains

Proceeding by (mutual) induction, each case requires calculations with substitutions to produce the required π ′ . All the interest lies in a single case:
Case [T-App]: We have a derivation (to minimize subscripts, all metavariables
used here are fresh – the “e” and “T ” below are not that of the theorem
but new for this proof case)

..
.

..
.

πΓ ⊢ πf : π(x : T → U )

πΓ ⊢ πe : πT

πΓ ⊢ πf πe : [x 7→ πe : πT ] (πU )
By induction we have corresponding subderivations that we can put
together with [CG-App]

..
.

..
.

Γ  f : S f & Cf

Γ  e : S e & Ce

α fresh

Γ  f e : [x 7→ e : Se ] · α & Cf ∪ Ce ∪ {Γ, x : Se ⊢ α, Γ ⊢ Sf <: x : Se → α}
Our strengthened hypothesis provides us with πe and πf with disjoint
domains that combine with π to satisfy Ce and Cf , respectively, along
111

with the facts
1. πΓ ⊢ ππe Se <: πT
2. πΓ ⊢ ππf Sf <: π(x : T → U )
We need to account for the fresh α and the natural choice is the corresponding type from the premise. Namely, we use this solution:
π ′ = [α 7→ πU ] ◦ πf πe
Since the domains of all substitutions are disjoint, this also satisfies Ce
and Cf immediately, and the remaining constraint simplifies to
ππ ′ Γ ⊢ π ′ πSf <: ππ ′ (x : Se → πT )
and we can combine 1 and 2 along with the fact that subtyping is a
preorder (Lemma 3.1 on page 38) to calculate that this judgment holds.
Then to satisfy the strengthened induction hypothesis we also need
πΓ ⊢ π ′ π([x 7→ e : Se ] · α) <: [x 7→ πe : πT ] πU
which simplifies to
πΓ ⊢ [x 7→ ππ ′ e : ππ ′ Se ] πU <: [x 7→ πe : πT ] πU
in which both sides are syntactically identical since dom(π ′ ) contains
no type variables appearing in e and the type annotation on the substitution is irrelevant. 

Consider the following λRecon
term e (the expression let x : T = d in e is
H
syntactic sugar for (λx : T. e) d).

112

let id : (x : α1 → α2 ) = λx : α3 . x in
let w : {n : Int | n = 0} = 0 in
let y : {n : Int | n > w} = 3 in
id (id y)
Eliding some generated type variables and type well-formedness constraints for
clarity, the corresponding constraint generation judgement is
∅  e : [x 7→ (id y) : α1 ] · α2 & C
where C contains the following constraints, in which Tid ≡ (x : α1 → α2 ) and the
declaration of some variables (w and y in this case) is left implicit at their leftmost
occurence.
∅ ⊢

x : α3 → α3 <: x : α1 → α2

id : Tid ⊢ {n : Int | n = 0} <: {n : Int | n = 0}
id : Tid , w = 0 ⊢ {n : Int | n = 3} <: {n : Int | n > w}
id : Tid , w = 0, y > w ⊢ {n : Int | n > w} <: α1
id : Tid , w = 0, y > w ⊢ [x 7→ y : α1 ] · α2 <: α1

5.3

∅ ⊢

x : α1 → α2

∅ ⊢

α3

Shape Reconstruction
The second phase of reconstruction is to infer a type’s basic shape, deferring

resolution of refinement predicates. To defer reconstruction of refinements, we introduce
113

placeholders ψ ∈ P laceholder to represent unknown refinement predicates (in the same
way that type variables represent unknown types). The net effect of shape reconstruction
is to reduce subtyping contraints to implication constraints and type well-formdness
constraints to refinement well-typedness constraints. Figure 5.5 shows the full syntax
for the resulting language after shape reconstruction. During reconstruction, there may
be terms from the union of the two languages, but when it terminates successfully all
type variables are eliminated and all constraints are reduced.

- An implication constraint Γ ⊢ p ⇒ q indicates that for the program to be typeable,
the corresponding implication must hold.
- A refinement well-typedness constraint Γ ⊢ p : Bool constrains the variables that
may appear free in p.

Like type variables, each placeholder has an associated delayed substitution.

p

::=

··· | θ · ψ

A placeholder replacement is a function ρ : P laceholder → T erm and is lifted compatibly to all syntactic structures. As with type replacements, applying placeholder
replacement allows any delayed substitutions also to be applied.
[x 7→ e : T ](θ · ψ) = ([x 7→ e : T ], θ) · ψ
ρ(θ · ψ) = ρ(θ)(ρ(ψ))
For a placeholder replacement ρ, the definition of constraint satisfaction is analogous to
Definition 5.3 for type replacements.
114

Figure 5.5: Syntax for λRecon
shape reconstruction
H

p, q ::=
θ·ψ
e

Predicates:
placeholder with substitution
term

e, d, f, g ::=
k
x
λx : S. e
f e

Terms:
constant
variable
abstraction
application

v ::=

Values: (⊂ Terms)
constant
abstraction

k
λx : S. e
R, S, T, U ::=
x:S → T
{x : B | p}

Types:
dependent function type
refined basic type

θ ::=

Delayed substitution
empty substitution
substitution extension

[]
[x 7→ e : T ] ◦ θ
Γ⊢p⇒q
Γ ⊢ p : Bool

Implication constraint
Refinement well-typedness constraint

115

Definition 5.5 A placeholder replacement ρ satisfies a constraint set P if and only if
ρ(P ) contains only valid implications and typing judgments.

The shape reconstruction algorithm, detailed in Figure 5.6 takes as input a
subtyping constraint set C and processes the constraints in C nondeterministically according to the rules in Figure 5.6. When the conditions on the left-hand side of a rule
are satisfied, the updates described on the right-hand side are performed. The set P of
implication constraints, each of the form Γ ⊢ p ⇒ q, and the type replacement π are
outputs of the algorithm. Each rule in Figure 5.6 resembles a step of traditional type
reconstruction; this algorithm is presented as formal evidence that this step of reconstruction can, in fact, be separated from any reasoning about refinement predicates.

1. When a type variable α must have the shape of a function type, it is replaced by
x : α1 → α2 , where α1 and α2 are fresh type variables. The standard function
occurs checks that α has a finite solution, since λRecon
does not have recursive
H
types. Occurences of α which appear in refinement predicates or in the range of
a delayed substitution are ignored – these occurences do not require a solution
involving recursive types.
occurs(α, {x : B | e})

def

=

f alse

occurs(α, θ · α′ )

def

=

f alse (α 6= α′ )

occurs(α, θ · α)

def

=

true

occurs(α, x : S → T )

def

occurs(α, S) ∨ occurs(α, T )

=

116

Figure 5.6: Shape Reconstruction Algorithm

Input: C
Output: π, P
Initially: P = ∅ and π = [ ]
match some constraint in C, removing it, until quiescent:
Γ ⊢ θ · α <: x : T1 → T2
Γ ⊢ x : T1 → T2 <: θ · α

=⇒

or

if occurs(α, x : T1 → T2 )
thenf ail
otherwise for fresh α1 , α2
π := [α 7→ (x : α1 → α2 )] ◦ π
C := π(C)
P := π(P )

Γ ⊢ θ · α <: {x : B | e}
Γ ⊢ {x : B | e} <: θ · α

=⇒

or

for fresh ψ
π := [α 7→ {x : B | ψ}] ◦ π
C := π(C)
P := π(P )

1.

2.



Γ ⊢ T1 <: S1
Γ, x : T1 ⊢ S2 <: T2

3. Γ ⊢ (x : S1 → S2 ) <: (x : T1 → T2 )

=⇒ C := C ∪

4. Γ ⊢ {x : B | p} <: {x : B | q}

=⇒ P := P ∪ {Γ ⊢ p ⇒ q}


Γ⊢S
Γ, x : S ⊢ T



5. Γ ⊢ (x : S → T )

=⇒ C := C ∪

6. Γ ⊢ {x : B | p}

=⇒ P := P ∪ {Γ, x : B ⊢ p : Bool}

7.

or
or

Γ ⊢ {x : B | p} <: x : S → T
Γ ⊢ x : S → T <: {x : B | p}
Γ ⊢ {x : B | p} <: {x : B ′ | q}
(B 6= B ′ )

=⇒ fail

117



2. When a type variable must be a refinement of a base type B, the type variable is
replaced by {x : B | ψ} where ψ is a fresh placeholder.
3. A subtyping constraint between two function types induces additional constraints
between the domains and codomains of the function types.
4. A subtyping constraint between two refined base types is simplified to an implication constraint between their refinements.
5. A type well-formedness constraint on a function type is decomposed into type
well-formedness constraints on both of its components.
6. A type well-formedness constraint on a refined base type is replaced by the equivalent refinement well-typedness constraint.
7. Whenever two types have incompatible shapes, the algorithm fails immediately.
The algorithm terminates once no more progress can be made. At this stage,
any type variables remaining in π(C) are not constrained to be subtypes of any concrete
type but may be subtypes of each other. We set these type variables equal to an arbitrary
concrete type to eliminate them (the resulting subtyping judgements are trivial by
reflexivity).
For a set of subtyping constraints C, one of the following occurs:
1. Shape reconstruction fails, in which case C is unsatisfiable, or
2. Shape reconstruction succeeds, yielding π and P . Then P is satisfiable if and only
if C is satisfiable. Furthermore, if ρ satisfies P then ρ ◦ π satisfies C.
118

More formally,

Lemma 5.6 (Shape Reconstruction is Sound and Complete)
For any set of subtyping constraints C,



 π ′ , P result from shape reconstruction
′
∃π. π satisfies C ⇐⇒ ∃π , P, ρ.


 ρ satisfies P

Proof: Each step of the algorithm maintains the invariant that C and P are equisatisfiable with their prior state, so consider only the case where shape reconstruction
fails. The algorithm can only fail when two types are utterly incompatible, and by the
invariant this constraint originates from a constraint in the original set of subtyping
constraints. 
Returning to our example, shape reconstruction returns the type replacement
π = [ α1 7→ {n : Int | ψ1 }, α2 7→ {n : Int | ψ2 }, α3 7→ {n : Int | ψ3 } ]
and the following implication constraint set P , where now Tid = x : {n : Int | ψ2 } →
{n : Int | ψ3 }, noting that the type variables have been elaborated into refinement types

119

containing placeholders.
n : Int ⊢

ψ1 ⇒ ψ3

x : {n : Int | ψ1 }, n : Int ⊢

ψ3 ⇒ ψ2

id : Tid , n : Int ⊢ (n = 0) ⇒ (n = 0)
id : Tid , w = 0, n : Int ⊢ (n = 3) ⇒ (n > w)
id : Tid , w = 0, y > w, n : Int ⊢ (n > w) ⇒ ψ1
id : Tid , w = 0, y > w, n : Int ⊢

5.4

[x 7→ y : {n : Int | ψ1 }] · ψ2 ⇒ ψ1

n : Int ⊢

ψ1 : Bool

n : Int, x : {n : Int | ψ1 } ⊢

ψ2 : Bool

n : Int ⊢

ψ3 : Bool

Strongest Refinements
The final phase of type reconstruction solves the residual implication constraint

set P by finding a placeholder replacement that preserves satisfiability.
Our approach is based on the intuition – temporarily ignoring the complication
of dependent types – that implications are essentially dataflow paths that carry the specifications of data sources (constants and function postconditions) to the requirements
of data sinks (function preconditions), with placeholders functioning as intermediate
nodes in the dataflow graph. Thus, if a placeholder ψ appears on the right-hand side
of two implication constraints Γ ⊢ p ⇒ ψ and Γ ⊢ q ⇒ ψ, then our replacement for ψ
is simply the disjunction p ∨ q (the strongest consequence) of these two lower bounds.
Our algorithm repeatedly applies this transformation until no placeholders remain, but
120

several difficulties arise that are not present in the simpler first-order setting of SP:

1. p or q may contain variables that cannot appear in a solution for ψ
2. ψ may have a delayed substitution
3. ψ may appear in p or q

To help resolve these issues, we extend the language of terms with parallel boolean connectives and existential quantification and will utilize the fixed point operator assumed
to be included in the primitives of the language.
e ::=

···

|

e∨e

|

e∧e

|

∃x : T. e

Figure 5.7 summarizes the syntax of the final language in which the output
of type reconstruction lies, and Figure 5.8 presents the operational semantics of the
parallel boolean connectives.

- The parallel disjunction e1 ∨ e2 (respectively conjunction e1 ∧ e2 ) evaluates e1 and
e2 nondeterministically, reducing to true (resp. false) if either of them reduces
to true (resp. false).
- The existential term ∃x : T. e binds x in e, and evaluates by nondeterministically
replacing x with a closed term of type T . The evaluation rules are summarized
in Figure 5.8. Note that neither parallel boolean connective (nor, obviously, existential quantification) can be encoded via β or δ reduction.3
3

In synthetic topology [Escardó 2004], where open sets correspond to semidecidable predicates very

121

Figure 5.7: Syntax for λRecon
with strongest refinements
H

e, d, f, g, q, p ::=
k
λx : S. e
x
f e
∃x : T. p
e∨e
e∧e

Terms:
constant
abstraction
variable
application
existential quantification
parallel disjunction
parallel conjunction

v ::=

Values: (⊂ Terms)
constant
abstraction

k
λx : S. e
R, S, T, U ::=
x:S → T
{x : B | p}

Types:
dependent function type
refined basic type

Figure 5.8: Additional Redex Reduction for Parallel Logical Connectives

true ∨ e y true

[E-Or-L]

false ∧ e y false [E-And-L]

e ∨ true y true

[E-Or-R]

e ∧ false y false [E-And-R]

false ∨ false y false [E-Or-F]
∃x : T. p y [x 7→ e : T ] p

true ∧ true y true
if ∅ ⊢ e : T

[E-Exists]

C ::= · · · | e ∨ C | C ∨ e | C ∧ e | e ∧ C | ∃x : D. p | ∃x : T. C

122

[E-And-T]

5.4.1

Free Variable Elimination
The immediate lower bounds for a placeholder may contain free variables that

may not appear free in any solution. In our example program, the type variable α1
appeared in the empty environment and π(α1 ) = {n : Int | ψ1 }, yielding a refinement
well-typedness constraint n : Int ⊢ ψ1 : Bool. The only variable that can appear in a
solution for ψ1 is therefore n. But consider the following lower bound for ψ1 :
id : Tid , w = 0, y > w, n : Int ⊢ (n > w) ⇒ ψ1
Since id, w, and y cannot appear in a solution for ψ1 , we need a way to identify and
remove them from the lower bound without changing its logical interpretation.
In general, each placeholder ψ introduced by shape reconstruction has an associated environment Γψ in which it must have type Bool, recorded in the refinement
well-typedness constraints. This gives us a reasonable definition for the free variables
of a placeholder (with its associated delayed subtitution):
f v(θ · ψ) = (dom(Γψ ) \ dom(θ)) ∪ f v(rng(θ))
We then rewrite each implication constraint Γ, y : T ⊢ p ⇒ q where y 6∈ f v(q) into
the constraint Γ ⊢ (∃y : T. p) ⇒ q. This corresponds to the following tautology4
much like executable refinement types, the “parallel or” connective is required in order for the usual
defining property of a topology to apply. Synthetic topology uses a single-valued boolean, denoting
falsity by nontermination. Because we denote falsity by termination at false we also require “parallel
and”. Our inputs are essentially open sets, though our syntactic approach obscures this to the point
where formally establishing the connection must be left for future research, and our output must also
be an open set, which we construct by union and intersection. Further, any overt data type – dual
to compact – is equipped with a semidecidable existential quantifier, which would become relevant if
existential quantification were ever to be supported in runtime checks.
4
A “De Morgan law” for quantification.

123

(∀x. p ⇒ q) ⇔ ((∃x.p) ⇒ q) when x 6∈ f v(q); the executable existential is carefully
defined so that the tautology holds.
Thus we rewrite the constraint
id : Tid , w = 0, y > w, n : Int ⊢ (n > w) ⇒ ψ1
to the new constraint
n : Int ⊢ (∃id : Tid . ∃w : w = 0. ∃y : y > w. n > w) ⇒ ψ1
This transformation is semantics-preserving:

Lemma 5.7 (Correctness of Free Variable Elimination) For y ∈
/ f v(q), Γ, y :
T ⊢ p ⇒ q if and only if Γ ⊢ (∃y : T. p) ⇒ q
Proof: Consider each direction of entailment separately.

(⇒)

Suppose Γ, y : T ⊢ p ⇒ q and y 6∈ f v(q) and consider any σ such that ⊢ σ : Γ
∗

and σ(∃y : T. p)
σ(∃y : T. p)

true. Then there is some e such that ∅ ⊢ e : T and
∗

σ([x 7→ e : T ]p)

true.

Let σ ′ = σ ◦ [y 7→ e] so that ⊢ σ ′ : Γ, y : T . Then by assumption, σ(p)
implies σ(q)

∗

∗

true

true. Since y ∈
/ f v(q), σ ′ (q) = σ(q), and we have proved

Γ ⊢ ∃y : T. p ⇒ q.
(⇐)

Conversely, suppose Γ ⊢ ∃y : T. p ⇒ q where y 6∈ f v(q) and consider σ such
that ⊢ σ : Γ, y : T and σ(p)

∗

true.

124

Since y 6∈ dom(Γ) we can ignore it, so ⊢ σ : Γ, and the nondeterministic
existential can replace y with σ(y), so σ(∃y : T. p)
assumption, this implies that σ(q)

∗

∗

σ(p)

∗

true. By

true, and we have proved Γ, y : T ⊢ p ⇒

q. 

Repeatedly applying this transformation, we rewrite each implication constraint until the domain of the environment (and hence the free variables of the left-hand
side) is a subset of the free variables of the right-hand side.

5.4.2

Delayed Substitution Elimination
At this stage, all constraints are of the form
Γ⊢p⇒θ·ψ

where dom(Γ) ⊆ f v(θ · ψ) The next issue is the presence of delayed substitutions in
constraints. To eliminate the delayed substitution θ we add the variables in its domain
to the environment and encode its semantic content as an additional predicate. Formally,
env([ ]) = ∅

pred( [ ] ) = true

env([x 7→ e : T ], θ) = x : T, env(θ)

pred( [x 7→ e : T ], θ ) = (x = e) ∧ pred(θ)

The environment env(θ) binds all the variables in dom(θ) while the term pred(θ) represents the semantic content of θ.
We then transform the constraint
Γ⊢p⇒θ·ψ
125

into the equivalent constraint
Γ, env(θ) ⊢ pred(θ) ∧ p ⇒ ψ.
But we can rewrite the constraint even more cleanly: Γ must be some prefix of Γψ since
by the previous transformation dom(Γ) ⊆ f v(θ · ψ) ⊆ dom(Γψ ). Any x ∈ dom(θ) such
that x 6∈ dom(Γψ ) can be dropped from θ and we see that Γ, env(θ) is then exactly Γψ .
So our constraint is
Γψ ⊢ pred(θ) ∧ p ⇒ ψ
To prove this transformation correct, we use the following well formedness
judgement Γ ⊢wf θ which distinguishes those delayed substitutions that may actually
occur in context Γ.

[WF-Empty]

Γ, x : T ⊢wf θ ′

Γ⊢e : T

Γ ⊢wf [ ]

Γ ⊢wf [x 7→ e : T ] ◦ θ

[WF-Ext]

′

Lemma 5.8 (Correctness of Substitution Elimination) Suppose ρ is a placeholder
replacement such that ρ(Γ) ⊢wf ρ(θ). Then ρ satisfies Γ ⊢ p ⇒ θ · ψ if and only if ρ
satisfies Γ, env(θ) ⊢ pred(θ) ∧ p ⇒ ψ.
Proof: Assume throughout that ρΓ ⊢wf ρθ
(⇒)

Suppose ρ satisfies Γ ⊢ p ⇒ θ · ψ, i.e. ρΓ ⊢ ρp ⇒ ρθ(ρψ) and consider any σ
s.t. ⊢ σ : ρΓ, env(ρθ) and σ(ρ(pred(θ) ∧ p))
the conjunction we know that σ(ρp)
assumption, which is σ(ρθ(ρψ))

∗

∗

true.

126

∗

true. Then by definition of

true, hence σ(ρ(θ · ψ))

∗

true by

Thus since σ is a closing substitution for ρ(Γ, env(θ)), we know for any x ∈
dom(θ) that σ(x) = (ρθ)(x) hence σ(ρψ) = σ(ρθ(ρψ)), which we already know
evaluates to true. Hence ρ satisfies Γ, env(θ) ⊢ pred(θ) ∧ p ⇒ ψ.
(⇐)

Suppose ρ satisfies Γ, env(θ) ⊢ pred(θ) ∧ p ⇒ ψ i.e. ρΓ, env(ρθ) ⊢ ρ(pred(θ) ∧ p) ⇒
ρψ and consider any σ s.t. ⊢ σ : ρΓ and σ(ρp)

∗

true. Let σ ′ = σ ◦ (ρθ);

note that ⊢ σ ′ : ρΓ, env(ρθ).
Obviously, σ ′ (ρp)

∗

true since dom(θ) ∩ f v(p) = ∅. Furthermore, by inten-

sional equality, we see that σ ′ (pred(θ))

∗

true. So the conjunction in our

assumption evaluates to true, and we infer that σ ′ (ρψ)

∗

true.

And σ ′ (ρψ) = σ(ρθ(ρψ)) which is exactly what we need to conclude that ρ
satisfies Γ ⊢ p ⇒ θ · ψ 

5.4.3

Placeholder Solution
After the previous transformations, all lower bounds of a placeholder ψ appear

in constraints of the form
Γψ ⊢ p i ⇒ ψ
for i ∈ {1..n}, assuming ψ has n lower bounds. We want to set ψ equal to the parallel
disjunction p1 ∨ p2 ∨ · · · ∨ pn of all its lower bounds (the disjunction must be parallel
because some subterms may be nonterminating). However, ψ may appear in some pi
due to recursion or self-composition of a function. In this case we use a least fixed
point operator, conveniently already available in our language, to find a solution to the

127

Figure 5.9: Shorthands for λRecon
H

T → Bool
λx : T . e
f x

def

=

T1 → T2 → · · · → Tk → Bool

def

=

λx1 : T1 . λx2 : T2 . · · · λxk : Tk . e

def

f x1 x2 · · · xk

=

equation ψ = p1 ∨ · · · ∨ pn .
More formally, suppose Γψ = x1 : T1 , · · · , xk : Tk . Then ψ is a predicate over
x1 · · · xk and we can interpret it as a function Fγ : T1 → · · · → Tk → Bool and give
a recursive definition for the function. Using the shorthands of Figure 5.9 the function
Fψ can be defined as the following least fixed point computation:
Fψ = fixT →Bool (λf : T → Bool. λx : T . [ψ 7→ f x](p1 ∨ · · · ∨ pn ))
Our solution for ψ is SR(ψ) = Fψ x. This is the strongest refinement (SR)
that is implied by all lower bounds of ψ. This fixed-point calculation will terminate
immediately in the event that ψ does not appear in any of its lower bounds. When the
program is recursive, the presence of a fixed-point calculation is expected. Applying
existing and new techniques to this carefully isolated subproblem remains an open and
fruitful area for future research, but has already borne fruit: Subsequent to this work,
Rondon et al. [2008] applied predicate abstraction to calculate enough of these fixed
points that the program annotations to ensure fully static type-checking were reduced
from 31% of the text to less than 1%.
128

Lemma 5.9 (SR is the Strongest Refinement)
If ρ satisfies P , then ρ satisfies Γψ ⊢ SR(ψ) ⇒ ψ.
Proof: Consider any ρ satisfying P and σ such that ⊢ σ : ρΓψ and σρ(SR(ψ))

∗

true

minimal k. Since the possible nondeterministic evaluations of σρSR(ψ) correspond to
simultaneously evaluating all lower bounds for ψ simultaneously, one of them must
eventually evaluate to true. Then by the assumption that ρ satisfies P , we know that
σρψ

∗

true. 
The result of equisatisfiability follows from the fact that we have chosen the

strongest possible solution for ψ.

Lemma 5.10 (Correctness of Strongest Refinement Calculation)
P is satisfiable if and only if P [ψ := SR(ψ)] is satisfiable.

Proof:

(⇐)

For any ρ : P laceHolders → T erms that satisfies P [ψ := SR(ψ)], we have
ρ ◦ [ψ := SR(ψ)] that satisfies P .

(⇒)

Consider any ρ : P laceHolders → T erms that satisfies P . By Lemma 5.9 if
ρ(ψ) ⇒ p occurs in P , then SR(ψ) ⇒ ρ(ψ) ⇒ p; covariant occurences of ψ
in environments are analogous. If p ⇒ ρ(ψ) occurs in P , then p ⇒ SR(ψ) by
construction of SR(ψ); contravariant occurences of types in environments do
not affect satisfiability. 

129

In our example, the only lower bound of ψ3 is ψ1 and the only lower bound of
ψ2 is ψ3 , so let us set ψ3 := ψ1 and ψ2 := ψ3 in order to discuss the more interesting
solution for ψ1 . The resulting unsatisfied constraints (simplified for clarity) are:
n : Int ⊢ ∃w : {n : Int | n = 0}. (n > w) ⇒ ψ1
n : Int ⊢

∃w : w = 0. ∃y : y > w. [x 7→ y] · ψ1 ⇒ ψ1

The exact text of SR(ψ1 ) is too large to print here, but it is equivalent to
∃w : w = 0. n > w and thus equivalent to (n > 0). The resulting explicitly typed
program (simplified accordingly) is:
let id : (x : {n : Int | n > 0} → {n : Int | n > 0}) = λx : {n : Int | n > 0}. x in
let w : {n : Int | n = 0} = 0 in
let y : {n : Int | n > w} = 3 in
id (id y)

5.5

Type Reconstruction is Typability-Preserving
The output of our algorithm is the composition of the type replacement re-

turned by shape reconstruction and the placeholder replacement returned by the satisfiability routine. Application of this composed replacement is a typeability-preserving
transformation. Moreover, for any typeable program, the algorithm succeeds in producing such a replacement.

Theorem 5.11 For any λRecon
program e, one of the following occurs:
H
1. Type reconstruction fails, in which case e is untypeable, or
130

2. Type reconstruction returns a type replacement π such that e is typeable if and
only if π(e) is well typed.

Proof:
1. Only shape reconstruction can fail. If it does, then by Lemma 5.6 the subtyping
constraints are unsatisfiable. Then by Lemma 5.4, e is not typeable.
2. Type reconstruction solved constraints that were faithful, by Lemma 5.4. Thus by
Lemma 5.6 we have π and by Lemma 5.10 we have ρ such that ρπe is typeable (well
typed) if and only if e is typeable. 

5.6

Related Work
Constraint-based type reconstruction for systems with subtyping is a tremen-

dously broad topic that we cannot fully review here. The problem is studied in some generality by Mitchell [1984], Fuh and Mishra [1988], Lincoln and Mitchell [1992], Aiken and Wimmers
[1993], and Hoang and Mitchell [1995]. Type inference systems parameterized by a subtyping constraint system are developed by Pottier [1996] and Odersky et al. [1999].
This work is complementary to generalized systems in that it focuses on the solution of our particular instantiation of subtyping constraints; we also do not investigate
parametric polymorphism, which is included in the mentioned frameworks. Set-based
analysis presents many similar ideas, and this chapter also draws inspiration from the
works of Heintze [1992], Cousot and Cousot [1995], Fahndrich and Aiken [1996], and
Flanagan and Felleisen [1997].
131

Many of the refinement type systems discussed in Section 1.1.2 perform some
manner of local type reconstruction [Pierce and Turner 1998; Dunfield and Pfenning
2004], but require type annotations to guide the type checking process, and none address the general problem of the satisfaction of a set of subtyping constraints between
refinement types. Dependent ML Xi and Pfenning [1999] solves systems of linear inequalities to infer a restricted class of type indices.
Liquid types [Rondon et al. 2008], discussed at length in Section 8.2.2 take the
solution presented here a step further by using abstract interpretation to solve for closed
forms of fixed points such as those produced by our algorithm. The particular abstract
interpretation used is predicate abstraction where a finite set of predicate templates are
instantiated with the variables of the program, so common predicates for a given domain
can be outlined and then the system performs full type reconstruction. If a solution
cannot be found by the predicate abstraction process, then the program is rejected. A
great benefit of Liquid Types is that the predicates inferred can be very readable as
they come from a small set of templates and do not include fixed points or existential
quantification. Our deliberate separation of the generation of the fixed point constraints
from the solving of them for a closed form leaves other avenues open. For example, it
is not generally necessary to find a closed form solution except for nice error messages:
our reconstructed predicates are never used as preconditions, but only to satisfy other
preconditions, so strategic unrolling and approximation may be sufficient to perform
(possibly hybrid) type checking of the resultant program.
Rastogi et al. [2012] developes a type inference system for the much more com132

plex context of Actionscript and evaluate the performance asymptotically and through
an implementation with benchmarks time trials.

133

Chapter 6
Compositional and Decidable Checking

Having established that executable refinement types can be effectively reconstructed from a program with no type annotations and subsequently enforced using
hybrid type checking, we return to the question of when – if ever – we can predict that
the type checking question for a particular program will be decidable. Note that this is
distinct from, though related to, the design of a decidable restriction of the type system.
Unfortunately, the essential difference between dependent types and simple
structural type systems prevents a satisfactory answer. Simple type systems such as that
of ST LC perform compositional reasoning in that the type of a term depends only on
the types of its subterms, not on their semantics. Dependent types offer more expressive
abstractions, but violate those abstractions and base their reasoning directly upon the
semantics of terms.1 Pragmatically, noncompositionality makes the decidability of static
checking unpredictable.
1

This is, in fact, the origin of the term “dependent type”: the types depend on terms.

134

This chapter shows how compositional reasoning may be restored using standard type-theoretic techniques, namely a form of existential types and subtyping. Despite its compositional nature, the type system is exact, in that the type of a term can
completely capture its semantics, thus demonstrating that precision and compositionality are compatible. We then address predictability of static checking for executable
refinement types by giving a type-checking algorithm for an important class of programs
with refinement predicates drawn from a decidable theory. Our algorithm relies crucially on the fact that the type of a term depends only the types of its subterms (which
fall into the decidable theory) and not their semantics (which will not, in general).

Chapter Outline
This chapter is organized as follows:
- Section 6.1 clarifies the non-compositionality of dependent type systems and its
consequences for executable refinement types.
- Section 6.2 Describes λComp
, a variant of λH augmented to support compositional
H
reasoning.
.
- Section 6.3 provides an operational semantics to λComp
H
type system and proves its compositionality.
- Section 6.5 presents the λComp
H
- Section 6.7 proves that the λComp
type system is exact: Whenever primitives have
H
suitably precise types, every term may be assigned a type that precisely describes
its semantics.
135

- Section 6.8 proves type soundness for λComp
, relying crucially on exactness.
H
- Section 6.6 extends the definition of extensional equivalence for λH to λComp
H
- Section 6.9 provides a type checking algorithm for the important special case of
programs whose refinement predicates fall in a decidable theory.
- Section 6.10 demonstrates the verification in λComp
of the classic example of binary
H
search trees.
- Section 6.11 discusses work with related goals and surveys other applications of
similar type-theoretic techniques.

6.1

Compositional Reasoning
During program development, compositional reasoning separates a program

into cognitively manageable pieces. During analysis and verification, compositional
reasoning limits the amount of information that must be stored and processed. Before
we embark upon a formal definition, consider the following illustration of how structural
type systems reason compositionally. In a well-typed program such as
let x : T = e in d
the type T provides an abstract specification of e that is concise and yet sufficiently
precise to verify that e and d interact properly. This verification process is compositional
in that the verification of d cannot rely on any properties of e other than those exposed
via its type T ; the analysis of the superterm relies only on the results of analysis of the
136

subterm(s). Consequently, replacing e with any equivalently-typed term does not affect
the typeability of the overall program. Formally, we can define compositionality of any
type system via its type rules.2

Definition 6.1 (Compositional Reasoning) A type rule [A] performs compositional
reasoning if for any derivation Γ ⊢ e : S used in the premise, as in
..
.
Γ⊢e : S

possibly other premises
[A]
′

Γ ⊢ C[e] : T
any derivation of Γ ⊢ d : S may be used in place, as in

..
.
Γ⊢d : S

possibly other premises
[A]
′

Γ ⊢ C[d] : T
Such a rule is said to be compositional. A type system containing only compositional
rules is also called compositional.

Seen through the lens of the Curry-Howard-Lambek correspondence [Curry
1934; Curry and Feys 1958; Howard 1980; Lambek and Scott 1986] this corresponds to
a sort of proof irrelevance: The particular proof of a proposition cannot affect how that
property is invoked in proofs of other propositions.
2

This assumes that a type rule does not inspect the derivation of its antecedents, but only their
content. This is, in fact, compositionality (proof irrelevance) of the metatheory.

137

6.1.1

Dependent Types Perform Non-compositional Reasoning
Executable refinement types provide a natural means to abstract a term’s

behavior to a greater or lesser degree (or, in the extreme, not abstract its behavior at
all). For example, the constant 1 may be given an infinite number of types, including
Int, Pos and the exact type {y : Int | y = 1}. These types are related via subtyping in
the expected manner:
{y : Int | y = 1}

<:

Pos

<:

Int

As demonstrated throughout this dissertation, executable refinement types cooperate
in a clean and expressive manner with dependent function types. For example, the
function add1 may also be given many types, including the simple type Int → Int,
the an exact type x : Int → {y : Int | y = x + 1}, or the intermediate specification
x : Int → {y : Int | y > x} describing only that add1 is increasing. Again, subtyping
appropriately relates these various specifications for add1:
x : Int → {y : Int | y = x + 1}
<:

x : Int → {y : Int | y > x}

<:

Int → Int

At first, it appears that a programmer has precise control, via type annotations, over the specifications that must be verified during type-checking. Unfortunately,
this is not the case at all! Standard dependent type systems, such as those of Cayenne
[Augustsson 1998], Epigram [McBride and McKinna 2004], Agda [Norell 2009] and Coq
[The Coq development team 2012], when given a program C[e], reason about the inter138

action between the term e and its context C using both:
- compositional reasoning based on the type of e; and
- non-compositional reasoning based on the syntax of e (hence its behavior).
The non-compositional nature of dependent type systems originates from the following
standard type rule for dependent function application. (For simplicity, we elide the
typing environment).
f : (x : S → T )

e : S

f e : [x 7→ e] T
The inferred type [x 7→ e] T (denoting T with x replaced by e) of f e includes the term
e itself, and not just its type S.
To highlight the consequences of the non-compositional nature of this rule,
suppose that e is an arbitrary term of type Pos, and that f is the exactly-typed identity
function on integers:
e : Pos
f

: (x : Int → {y : Int | y = x})

Using compositional reasoning, we should only be able to infer that f returns its argument, which is of type Pos, and so f e has type Pos.
Under the above type rule, however, the application f e has the more precise
(indeed, exact) type stating that f e returns exactly the value of e.
f e : {y : Int | y = e}

139

Thus, the abstraction Pos of e has now been circumvented. To explicitly see the counterexample to compositionality, consider the corresponding derivation
e : Pos

f : (x : Int → {y : Int | y = x})
f e : {y : Int | y = e}

and consider replacing the derivation of e : Pos with another for d : Pos, yielding the
erroneous derivation
d : Pos

f : (x : Int → {y : Int | y = x})

Invalid

f e : {y : Int | y = e}
Non-compositional reasoning causes multiple difficulties. First, an arbitrary
program term e has leaked into a refinement predicate. Since e could be a large term,
any type-checking procedure now needs to deal with extremely large types, probably
much larger than any of the types present in the source program. Since the types in
source programs are abstract specifications carefully chosen by the programmer, it is
not clear that these new, larger, types generated during type checking are at all the
right abstractions for performing lightweight verification.
Second, even if the refinement predicates in source programs are carefully chosen from a decidable theory, the fact that program terms leak into refinement predicates
during type checking means that static type checking still requires deciding implications
between arbitrary program terms, which is of course undecidable.

140

6.1.2

Compositional Reasoning for Executable Refinement Types
Given the universal and precise abstractions provided by dependent and re-

finement types, and the benefits of compositional reasoning, this chapter explores an
alternative strategy for static checking of executable refinement types with dependent
function types. The key idea underlying our approach is illustrated by the following
rule for function application:
f : (x : T → U )

e:S

S <: T

f e : (∃x : S. U )
The application f e must return a value of type U , where x is bound appropriately, but
what is the appropriate binding for x?
Compositionality dictates that all we are permitted to know about e (and
hence about x) is that it has type S. Thus, the inferred type of the application f e is
the existential type
∃x : S. U
Roughly speaking, this type denotes the union of all types of the form [x 7→ d] U , where
d ranges over terms of type S.3
For the application f e considered earlier, this rule infers the existential type
f e : (∃x : Pos. {y : Int | y = x})
3

Existential types are also written as dependent pairs Σx : S.U , with two equally valid informal
readings. The first is that the type is the (disjoint) union of [x 7→ d] U for each d of type S. The second
is as the type of pairs of a term d of type S along with a corresponding type [x 7→ d] U . The latter is
more prevalent in constructive logic, as it emphasizes the reification of the existential witness.

141

that is (informally) equivalent to the type Pos, and indeed is a subtype of Pos.
(∃x : Pos. {y : Int | y = x}) <: Pos
Thus, the type rule achieves the desired goal of compositional reasoning: f e has type
Pos simply because the argument expression has type Pos. All we can know about the
argument is its type. Furthermore, note that the expression e itself no longer leaks into
the refinement predicate, which provides benefits in terms of smaller inferred types and
facilitates decidable type checking.

6.1.3

Expressiveness and Exactness
Given that this type system is strictly limited to compositional reasoning, and

cannot, for example, include the traditional rule for dependent function application, a
key question that arises is to what degree the requirement for compositional reasoning
limits the expressiveness of the type system. Certainly, the refinement type language
itself is sufficiently expressive to describe exact types, such as the type
{x : Int | x = 1}
for the constant 1. More interestingly, we show that, if every constant in the language
is assigned an exact type, then for any well-typed program e, our type system will infer
an exact type that exactly captures the run-time semantics of e. This result holds even
if e itself includes coarse type specifications such as Int. This curious result occurs
because all annotations in a program are preconditions to functions, which will be made
as precise as allowed by the type of the argument to the function. Thus it is the types of
142

opaque constants that drives the precision of type checking. This is discussed at length
in Section 6.7.
Assigning exact types to primitives is straightforward, via selfification. For
example, the fixed point primitive fixT is typically given the inexact type (T → T ) → T .
For the case where T = x : S → B, an exact type for fixT is:
f : (T → T ) → x : S → {y : B | y = fixT f x}
In practice, of course, primitives are typically assigned somewhat coarser types.
Nevertheless, this completeness result indicates that the precision of the type system is
entirely parameterized by the types of these primitives; the type system itself neither
requires nor performs any additional abstraction.
Put differently, the precision of any compositional analysis is naturally driven
by the precision of the abstractions that it composes, and careful choice of abstractions is
crucial for achieving precise, scalable analyses. The type system is entirely configurable
in this regard; it does not perform any abstraction itself, and instead simply propagates
the abstractions inherent in the types of constants and recursive functions, with the
result that the precision of the analysis is largely under the control of the programmer.

6.1.4

Decidable Type Checking
A key benefit of compositional type checking is that program terms never

leak into refinement predicates, so the language of refinement predicates can be cleanly
separated from that of program terms. In particular, if refinement predicates in source

143

programs are drawn from the decidable theory of linear inequalities, then all proof
obligations generated during type checking also fall within a decidable theory, and so
type checking is decidable. We use this result to develop a decidable type checking
algorithm for an important and easily-identifiable subset of our target language.

6.2

The Language λComp
H
We present our ideas via the language λComp
, a variant of λH augmented with
H

existential types to support compositionality and also new case discrimination constructs
(also called “pattern matching”) to enable more interesting and challenging examples.
Figure 6.1 presents the syntax of λComp
, highlighting forms that are not present in λH .
H
As before, primitives are presumed to include basic operations of the language, such as boolean and arithmetic operations (+, ×, not, ⇔, etc.), and a fixed
point operator fixT for each type T . However, in λComp
primitives are separated into
H
syntactic classes of primitive functions k and data constructors c. Constructors include
the boolean constants (true, false), integer constants (1, 2, 6, −17, etc), and any
constructors for algebraic data types such as the list constructors nil and cons.
A constructor is eliminated by using the case discrimination expression
case e of c Z⇒ f
where c Z⇒ f is a sequence of cases to whose constructor c to which e is compared, and
the appropriate one chosen. For simplicity, we assume all case discrimination is disjoint
and complete, introducing nonterminating alternatives as necessary. Thus the ordering
144

Figure 6.1: Syntax of λComp
H

d, e, f, g, p, q ::=
x
k
λx : S. e
f e
c
case e of c Z⇒ f

Expressions:
variable
primitive function
abstraction
application
constructor
case discrimination

v ::=

Values: (⊂ Terms)
primitive function
abstraction
constructor application

k
λx : S. e
ce
S, T, U ::=
{x : B | p}
x:S → T
∃x : S. T

Types:
refinement type
dependent function type
existential type
ce

def

=

x:S → T

def

c Z⇒ f

def

∃x : S. T

=

def

=

=

c e1 · · · en

x1 : S 1 → · · · → xn : S n → T
∃x1 : S1 . · · · ∃xn : Sn . T

c1 Z⇒ f1 , · · · , cn Z⇒ fn

145

of the cases is irrelevant and they can be freely reordered.
The λComp
type language includes existential types of the form
H
∃x : S. T
which classify terms of type T where x represents some unknown term of type S. Since
λComp
lacks polymorphism, in addition to Bool and Int we also assume a base type
H
of lists of integers IntList to develop our examples; adding other monomorphic data
types is straightforward.

6.3

Operational Semantics of λComp
H
Figure 6.2 presents the operational semantics of λComp
. The notable enhanceH

ment is case discrimination, but the complete set of redex reduction rules, contextual
evaluation rules, and evaluation contexts is presented for ease of reference.
[E-case]: The case discrimination expression case c e of c Z⇒ f reduces by selecting the

appropriate case c Z⇒ f and applying f to all the arguments of c. After all of
e, the full term c e is also passed to f ; this is a formality (an important one)
that enables path sensitivity, discussed later in Section 6.5.

6.4

types to sets of ST LC terms
Denotation of λComp
H
The definition of implication for λComp
is unchanged from that of λH (Figure 3.9 on page 39)
H

but the quantification over closing substitutions depends on a denotational interpreta146

Figure 6.2: Redex Evaluation for λComp
H

Redex Evaluation

e1 y e2

(λx : T. e1 ) e2 y [x 7→ e2 ] e1
k e y δ(k, e)
case c e of c Z⇒ f, · · · y f e (c e)

[E-β]
[E-δ]
[E-Case]

Contextual Evaluation
C[d]
D[d]

d
C[e] if d y e
D[e] if d y e

e

[E-Compat]
[E-TyCompat]

C, D

Evaluation Contexts

C ::= • | C e | e C | λx : S. C | λx : D. e | case C of c Z⇒ f | case e of c Z⇒ C, · · ·
D ::= x : D → T | x : S → D | {x : B | C} | ∃x : D. T | ∃x : S. D

147

Figure 6.3: Shape of λComp
types and terms
H
⌊{x : B | p}⌋

def

=

B

⌊x : S → T ⌋

def

⌊S⌋ → ⌊T ⌋

⌊∃x : S. T ⌋

=

=

def

⌊T ⌋

tion of all λComp
types as sets of terms. The denotational semantics of Section 3.3
H
extends naturally to the existential types of λComp
, mirroring the denotation of function
H
types.
The extended definition of the shape of a λComp
types is given in Figure 6.3.
H
The shape of an existential type ∃x : S. T is simply the shape of the underlying type T ,
since bound variables are irrelevant in ST LC. The shape of a term is still simply the
same term where all types have been replaced by their shape.
Figure 6.4 shows the interpretation of λComp
types as sets of terms. The inH
terpretations of refined basic types and function types are unchanged from λH . An
existential types ∃x : S. T is interpreted as the set of ST LC terms e of simple type ⌊T ⌋
for which there exists some existential witness d in [[S]] such that e ∈ [[[x 7→ e] T ]].

6.5

The λComp
Type System
H
The judgments of the λComp
type system are those of λH (Section 3.4 on page 32)
H

but their definitions are considerably more involved due to the enriched type and term
languages.

148

Figure 6.4: Denotation from λComp
types to sets STLC terms
H

[[{x : B | p}]]

def

=

{e | ⊢stlc ⌊e⌋ : B ∧ (e

[[x : S → T ]]

def

=

{f | ⊢stlc ⌊f ⌋ : ⌊S⌋ → ⌊T ⌋ ∧ ∀e ∈ [[S]], f e ∈ [[[x 7→ e] T ]]}

[[∃x : S. T ]]

def

{e | ⊢stlc ⌊e⌋ : ∧ ∃d ∈ [[S]], e ∈ [[[x 7→ d] T ]]}

=

∗

k implies [x 7→ k] p

∗

true)}

- The typing judgment Γ ⊢ e : T states that term e may be assigned type T under
the assumptions in environment Γ. (Figure 6.5)
- The type well-formedness judgment Γ ⊢ T ensures that T is a well-formed type
under the environment Γ. (Figure 6.7)
- The subtyping judgment Γ ⊢ S <: T retains the standard reading: Any term of
type S may also be assigned type T . (Figure 6.8)

6.5.1

Typing for λComp
H
The typing judgment
Γ⊢e : T

for λComp
is enhanced from that of λH to support existentially quantified variables ocH
curring in types, and case discrimination. These two constructs interact in important
ways to provide path-sensitivity, without which precise types for constructors do not
provide information during case discrimination.

149

Figure 6.5: Type Rules for λComp
H

Γ⊢e : T

Typing

[T-Const]

[T-Prim]

Γ ⊢ c : ty(c)
Γ⊢S

Γ ⊢ k : ty(k)

Γ, x : S ⊢ e : T

[T-Fun]

[T-Var]

Γ ⊢ x : self (T, x)

Γ⊢e : S

Γ ⊢ (λx : S. e) : (x : S → T )

(x : T ) ∈ Γ

Γ⊢f : S→T

[T-App]

Γ⊢f e : T

Γ ⊢ e : ∃z : T . {y : B | q}
For each c Z⇒ f :
ty(c) = x : S → {y : B | p}

Γ ⊢ f : (x : S → y : ∃z : T . {B | p ∧ q} → U )

Γ ⊢ case e of c Z⇒ f : U

150

[T-Case]

[T-Const] and [T-Prim]: Constructors c and primitive functions f are assigned types

according to ty.
[T-Var]: Variables are assigned types yielded by self , defined in Figure 6.6, a key fea-

ture discussed in Section 6.5.5.
[T-Fun]: This standard rule for λ-abstractions is unchanged.

[T-App]: Typing of function a application (f e) is interesting, since it appears not to sup-

port dependent function types at all. We discuss this in detail in Section 6.5.4.
[T-Case]: This rule for a case discrimination expression case e of c Z⇒ f is rather

complex, because it uses only the information contained in the type of c to
provide a measure of path-sensitivity, as the type of if did in λH .
Each constructor c must necessarily return some refinement {x : B | p} of the
same base type B (for example, nil and cons both return refinements of
IntList). The type of the discriminand e must also be some refinement of
B, but may have existentially quantified variables z : T , thus the type of e
must have the form ∃z : T . {y : B | q}.
For each branch c Z⇒ f , where the particular constructor c has a type of the
form x : S → {y : B | p}, if this branch is chosen, then we have “learned” that e
was constructed with constructor c, so the predicate p holds for e. So we conjoin
p to the type of e, yielding ∃z : T . {y : B | p ∧ q}. The handling function f must
then take parameters x : S corresponding to the arguments to the constructor

151

as well as the constructed term itself. Though x may not occur in U , their
occurrences in p ∧ q capture relationships between the variables so that the
body of f may assume anything already known about e as well as anything
learned by the fact that the constructor used was c. In this way, the precision
of the path-sensitivity of [T-Case] is determined by the types of constructors.
For example, cons may be assigned a range of types, including:
cons : Int → IntList → IntList
cons : Int → x : IntList →
{y : IntList | length(y) = length(x) + 1}
cons : n : Int → x : IntList →
{y : IntList | y = cons n x}
In particular, if the constructor c has the precise return type {y : B | y = c x},
then the new binding implies [y 7→ c x] e′ . However, if c has a more coarse type,
then the naı̈ve approach of adding an assumption such as e = c x is reasoning
noncompositionally using the exact semantics of c, ignoring its type.

6.5.2

Type well-formedness for λComp
H
The type well-formedness judgment
Γ⊢T

for λComp
is that of λH with the addition of well-formedness for existential types. The
H
rule [WT-Exists] for an existential type ∃x : S. T ensures that T is well formed in the
152

Figure 6.6: Definition of self

self ({x : B | p}, e) = {x : B | p ∧ (x =B e)}
self (x : S → T, e) = x : S → self (T, e x)
self (∃x : S. T, e) = ∃x : S. self (T, e)

extended environment Γ, x : S.

6.5.3

Subtyping for λComp
H
Subtyping between existential types (Figure 6.8) is derived from the logical

interpretation of subtyping as “implication” between propositions.4

[S-Arrow] and [S-Base]: Subtyping between function types and base types is standard.

[S-Bind]: This rule to introduce an existential on the left corresponds to the tautology

of first-order logic:
(∀x ∈ A. P (x) ⇒ Q) =⇒ (∃x ∈ A. P (x)) ⇒ Q where x ∈
/ f v(C)
[S-Witness]: To introduce an existential on the right requires a witness term e of type

T for the variable x. If S is a subtype of [x 7→ e] U , then we can disregard the
identity of the witness and retain only its specification to conclude that T is a
subtype of ∃x : T. U .
4

A similar inspiration may underlie Dreyer et al. [2003] where subtyping rules like our own are briefly
mentioned, but ultimately not used, as their work had different goals.

153

Figure 6.7: Type well-formedness for for λComp
H

⊢T

Well-formed Types
Γ⊢S

Γ, x : S ⊢ T

Γ, x : B ⊢ p : Bool

[WT-Arrow]

Γ ⊢ x:S → T

[WT-Base]

Γ ⊢ {x : B | p}
Γ⊢S

Γ, x : S ⊢ T

[WT-Exists]

Γ ⊢ ∃x : S. T

Corresponding to our intuition, a combination of [S-Witness] and [S-Bind] shows that
the following reassuringly covariant rule is admissible:5
Γ ⊢ S1 <: T1

Γ, x : S1 ⊢ S2 <: T2

Γ ⊢ ∃x : S1 . S2 <: ∃x : T1 . T2

6.5.4

Non-dependent Function Application
As mentioned above, the typing rule for a function application (f e) is ex-

tremely simple, and does not refer to dependent types at all. Instead, the combination
of existential types and subtyping provides enough power in other areas of the type sys5

The full derivation is not too complex, eliding some the environment prefix and some weakening:
x : S1 ⊢ x : S1

⊢ S1 <: T1

x : S1 ⊢ x : T 1

[T-Sub]

x : S1 ⊢ S2 <: ∃x : T1 . T2

x : S1 ⊢ S2 <: T2

⊢ ∃x : S1 . S2 <: ∃x : T1 . T2

154

[T-Witness]
[T-Bind]

Figure 6.8: Subtyping for λComp
H

Γ ⊢ S <: T

Subtyping
Γ ⊢ T1 <: S1

Γ, x : T1 ⊢ S2 <: T2

[S-Arrow]

Γ ⊢ (x : S1 → S2 ) <: (x : T1 → T2 )
Γ, x : B ⊢ p ⇒ q

[S-Base]

Γ ⊢ {x : B | p} <: {x : B | q}
Γ⊢e : T

Γ ⊢ S <: [x 7→ e] U

[S-Witness]

Γ ⊢ S <: ∃x : T. U
Γ, x : S ⊢ T <: U

x 6∈ F V (U )

Γ ⊢ ∃x : S. T <: U

155

[S-Bind]

tem that this straightforward rule is sufficiently expressive. Suppose f has a dependent
function type x : T → U and the argument e has type S where S <: T . Then, we use
subtyping to specialize the function type as follows:
(x : T → U )

<:

(x : S → U )

<:

(x : S → ∃x : S. U )

≡

(S → ∃x : S. U )

since x ∈
/ f v(S)

The new function type S → ∃x : S. U is only applicable to terms of type S. More
interestingly, its existential return type now internalizes the assumption that x will be
of type S, so we have a non-dependent function type that precisely characterizes the
behavior of the function f on arguments of type S, and which does not refer to the
exact semantics of the argument e.6
Based on the above discussion, the following rule (mentioned in the introduction) is admissible:
Γ ⊢ f : x:T → U

Γ⊢e : S

Γ ⊢ S <: T

Γ ⊢ f e : (∃x : S. U )
6

Non-dependent type rules for function application in a dependent type system are also used by
Harper and Lillibridge [1994] and Dreyer et al. [2003] in ML module systems where computational effects make the standard substitution-based rule unsound. In the latter, a specialization of the technique
we present allows the power of dependent function application when the argument is effect-free.

156

6.5.5

Self Types
To motivate our non-standard rule for variable references, consider the expres-

sion x − x, where x has type Int and subtraction has an exact type:
− : (w : Int → y : Int → {z : Int | z = w − y})
Under the standard rule, whereby a reference to x simply has type Int, the
type of x − x is given by the judgment
x : Int ⊢ (x − x) : (∃w : Int. ∃y : Int. {z : Int | z = w − y})
which could be any integer. This is needlessly coarse, since the fact that x − x = 0
does not depend on the particular value of x. Informally, it would appear that even via
compositional reasoning we should be able to give an exact type {z : Int | z = 0}. What
happened? The type system has lost a key notion of identity, that the two variable
references in x − x refer to the same value.
To maintain the necessary information, selfification is used to assign to the
variable reference x the type self (Int, x), or {y : Int | y = x}, which captures the identity of x, and yields the conclusion:
x : Int ⊢ (x − x) : ∃w : {Int | w = x}.
∃y : {Int | y = x}.
{z : Int | z = w − y}
Since (w = x) ∧ (y = x) ∧ (z = w − y) implies z = 0, this is a subtype of {z : Int | z = 0},
as intended. In addition to this example, self types are also crucial for achieving path157

sensitivity in case discrimination, which requires a notion of identity for the matched
expression.
Selfification interacts in delicate ways with our goal of compositional reasoning.
For example, Ou et al. [2004], in their declarative system, give a general selfification rule
Γ⊢e : T

[T-Self]

Γ ⊢ e : self (T, e)
But this rule is non-compositional as they cannot replace the subderivation Γ ⊢ e : T
with another Γ ⊢ e′ : T . In their algorithmic presentation, they give self types only to
constants and variables. A key point not highlighted, however, is that giving self types
to variables is compositional: Since there are no subderivations, compositionality is vacuous. This is not simply a syntactic trick, but the syntactic manifestation of the essence
of compositional reasoning: We may know nothing about a variable but its type and we
may remember from which variable we “fetch” a value. This insight is instrumental to
reconciling compositional reasoning with the precision of selfification. The restriction of
selfification to variables enables us to prove the following key compositionality theorem
for our system:
Theorem 6.2 (Compositionality) The λComp
type system is compositional.
H
Proof: This is directly observable by inspection of the typing rules. 

6.6

Extensional equivalence for λComp
H
158

Figure 6.9 presents the full definition of extensional equivalence for λComp
. It is
H
that of λH from Figure 3.10 on page 49 augmented with a definition for equivalence at
existential type: Two terms e1 and e2 of existential type ∃x : S. T are equivalent if there
exist equivalent witness terms d1 and d2 of type S under which e1 and e2 are equivalent
at type [x 7→ d1 ] T f [x 7→ d2 ] T .
As for λH , extensional equivalence in λComp
implies contextual equivalence; the
H
required proofs are straightforward extensions of those in Section 3.6.

6.7

Type System is Exact
The λComp
H
The requirement for compositional reasoning restricts the kinds of inference

rules the λComp
type system can include. For example, we have seen that it forbids both
H
the standard rule for dependent function application and the selfification of arbitrary
terms. The type system described above carefully adds the most important capabilities
while maintaining compositional reasoning. We now address the important question
of how much precision or expressive power our type system has lost because of its
restriction to compositional type rules.
In fact, under moderate assumptions, our type system is exact: it can assign
to each program term a type that completely captures the semantics of that term.
Informally, an exact type is any type that captures a terms semantics and an exact type
system is one that can capture any term’s semantics. The intent of the function self is
to assign exact types (to variables, in this case) so that quantifications over self types

159

Figure 6.9: Extensional Equivalence Under Reduction for λComp
H

Γ ⊢ e1 ∼ e2 : T

(defined by induction on ⌊T ⌋)

Γ ⊢ e1 ∼ e2 : {x : B | p}

def

⇔ Γ ⊢ e1 ∈ {x : B | p}
Γ ⊢ e2 ∈ {x : B | p}
∀σ such that ⊢ σ : Γ, (σ(e1 )

Γ ⊢ f1 ∼ f2 : (x : S → T )

∗

k) ⇔ (σ(e2 )

def

⇔ ∀e1 , e2 such that Γ ⊢ e1 ∼ e2 : S,
Γ ⊢ f1 e1 ∼ f2 e2 : [x 7→ e1 ] T f [x 7→ e2 ] T

Γ ⊢ e1 ∼ e2 : (∃x : S. T )

def

⇔ ∃d1 , d2 such that Γ ⊢ d1 ∼ d2 : S,
Γ ⊢ e1 ∼ e2 : [x 7→ d1 ] T f [x 7→ d2 ] T

SfT

{x : B | p} f {x : B | q}

def

=

{x : B | p ∧ q}

(x : S1 → S2 ) f (x : T1 → T2 )

def

=

x : (S1 f T1 ) → (S2 f T2 )

def

∃x : (S1 f T1 ). (S2 f T2 )

(∃x : S1 . S2 ) f (∃x : T1 . T2 )

=

160

∗

k)

are equivalent to quantifications over a single value. By defining exactness clearly we
can prove this is the case.
Definition 6.3 (Exactness) If Γ ⊢ e : T then the type T is exact for term e in
environment Γ if for any other term d the judgment Γ ⊢ d : T implies d ≡ e. A type
system is exact when it can assign an exact type to every well-typed term.
Lemma 6.4 (Exactness of self type denotation) If d ∈ [[self (T, e)]] then d ≡ e.
Proof: This is equivalent to showing ∅ ⊢ d ∼ e : self (T, e), which is proved by
induction on ⌊T ⌋. 
Since self types have the desired denotation, the following chain of corollaries
establishes that all of our judgments interact with self types as expected.
Corollary 6.5
1. If Γ ⊢ d : self (T, e) then d ≡ e.
2. If ⊢ σ : Γ, x : self (T, e), Γ′ then σ(x) ≡ σ(e).
3. Γ, x : self (T, e), Γ′ ⊢ p ⇒ [x 7→ e] p
4. Γ, x : self (T, e), Γ′ ⊢ T <: [x 7→ e] T
5. Γ, Γ′ ⊢ ∃x : self (S, e). T <: [x 7→ e] T
requires that each primitive function k and
Establishing exactness for λComp
H
constructor c) be given an exact type. For the duration of this section, we assume that
ty has this property:
161

Requirement 6.6 (Exact Types for Primitives) In the exact variant of λComp
each
H
primitive f and constructor c is given an exact type by ty.

The exact information provided by types of constants and the self types of
variables is propagated losslessly by the λComp
type system.
H
Theorem 6.7 (The λComp
type system is exact) Supposing Requirement 6.6 holds,
H
if Γ ⊢ e : T then also Γ ⊢ e : T ′ where T ′ is an exact type for e and Γ.
Proof: This is proved by demonstrating that Γ ⊢ e : self (T, e) using Corollary 6.5,
by induction on the typing derivation Γ ⊢ e : T . 
Theorem 6.7 seems very strong: for an arbitrary term e of type T (where e’s
subterms may include coarse types), we can assign e the type self (T, e) that exactly
captures the semantics of e, all via compositional reasoning. In exploring the conflict
upon which this paper is premised, we have developed a compositional type system
as powerful as one based on substitution, but without the violation of abstraction.7
Rather, our type system insists that whatever precision is desired must be explicitly
expressed via types.
As an illustration of Theorem 6.7, consider the type of the fixed point primitive
fixT used to express recursion, which is typically given the inexact type (T → T ) → T .
We can make this type exact via selfification. For example, if T = x : U → B, then an
7
We do not claim to have improved upon dependent types in their general use as a foundation for
mathematics, but only for the particular use of verifying a program specified by executable refinement
types.

162

exact type for fixT is self ( (T → T ) → T, fixT ) which is
f : (T → T ) → x : U → {y : B | y = fixT f x}
For a more interesting example, consider Euclid’s algorithm for computing
greatest common divisors, where we use if expressions to abbreviate case discrimination
on type Bool:
gcd

=

fixT (λg : T. λa : Int. λb : Int. e)

where

T

=

Int → Int → Int

and

e

=

if b = 0 then a
else if a > b then g (a − b) b
else g a (b − a)

If fixT has the exact type self ( (T → T ) → T, fixT ), then the type systems infers the
following exact type for gcd:
∃h : (g : T → a : Int → b : Int → {r : Int | r = e}).
a : Int → b : Int → {r : Int | r = fixT h a b}
Note that this type includes within refinement predicates both fixed point computations
and also the body e of gcd. Thus, the type system is essentially translating the entire
computation into the predicate language. While this illustrates the expressiveness of
the type system, reasoning about fixed point computations is notoriously difficult for
automated theorem provers.
However, primitive functions in this calculus represent terms whose semantics
are unavailable; the predicates that occur in inferred types are determined by the specmodel built-in constructs of a language
ifications of primitives. The primitives of λComp
H
163

implementation but also names from external modules8 , which will typically be assigned
approximate types. The import of the theorem is not that all abstractions can be broken
even without dependent types, but that the type system neither requires nor performs
any abstraction itself.
Returning to the example, a more practical approach is to use the simple type
(T → T ) → T for fixT , and for the programmer to provide an appropriately-precise
specification T for the recursive function gcd, corresponding to the imperative idea of
a loop invariant.9 The structural specification T = Int → Int → Int is rather coarse;
a more precise (but not exact) specification is:
T

= a : Int → b : Int → {r : Int | a mod r = 0 ∧ b mod r = 0}

The type system can then verify this specification for gcd, with simple proof obligations
such as “if b mod r = 0 and (a − b) mod r = 0 then a mod r = 0.”

6.8

Type Soundness for λComp
H
This chapter proves type soundness for λComp
not by the usual means, but by
H

proving type soundness for the exact variant – where all primitives have exact types –
which ensures that any more approximate variant is also safe.
Given exactness, an existentially quantified variable with a singleton type is
equivalent to having simply performed a substitution (it is essentially an explicit sub8

In a well-designed language, built-in primitives and imported symbols are indistinguishable
The wide variety of methods for inferring loop invariants may be applied to avoid this annotation
burden, as mentioned in Chapter 5. The most closely related progress on this variant of the problem is
discussed in Chapter 8.
9

164

stitution [Abadi et al. 1990]). This intuition is formalized in Corollary 6.5 and the
following complementary lemma:
Lemma 6.8 For any expression e and type T , if Γ ⊢ e : S and Γ, x : S ⊢ T then
Γ ⊢ [x 7→ e] T <: ∃x : self (S, e). T
Proof: By induction on the size of T , utilizing Theorem 6.7 to provide the
exact witness e via [S-Witness]. 
From exactness, the standard substitution, preservation, and progress lemmas
apply unchanged. The proofs follow the same structure as those for λH , so we omit the
details and simply state type safety. If a term is well-typed in any variant of our system,
then it is certainly well-typed in the exact variant. Hence, a well-typed term cannot
“get stuck” even in those imprecise variants where the substitution lemma cannot be
proved.

Theorem 6.9 If ∅ ⊢ e : T , then e either reduces to a value or is nonterminating.
Proof: Map the derivation of Γ ⊢ e : T into the exact variant by induction
over the derivation, then apply progress and preservation. 
There may be another syntactic approach to type soundness that applies in
these systems, but it is informative to consider such systems as coarsenings of the exact
variant.

165

6.9

A Type-checking Algorithm for λComp
H
We now investigate how to provide decidable compositional type checking for

an interesting subset of λComp
programs. Type checking for λComp
is undecidable, deH
H
spite the recovery of compositional reasoning, because implication remains undecidable.
However, arbitrary terms are no longer injected into types. So by restricting refinement predicates to a decidable theory we can now provide a decidable, compositional,
dependent type system. Note that this decidability result crucially relies on compositional reasoning: in a traditional dependent type system, even when all annotations fall
within a decidable theory, substitutions made during type checking may result in proof
obligations outside of that theory.
Figure 6.10 defines the type language for which we provide a type-checking
algorithm. We leave the exact language of atomic decidable predicates l abstract, so
the approach is applicable to any decidable predicate language. During type-checking,
atomic predicates will be combined with conjunction and case-splitting on variables, so
those are also included in the predicate sublanguage of terms, denoted by metavariables
p or q, which are now no longer used for arbitrary terms.
We distinguish two sublanguages of types. The first, ranged over by metavariable Q and R, does not include existential quantifiers and may appear in source programs. Augmented types ranged over by E and F generated during type checking may
contain existential quantification, but only in positive positions. Thus Q ⊆ E ⊆ T .
Only augmented types E are bound in the environment during type checking, so we use

166

Figure 6.10: Syntax for Algorithmic Typing of λComp
H

p ::=

Predicates:
atomic predicate
conjunction
case discrimination

l
p∧p
case w of c x Z⇒ p
d, e, f, g, p, q ::=
x
k
λx : R. e
f e
c
case w of c x Z⇒ e

Terms:
variable
primitive function
abstraction
application
constructor
case discrimination

v ::=

Values: (⊂ Terms)
primitive function
abstraction
constructor application

k
λx : S. e
ce
Q, R ::=
{x : B | p}
x:Q → R

Restricted Types:
refinement type
restricted function type

E, F ::=
{x : B | p}
x:R → E
∃x : E. F

Types with Covariant Existentials:
refinement type
augmented function type
augmented existential type

∆ ::=

Algorithmic Typing Environments:
empty environment
environment extension

∅
∆, x : E

167

Figure 6.11: Shorthand for pushing case expressions inside types

case w of c x Z⇒ e

def

=

case w of c Z⇒ λx : R. λy : E. e

where ty(c) = x : R → {y : B | p}
and

E has the form ∃z : F . {y : B | p ∧ q}
(z : F and q will be clear from context)
def

case w of c x Z⇒ ∃zc : Ec . Fc = ∃xc : Ec . case w of c Z⇒ Fc
where xc : Ec contains all of the bindings for each c, (possibly empty)

case w of c x Z⇒ {z : B | q} = {y : B | case w of c x Z⇒ q}
where ty(c) = xc : Rc → {z : B | p}

168

a new metavariable ∆ to range over these E-environments.
The type-checking algorithm is presented as a set of syntax-directed inference
rules. The judgments are analogous to those of the type system.
- The algorithmic typing judgment ∆  e : E reads that e is assigned type E,
which may contain covariant existentials.
- The algorithmic type well-formedness judgment ∆  R is only invoked for programmerprovided restricted types R, and ensures that R is well-formed in environment Γ.
- The algorithmic subtyping judgment Γ  E <: R checks that the type E is a
subtype of the restricted type R. Though E may contain covariant existentials,
they are not problematic for algorithmic checking. The upper bound R will never
contain existentials.

6.9.1

Algorithmic typing for λComp
H
Our algorithmic type checking judgment
∆e : E

is shown in Figure 6.12. It is essentially derived from the typing judgment Γ ⊢ e : T .
As usual for syntax-directed type checking, we remove the subsumption rule and instead
inline subtyping where needed in function applications to ensure the argument is of the
appropriate type, and in case discrimination to merge the types of all the branches.

[A-Const] and [A-Prim]: Constructors and primitive functions are given types by ty.

169

[A-Var]: Variables are still given types via self . Adding uninterpreted function sym-

bols to the decidable predicate language retains decidability. Other approximations may be considered for efficiency; because of our nonstandard approach
to soundness, any approximation is sound.
[A-App]: For an application f e, this rule introspects the type of f to discover a func-

tional type within existential quantifiers, of the form ∃z : Ez . x : R → E1 (performing the work of as many applications of [S-Bind] as necessary). Then the
type E2 of the argument e is checked for compatibility with R. The result of
the application is assigned type ∃z : Ez . ∃x : E2 . E1
[A-Case] In typing a case discrimination expression, we lift the case syntax to types to

concisely express the resulting type. Intuitively, case e of c x Z⇒ E is equivalent
to whichever case succeeds. Figure 6.12 includes the exact definition. To avoid
arbitrary expressions appearing in refinement predicates, we require that the
inspected subexpression in a case construct be a variable. This requirement
can be satisfied by rewriting the more general form case e of c x Z⇒ f into
the semantically equivalent (λy : S. case y of c x Z⇒ f ) e. This rewriting step,
which is a burden imposed on the input program, introduces a type R for the
variable y, and so ensures that any expression used in case discrimination will
have a specification that falls in the restricted type language.10
10

A similar restriction is present in Rondon et al. [2008], where only variables may be inserted into
predicate templates, so introducing bindings of intermediate results to variables is crucial to achieving
precision while maintaining decidability.

170

Figure 6.12: Algorithmic Typing for λComp
H

Algorithmic Typing

∆e : E

[A-Const]

[A-Prim]

∆  c : ty(c)

(x : A) ∈ ∆

∆  k : ty(k)

∆R

∆, x : R  e : E

[A-Var-Base]

[A-Fun]

∆  (λx : R. e) : (x : R → E)

∆  x : self (E, x)

∆  e1 : ∃z : Ez . x : R → E1

∆  e2 : E2

∆, z : Ez  E2 <: R
[A-App]

∆  e1 e2 : ∃z : Ez . ∃x : E2 . E1

∆  w : ∃z : E. {y : B | q}
For each c x Z⇒ d :
ty(c) = x : Q → {y : B | p}

∆, x : Q, y : ∃z : E. {B | q ∧ p}  d : F

∆  (case w of c x Z⇒ d) : (case w of c x Z⇒ F )

171

[A-Case]

Figure 6.13: Algorithmic Type Well-formedness for λComp
H

∆R

Algorithmic Well-formed Types
∆  R1

∆, x : R1  R2

∆, x : B  p : Bool
[AT-Base]

[AT-Arrow]

∆  x : R 1 → R2

6.9.2

∆  {x : B | p}

Algorithmic type-wellformedness for λComp
H
Figure 6.14 presents the rules defining the algorithmic type well-formedness

judgment
ΓR
which checks if programmer-supplied types R are well-formed. It is easy to check wellformedness of types with existential quantification, but for illustrative purposes the rules
are deliberately limited to those cases that actually may occur during type checking.

6.9.3

Algorithmic subtyping for λComp
H
Like typing, algorithmic type checking relies on subtyping, but interestingly

only for the asymmetric form
∆  E <: R

172

Figure 6.14: Algorithmic Subtyping for λComp
H

Algorithmic Subtyping
∆  R2 <: R1

∆  E <: R
∆, x : R2  E1 <: R3
[AS-Arrow]

∆  (x : R1 → E1 ) <: (x : R2 → R3 )
∆, x : B ⊢ q ⇒ p

∆, x : E1  E2 <: R3
[AS-Base]

∆  {x : B | q} <: {x : B | p}

[AS-Bind]

∆  ∃x : E1 . E2 <: R3

which checks subtyping between an inferred type E, with covariant existential quantifications, and a programmer-specified, restricted type R.

[AS-Bind]: Since existential types never appear on the right hand side, we (fortunately!)

never need to “guess” witnesses for existentials. Existentials on the left are unproblematic, because the existential quantification in a negative context transforms into a universal quantification in a positive context, resulting in just
another binding in the typing environment. We could add the covariant rule
from the end of Section 6.1 to allow some existential quantification on the right
side of subtyping, but our syntactic conditions make that unnecessary.

173

Algorithmic typeability is not closed under evaluation; instead soundness is
guaranteed by the correspondence between algorithmic typing and the full type system.

Theorem 6.10 (Soundness of Algorithmic Typing)
If ∆  e : E then ∆ ⊢ e : E
Proof: Proceed by induction on the derivation of ∆  e : E. 
While the algorithm is not complete with respect to the full type system, it is
complete for the class of sublanguages we have described.

Theorem 6.11 (Relative Completeness)
If ∆ ⊢ e : T and e is annotated with restricted types, and case discrimination inspects
only variables, then there exists a type E such that ∆  e : E and ∆ ⊢ E <: T
Proof: By induction on the derivation of ∆ ⊢ e : T . 
These algorithmic rules characterize a class of programs for which type checking is decidable: If all program annotations are restricted types whose refinements
are expressions in some decidable theory (where this means that queries of the form
∆  q ⇒ p are decidable, such as linear inequalities with conjunction and case splits),
then all implication queries also fall in this theory, and are decidable. Interestingly,
because existentials only appear as assumptions in proof obligations, the theory need
only support a restricted form of existential quantification that is “productive” in that
each existential corresponds to positive existence of some term, and there is never any
existential proof obligation.
174

If a program includes some predicates that do not fall in the predetermined
language, this algorithm may still succeed in verifying the program if the theorem prover
used is able to dispatch the generated proof obligations. However, in this case a rejection
by this algorithm does not indicate that the program is ill-typed. Hybrid type checking
may be used as a fallback for such programs. The following informal architecture yields
an algorithm always verifies a well-defined subset of programs, but never rejects a welltyped program:

1. Given a program e, does the algorithm yield ∅  e : E?
2. If so, then accept the program.
3. If not, and the program’s annotations fall in the language of restricted types, then
reject the program.
4. Otherwise, perform hybrid type checking on the program.

6.10

Example: Binary Search Trees in λComp
H
As an illustration of our decidable type system, we now revisit the example of

binary search trees. We assume an additional base type BST to represents binary search
trees, with auxiliary functions:
lower : BST → Int
upper : BST → Int

175

that return the lower and upper bounds of integers in a BST, and return maxInt and
minInt, respectively, on empty BSTs. We also assume some additional constructors
and primitives:
minInt : Int
maxInt : Int
min : x : Int → y : Int → {z : Int | z ≤ x ∧ z ≤ y}
max : x : Int → y : Int → {z : Int | z ≥ x ∧ z ≥ y}
The type of a binary search tree with integers in the range [lo, hi) is defined by the
refinement type:
BSTlo,hi = {x : BST | lo ≤ lower(x) ∧ upper(x) < hi }
Binary search tree are created using the constructors empty and node, which
are assigned the following precise types:
empty : lo : Int → hi : Int → BSTlo,hi
node :

lo : Int → hi : Int →
v : {v : Int | lo ≤ v < hi} →
x : BSTlo,v → y : BSTv,hi → BSTlo,hi

Here, these constructors take additional “index” arguments lo and hi, and so we are using an index-type-like implementation of binary search trees. Using these constructors,

176

we can define the insert operation on BSTs:
fixT (λf : T.
λlo : Int. λhi : Int.
λv : {y : Int | lo ≤ y < hi}.
λx : BSTlo,hi .
case x of
(empty lo hi) ⊲ (node lo hi v (empty lo v) (empty v hi))
(node lo hi n l r) ⊲
(case v < n of
true ⊲ (node lo hi n (insert lo n x l) r)
false ⊲ (node lo hi n l (insert n hi x r))
where insert has type
T

=

lo : Int → hi : Int →
v : {v : Int | lo ≤ v < hi} →
BSTlo,hi → BSTlo,hi

All the proof obligations generated for this program are formulae over linear integer
inequalities, and hence decidable.
In this example, the variables lo and hi are somewhat awkward, and provide an
indirect specification of the behavior of insert. We can express this program more naturally by removing these parameters instead expressing the key data invariants directly
in terms of the underlying data structure. In this formulation, the BST constructors
177

have the more natural types:
empty : BSTmaxInt,minInt
node :

v : Int →
x : {x : BST | upper(x) < v} →
y : {y : BST | v ≤ lower(y)} →
BSTlower(x),upper(y)

The revised insert implementation is identical to the one shown above but elides index
variables:
fixT ′ (λf : T ′ . λv : Int. λx : BST.
case x of
empty ⊲ (node v empty empty)
(node n l r) ⊲
(case v < n of
true ⊲ (node n (insert x l) r)
false ⊲ (node n l (insert x r))
and has the following type T ′ :
T′ =

v : Int → x : BST →
BSTmin(lower(x),v),max(upper(x),v)

6.11

Related Work
First, we present a high-level comparison of refinement types with indexed

types, an alternative approach towards similar goals. We next survey other applications
178

of existential quantification in type systems. Finally, we briefly outline the development
of refinement and refinement types which this work directly builds upon.

6.11.1

Indexed Types
Indexed types are discussed in Section 1.1.2 in general, so here we focus on their

relationship to the concept of compositionality. Formally, the type systems of Dependent
ML [Xi and Pfenning 1999], ATS [Cui et al. 2005], and Ωmega [Sheard 2005], distinguish
compile-time from run-time data, and allow types to depend only on compile-time data.
This approach makes compositionality a formally vacuous proposition, and indeed can
sometimes be encoded in existing polymorphic type systems without use of dependent
types [Zenger 1997; McBride 2002].
As an illustrative example, consider the family of types IntListn , where n
indicates the length of lists inhabiting the type. The types of list constructors and the
append function are as follows. We use the type N to distinguish the compile-time type
of natural numbers.
nil : IntList0
cons : n : N → Int → IntListn → IntListn+1
append : m : N → n : N →
IntListm → IntListn → IntListm+n
The connection between run-time invariants and compile-time data is based essentially
on injecting an abstraction of run-time data into compile-time data; for example lists
indexed by their length use a compile-time copy of the natural numbers. The expres179

sions that are reflected into indices can be carefully controlled to ensure that type
compatibility remains decidable.
We can naturally express the indexed type IntListn as the refinement type
{x : IntList | length(x) = n}, and the above types and all proof obligations remain
equivalent. In this way, by reifying the abstraction function from a value to its associated
index, arbitrary indexed types can be embedded into executable refinement types. The
abstraction function (length in this case) may be treated as an uninterpreted symbol
– its definition cannot be necessary in proof obligation since it is not even available to
indexed types.
The above type for append is somewhat awkward, though, as it requires the
additional index parameters n and m.11 In contrast, executable refinement types allow
a more natural expression of the same specification for append, that eliminates these
index parameters:
x : IntList → y : IntList →
{z : IntList | length(z) = length(x) + length(y)}
More critical than the aesthetic issue of index parameters, the index of a data
structure, decided by its implementor, determines which properties may be reasoned
about, inhibiting reuse and composition of data structures. For example, if one is
interested in verifying the ordering of a list, rather that its length, then a different
index is required, specifically the minimum element (at the head of the list) to specify
cons, and also the maximum element (at the tail) to specify append. We also need
11

Note that these index parameters can be inferred in many cases.

180

integers indexed by their exact values Inti , where i is drawn from compile-time integers
Z (with ±∞ for corner cases). We define the type IntListi,j of lists in ascending order
with minimum element i and maximum element j using the following constructors. Note
that predicate subtypes are used on indices of compile-time type Z but not on runtime
terms.
nil : IntList∞,−∞
cons : i : Z → j : {j : Z | j ≥ i} → k : Z →
Inti → IntListj,k → IntListi,max(i,k)
append : i : Z → j : Z → k : {k : Z | k ≥ j} → l : Z →
IntListi,j → IntListk,l → IntListmin(i,k),l
In general, since the index of a data type determines the properties that may be reasoned
about, more complex properties require embedding of more data as indices. In the limit,
giving the precise type x : IntList → {y : IntList | y = x} to the identity function on
lists (or any type) requires embedding the entire type of lists (or any type) into the
index language, which reduces the utility of the syntactic distinction of compile-time
data.
In contrast, executable refinement types can naturally express a wide variety
of refinements over the same IntList type, such as
{x : IntList | minElem(x) = i ∧ maxElem(x) = j}
or the most natural {x : IntList | isSorted(x)}. The proof obligations for the latter
may be more problematic, and the current work is progress towards characterizing those
situations where they are not difficult.
181

Reasoning about how the constructors of a type relate to the global predicate
upon the type is an open question. Kawaguchi et al. [2009] defines a class of “measures”
which are global predicates calculated by catamorphism over the defining equations
of a recursive datatype, and automatically generates the appropriate refinements for
the constructors. Atkey et al. [2011] generalizes this approach to a category-theoretic
analysis of when a type defined as the initial algebra of a functor is actually a refinement
of a type defined by another functor.

6.11.2

Existential Types
Formal existential quantification in type-theory has found a variety of uses,

including closure conversion [Morrisett et al. 1999; Grossman 2002], module systems
[Mitchell and Plotkin 1988; Harper and Lillibridge 1994; Dreyer et al. 2003], and semantics of object orientation [Bruce et al. 1999]. Most similar to the present work are
ML module systems, which also combine existentials with subtyping and a different
(formal) form of singleton types.
The standard substitution-based typing rule for function application is unsound in the presence of effects, so dependencies have to be “forgotten” [Harper and Lillibridge
1994]. Dreyer et al. [2003] ameliorate this restriction with a sophisticated effect system
that restores the power of dependent application in the event that the argument to a
function is effect-free.
Also similar is the need to express sharing constraints. Just as we give self
types to variables, Stone and Harper [2000] and subsequently Dreyer et al. [2003] assign
182

singleton kinds (a special syntactic form) to modules in order to preserve information
when applying a dependently-kinded functor. In contrast to module languages, we
examine the consequences and form of existentials and selfification in the context of
executable refinements with case discrimination and automatic theorem proving. Our
singletons are expressed in the existing type language rather than adding a special
form, and in our setting a type of a variable x may constrain other bound variables so
we maintain this information while adding the identity information to the known type
of x.
Our different approaches and goals led us to emphasize compositional reasoning
as a key aspect of our system, which we present in a more minimal calculus than the
large and pragmatism-oriented ML module type systems. Compositionality is obviously
important in their work as well, since it affects separate (re-)compilation.
Our existential types may be considered a limited expression of Σ types for dependent pairs in powerful type theories such as those underlying Coq [The Coq development team
2012], Hoare Type Theory [Nanevski et al. 2006], and Epigram [McBride and McKinna
2004]. Dependent ML, in fact, makes use of types such as Σx : T1 .T2 with explicit introduction and elimination forms (i.e. without subtyping) to allow functions operating
over arguments of unknown index.
With respect to Σ types, the work presented in this chapter can be interpreted
as a way of providing automation for a simple and common case where the full power
of these type theories is not necessary.

183

6.11.3

Liquid Types
Liquid Types [Rondon et al. 2008] present a similar system, with predicates

drawn from a carefully designed language of predicate templates. To avoid inserting
arbitrary terms into predicates, only variables are used to instantiate these templates,
and variables are required to have types within the selected predicate language. For
programs in a-normal form, where every subexpression is bound to a variable via a
let expression, every subexpression is given a type within the language of predicate
templates. This provides some of the benefits of compositionality, as no predicate will
need to be proved that falls outside of the selected predicate language. Instead, as
with our approximate variant, the approximation occurs because a precise type for each
expression may not be expressible within the decidable predicate language.

184

Chapter 7
Sage: An Implementation of Executable
Refinement Types

This chapter presents Sage, an exploratory implementation of executable refinement types and hybrid type checking. In creating a new language with experimental
features, we hope not for mass adoption, but to ascertain the technical feasibility of the
eventual incorporation of our ideas into mainstream languages. Of interest are question
such as:

- How long does type checking take?
- How effective are state-of-the-art theorem provers at dispatching the obligations
that arise?
- Can realistic programs be easily written and specified?

185

To place these questions in the introductory context of this dissertation, consider again
this variety of specifications for a function that inverts a matrix, reproduced here verbatim for reference:
1. The argument can be any (dynamically-typed) value.
2. The argument must be an array of arrays of numbers.
3. The argument must be a matrix, that is, a rectangular (non-ragged) array of arrays
of numbers.
4. The argument must be a square matrix.
5. The argument must be a square matrix with nonzero determinant.
6. The argument must be a square matrix satisfying a custom invertibility test procedure isInvertible.
Sage supports all of these specifications in a unified type system.

Chapter Outline
- Section 7.1 gives a brief overview of the key features of Sage.
- Section 7.2 illustrates the Sage language through a series of examples written in
Sage’s concrete syntax.
- Section 7.3 presents the abstract syntax for Sage Core, a minimal calculus for
discussing Sage’s operational semantics and type system.
186

- Section 7.4 defines a small-step operational semantics for Sage Core.
- Section 7.5 presents the type system of Sage Core.
- Section 7.6 presents a hybrid type checking / cast insertion algorithm for the
language.
- Section 7.7 and Section 7.8 describe our implementation and experimental results.

7.1

Overview of Sage features
On a technical level, the Sage type system can be viewed as a synthesis of

three concepts: the type Dynamic of terms that “may be” any value expressible in
the language, executable refinement types, and first-class types. These features add
expressive power in three orthogonal directions, yet they all cooperate neatly within
Sage’s hybrid static/dynamic checking framework.
For this section and the next, Sage concrete syntax will be used. Sage’s
concrete syntactic style is borrowed from OCaml and Coq so that the transliterations
of mathematical symbols into ASCII are standard and familiar. Here are some brief
examples:
- The dependent function type x : Int → Int is written x:Int -> Int.
- The function λx : S. x + 1 is written fn (x:S) => x + 1.
- Top-level definitions are introduced with let and terminated with a semicolon.
For example, this defines the function add1.
187

let add1 = fn (x:Int) => x + 1;

The function arguments may also be placed to the left of the equals sign. The
definition of add1 is equivalently written as
let add1 (x:Int) = x + 1;

Any further details of the concrete syntax should be, hopefully, intuitive, but
the full syntactic sugar provided by the Sage implementation is technically uninteresting.

7.1.1

Type Dynamic
Sage supports dynamically-typed programming with the type Dynamic [Thatte

1990; Henglein 1994]. Dynamic is a supertype of all types; any value can be upcast to
type Dynamic. A value of type Dynamic can be implicitly downcast (via a run-time
check) to a more precise type. Downcasts are implicitly inserted when necessary, such
as when applying the primitive addition operator +, which requires arguments of type
Int, to arguments of type Dynamic. Thus, declaring variables to have type Dynamic
(which is the default if type annotations are omitted) yields the usual programming
style and runtime characteristics of dynamically-typed programming languages such as
Scheme, Python, Perl, Ruby, etc.
These dynamically-typed programs can later be annotated with traditional
type specifications like Int and Bool. The programmer need not fully annotate the
program with types in order to reap some benefit. Types enable Sage to check more
188

properties statically, but it is still able to fall back to dynamic checking whenever the
type Dynamic is encountered.

7.1.2

Executable Refinement Types
Sage supports high precision specifications via executable refinement types.

For example, the following code snippet defines the type of integers in the range from
lo (inclusive) to hi (exclusive):
{ x:Int | lo <= x && x < hi }
Sage extends prior chapters by allowing refinement of any type, not only base
types. This results in additional challenges to decidability. For example, whether an
arbitrary function may be assigned the type
{ f : Int -> Int | f(0) == f(3) }
is undecidable. It may even be difficult to translate proof obligations to the input
language of an off-the-shelf theorem prover. Nonetheless, hybrid type checking techniques still apply to this language of types, with difficult cases resulting in simply more
run-time checks.

7.1.3

First-Class Types
Finally, Sage elevates types to be first-class values, following the Type The-

ory of Martin-Löf [1975] and Pure Type Systems of Barendregt [1991]1 . To the usual
functions from terms to terms that exist in all languages, first-class types add:
1
Type Theory and Pure Type System have inspired other languages, including Cayenne, Agda, and
Epigram.

189

- Functions from terms to types. The following function Range takes two integers
and returns the type of integers within that range:
let Range (lo:Int) (hi:Int) : * = { x:Int | lo <= x && x < hi };
Here, * is the type of types and indicates that Range returns a type.
- Functions from types to types. The following function endofunction accepts a
type T as a parameter and returns the type of of endofunctions over T:
let endofunction (T:*) : * = T -> T;

- Functions from types to terms. The following function id accepts a type T as its
first parameter and returns the identity function on T:
let id (T:*) (x:T) : T = x;

The traditional limitation of both first-class types (in the presence of nonterminating programs) and executable refinement types is type checking is not statically
decidable. Sage applies hybrid type checking to both problems. The same mechanism
supports dynamic typing. In practice, most errors are detected statically, while only
more complicated violations escape static checking and are detected at run time.

7.2

Examples of Sage programs
This section examines some code snippets and then several longer example

programs to illustrate the features of Sage and give a sense of the language. The
190

following examples have fairly complete specifications to highlight the specification features, rather than leveraging the type Dynamic.

7.2.1

Binary Search Trees
We will first demonstrate Sage programming with the oft-studied example of

binary search trees, whose Sage implementation is shown in Figure 7.1. For completeness we include the entirety of the code here, with line numbers for reference, beginning
with the definition of Range from Section 7.1.3.
1: let Range (lo:Int) (hi:Int) : * =
2:
{x:Int | lo <= x && x < hi};

A binary search tree (BST lo hi) is an ordered tree containing integers in the
range [lo, hi). A tree may either be Empty, or a Node containing an integer v ∈ [lo, hi)
and two subtrees containing integers in the ranges [lo, v) and [v, hi), respectively.
4: datatype BST (lo:Int) (hi:Int) =
5:
Empty
6:
| Node of (v:Range lo hi) * (BST lo v) * (BST v hi);

This definition is desugared into the core language of Section 7.3. The exact
encoding is not important, but the types of the constructors are, using Sage’s concrete
syntax for types,
Empty : (lo:Int) -> (hi:Int) -> BST lo hi
Node : (lo:Int) -> (hi:Int) ->
(v:Range lo hi) -> (BST lo v) -> (BST v hi) ->
BST lo hi

191

Thus, the type of binary search trees explicates the requirement that these trees
must be ordered. This is a blend of the style of indexed types and direct style: The BST
itself is indexed, but the values on the nodes are constrained by direct refinement.
The function search takes as arguments two integers lo and hi, a binary
search tree of type (BST lo hi), and an integer x in the range contained in the tree. It
returns true if x is contained within the tree.
8: let rec search (lo:Int) (hi:Int) (t:BST lo hi)
9:
(x:Range lo hi) : Bool =
10:
case t of
11:
Empty _ _ -> false
12:
| Node v l r ->
13:
if x = v then true
14:
else if x < v
15:
then search lo v l x
16:
else search v hi r x;

Note the use of dependent function types, where the types of the parameters
t and x to search depend on the values of the parameters lo and hi.
The function insert takes similar arguments and extends the given tree with
the integer x. It is a useful demonstration of the construction of the tree.
18: let rec insert (lo:Int) (hi:Int) (t:BST lo hi)
19:
(x:Range lo hi) : (BST lo hi) =
20:
case t of
21:
Empty -> Node lo hi x (Empty lo x) (Empty x hi)
22:
| Node v l r ->
23:
if x < v
24:
then Node lo hi v (insert lo v l x) r
25:
else Node lo hi v l (insert v hi r x);

The Sage system uses an automatic theorem prover to statically verify that the
specified ordering invariants on binary search trees are satisfied by these two functions.
192

No run-time checks are required for this example.
The precise type specifications enable Sage to detect various common programming errors. For example, suppose we inadvertently used the wrong conditional
test:
23:

if x <= v

For this (incorrect and ill-typed) program, Sage will report that the specification for
insert is violated by the first recursive call:
line 25:

x does not have type (Range lo v)

Similarly, if one of the arguments to the constructor Node is incorrect, e.g.:
26:

else Node lo hi v r (insert v hi r x);

Sage will report the type error:
line 26:

r does not have type (BST lo v)

Using this BST implementation, constructing trees with specific constraints
is straightforward and verifiable. For example, the following code constructs a tree
containing only positive numbers:
let PosBST : * = BST 1 MAXINT;
let nil : PosBST = Empty 1 MAXINT;
let add (t:PosBST) (x:Range 1 MAXINT) : PosBST =
insert 1 MAXINT t x;
let find (t:PosBST) (x:Range 1 MAXINT) : Bool =
search 1 MAXINT t x;
let t :

PosBST = add (add (add nil 1) 3) 5;
193

Figure 7.1: Binary Search Trees in Sage

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:

let Range (lo:Int) (hi:Int) : * =
{x:Int | lo <= x && x < hi};
datatype BST (lo:Int) (hi:Int) =
Empty
| Node of (v:Range lo hi) * (BST lo v) * (BST v hi);
let rec search (lo:Int) (hi:Int) (t:BST lo hi)
(x:Range lo hi) : Bool =
case t of
Empty -> false
| Node v l r ->
if x = v then true
else if x < v
then search lo v l x
else search v hi r x;
let rec insert (lo:Int) (hi:Int) (t:BST lo hi)
(x:Range lo hi) : (BST lo hi) =
case t of
Empty -> Node lo hi x (Empty lo x) (Empty x hi)
| Node v l r ->
if x < v
then Node lo hi v (insert lo v l x) r
else Node lo hi v l (insert v hi r x);

194

Figure 7.2: Regular Expressions in Sage

datatype Regexp =
Alpha
| AlphaNum
| Kleene of Regexp
| Concat of Regexp * Regexp
| Or of Regexp * Regexp
| Empty;
let match (r:Regexp) (s:String) :

Bool = ...

let Credential = {s:String | match (Kleene AlphaNum) s};

Note that this fully-typed BST implementation inter-operates with dynamicallytyped client code:
let t :

7.2.2

Dynamic = (add nil 1) in find t 5;

Regular Expressions
Regular expressions are a common means of validating string data, and serve

as a good example of a specification beyond the common constraints using linear arithmetic. Figure 7.2 declares the Regexp data type and the function match, which determines if a string matches a regular expression. To avoid irrelevant complexity, Regexp
only expresses regular expressions over the two character classes of alphabetic characters (Alpha) and alphanumeric characters (AlphaNum). In addition to these primitive
character classes, we include the usual the Kleene closure, concatenation, and choice operators. As an example, the regular expression “[a-zA-Z0-9]*” would be represented
195

in our datatype as (Kleene AlphaNum).2
The code then uses match to define the type Credential, which refines the
type String to allow only alphanumeric strings. We use the type Credential to enforce an important, security-related interface specification for the following function
authenticate. This function performs authentication by querying a SQL database
(where ‘^’ denotes string concatenation):
let authenticate (user:Credential) (pass:Credential) :
let query : String =
("SELECT count(*) FROM client WHERE name =" ^
user ^ " and pwd=" ^ pass) in
executeSQLquery(query) > 0;

Bool =

This code is prone to SQL injection attacks if given specially-crafted non-alphanumeric
strings. For example, calling
authenticate

"admin --"

""

breaks the authentication mechanism because “--” starts a comment in SQL and consequently “comments out” the password part of the query. To prohibit this vulnerability,
the type:
authenticate :

Credential → Credential → Bool

specifies that authenticate should be applied only to alphanumeric strings.
Next, consider the following user-interface code:
let username : String = readString() in
let password : String = readString() in
authenticate username password;
2

Assuming ASCII, since a more robust character set is not important for this demonstration.

196

This code is ill-typed, since it passes arbitrary user input of type String to
authenticate. However, proving that this code is ill-typed is quite difficult, since it
depends on complex reasoning showing that the user-defined function match is not a
tautology, i.e. not all Strings are Credentials.
In fact, the current implementation of Sage cannot statically verify or refute
this code. Instead, it inserts the following casts at the call site to enforce the specification
for authenticate dynamically:
authenticate (hCredentialiusername) (hCredentialipassword);
At run time, these casts check that username and password are alphanumeric strings
satisfying the predicate match (Kleene AlphaNum). If the username “admin --” is
ever entered, the cast (hCredentialiusername) will fail and halt program execution.
As a bonus, a dynamic cast failure actually strengthens Sage’s ability to detect type errors statically. In particular, the string “admin --” is a witness proving
that not all Strings are Credentials, i.e., 6⊢ String <: Credential. Rather than
discarding this information, and potentially observing the same error on later runs or
in different programs, such refuted subtype relationships are stored in a database. If
the above code is later revisited, the Sage type checker will discover upon consulting
this database that String is not a subtype of Credential, and it will statically reject
the call to authenticate as ill-typed. Additionally, the database stores a list of other
programs previously type-checked under the assumption that String may be a subtype
of Credential. These programs may also fail at run time and Sage will also report

197

that they should be revisited using the more-informed type checker. This database is
discussed further after the formal developments, in Section 7.7.2.

7.2.3

Printf
Our final example is the classic printf function. The number and type of the

expected arguments to printf depends in subtle ways on the format string (the first
argument). In Sage, we can assign to printf the precise type:
printf :

(format:String) -> (Printf Args format)

where the user-defined function
Printf Args :

String -> *

returns the printf argument types for the given format string.3 For example,
(Printf Args "%d%d")
evaluates to the type
Int -> Int -> Unit
Thus, the Sage language is sufficiently expressive to need no special support for accommodating printf and catching errors in printf clients statically. In
contrast, other language implementations require special rules in the compiler or run
time to ensure the type safety of calls to printf. For example, Scheme [Sperber et al.
2007] and GHC [Marlow 2010] leave all type checking of arguments to the run time.
3

The implementation of Printf Args is more complex than warrants textual inclusion.

198

OCaml [Leroy et al. 2013], on the other hand, performs static checking, but it requires
the format string to be constant.
Sage can statically check many uses of printf with non-constant format
strings, as illustrated by the following example:
let repeat (s:String) (n:Int) : String =
if (n = 0) then "" else (s \verb@^@ (repeat s (n-1)));
// checked statically:
printf (repeat "\%d" 2) 1 2;

The Sage type checker infers that printf (repeat "%d" 2) has type
Printf Args (repeat "%d" 2), which evaluates (at cast insertion time) to Int →
Int → Unit, and hence this call is well-typed. Conversely, the type checker would
statically reject the following ill-typed call:
// compile-time error:
printf (repeat "%d" 2) 1 false;
For efficiency, and to avoid non-termination, the type checker performs only a bounded
number of evaluation steps before resorting to dynamic checking. Thus, the following
call requires a run-time check:
// run-time error:
printf (repeat "%d" 200) 1 2 ...

199 false;

As expected, the inserted dynamic cast catches the error.
The current Sage implementation is not yet able to statically verify that the
implementation of printf matches its specification,
(format:String) -> (Printf_Args format)

199

As a result, the type checker inserts a single dynamic type cast into the printf implementation. This example illustrates the flexibility of hybrid checking — the printf
specification is enforced dynamically on the printf implementation, but also enforced
(primarily) statically on client code. We revisit this example in Section 7.6.4 to illustrate
Sage’s cast insertion algorithm.

7.3

The Sage Core Language
Sage programs are desugared into a small core language shown in Figure 7.3.

We use the same metavariable conventions from the rest of this dissertation with few
differences that will be clear from context. Most importantly, the syntactic sort of types
and terms is merged, so the metavariables used for terms (e, f , p, etc) and those used for
types (S, T , etc) now refer to the same syntactic sort. The choice of which metavariable
to use in a particular situation is only to provide some intuition.
First-class types also allow us to express all type constructors other than dependent function types as primitives, similar to the treatment of conditionals (via if)
and recursion (via fix) in λH . This is the fruition of Type Theory: The core constructs
need not change, but form a meta-theoretical framework; we need only discuss the semantics of the primitives we include in the framework. The following primitives are
crucial to the definition of Sage:

- The primitive Refine is used to construct executable refinement types. Suppose
f : T → Bool is some arbitrary predicate over type T . Then the type Refine T f
200

Figure 7.3: Syntax of Sage Core

d, e, f, g, p, q, S, T, U ::=

Terms:

x

variable

k

primitive

let x : S = e in e

binding

λx : S. e

abstraction

f e

application

x:S → T

function type

u, v, V, W ::=

Values

λx : S. e

abstraction

x:S → T

function type

k
k v1 . . . vn

primitive
primitive, 0 < n < arity (k)

Refine W v

refinement

fix W v

recursive type

201

denotes the executable refinement type classifying all values of type T that satisfy
the predicate f . Following our notation established in previous chapters, we use
the shorthand {x : T | p} to abbreviate Refine T (λx : T. p).
- The primitive fix enables the definition of recursive functions and recursive types.
For example, the type of integer lists is defined via the fixed point calculation:
fix * (λL : *. Sum Unit (Pair Int L))
which (roughly speaking) returns a type L satisfying the equation:
L = Sum Unit (Pair Int L)
(Here, Sum and Pair are the usual type constructors for sums and pairs, respectively.)
The subtyping algorithm treats recursion in types differently than the operational
semantics treats recursion in terms, as most recursive types are infinite and result
in infinite reduction sequences.
- The primitive Dynamic [Thatte 1990; Abadi et al. 1991] can be thought of as the
most general type. Every value may be assigned type Dynamic, and casts can
be used to convert values from type Dynamic to other types (and of course such
downcasts may fail if applied to inappropriate values).
- The primitive cast performs dynamic coercions between types. It takes as arguments a type T and a value (of type Dynamic), and it attempts to cast that value
202

Figure 7.4: Sage Shorthands

hT i = cast T
{x : T | p} = Refine T (λx : T. p)
h{x : T | p}, q, vi = Checking T (λx : T. p) q v
ifT p then d else e = if T p (λx : {Unit | p}. d) (λx : {Unit | not p}. e)

to type T . We use the shorthand hT i e to abbreviate cast T e. Thus, for example,
the expression
h{x : Int | x ≥ 0}i y
casts the integer y to the refinement type of natural numbers, and fails if y is
negative.
- The primitive Checking is used to construct casts-in-progress while a refinement
predicate is being checked. Following the notation established in previous chapters, we will write h{x : T | p}, q, vi to abbreviate Checking T (λx : T. p) v q.
Shorthands for selected primitives are summarized in Figure 7.4.

7.4

Operational Semantics of Sage Core
Figure 7.5 and Figure 7.6 describe the meaning of Sage programs with a small-

step operational semantics. Because much of the interest lies in the meaning of prim203

itives, we do not embed all of this meaning in a δ function, but write out in full the
meaning of those primitives whose semantics bear discussion. We do, however, elide
standard semantics such as the basics of boolean and arithmetic connectives. The semantics the primitive if are presented as an example of a primitive with standard
semantics. The semantics of cast are presented in full.
Evaluation is performed inside call-by-value evaluation contexts E, to model
the actual Sage execution strategy. Arbitrary contexts are still written C.
[E-App] and [E-Let] Both λ-abstractions and let-bindings evaluate according to β-

reduction, replacing their bound variable with the actual value in their body.
[E-Fix]: The operation fix is used to define recursive functions and types, which are

considered values, and hence fix U v is also a value. However, when this
construct fix U v appears in a strict position (i.e., in a function position or in
a cast), the rule [E-fix] performs one step of unrolling to yield v (fix U v).
[E-If1] and [E-If2]: These rules encode the standard definition for a Church-encoded

conditional. Each of the branches of the conditional is a constant function
taking a unit parameter and returning the value of that branch.

Figure 7.6 displays all redex evaluation rules pertaining to casts in Sage. The
expanded type language and first-class types result in many more possibilities than in
λH .
[E-Cast-Dyn] Casts to type Dynamic always succeed.

204

Figure 7.5: Operational Semantics for Sage Core

Redex Evaluation

dye

(λx : S. e) v y [x 7→ v] e

[E-App]

let x : S = v in e y [x 7→ v] e

[E-Let]

S[fix U v] y S[v (fix U v)]

[E-Fix]

ifT true v1 v2 y v1 unit

[E-If1]

ifT false v1 v2 y v2 unit

[E-If2]

Evaluation

d
E[d]

E[e]

if d y e

[E-Compat]

C, D

Call-by-value Contexts
E

::=

• | E e | v E • | C e | e C | λx : S. C

S

Unfolding Contexts
S ::=

• v | h•i v

C, D

Arbitrary Contexts
C ::=

e

• | C e | e C | x : C → T | x : S → C | λx : C. e | λx : S. C
| let x : S = C in e | let x : C = e in e | let x : S = e in C

205

Figure 7.6: Operational Semantics for Sage Casts

Redex Evaluation

dye

hDynamici v y v

[E-Cast-Dyn]

hki v y v

[E-Cast-Basic]

if for k ∈ {Int, Bool, Unit} and appropriate v

hx : S → T i v y λx : S. hT i (v (hDi x))

[E-Cast-Fn]

where D = domain(v)

hRefine T f i v y Checking T f v (f (hT i v))
Checking T f v true y hT i v

[E-Cast-Begin]
[E-Cast-End]

h*i v y v

[E-Cast-Type]

if v is of one of the forms Int, Bool, Unit, Dynamic, *, x : S → T, Refine T f
domain

:

Value → Term

domain(λx : T. e) = T
domain(fix (x : T → T ′ ) v) = T

domain(k v1 . . . vi−1 ) = type of ith argument to k

206

[E-Cast-Basic] Casts to primitive types such as Int, Bool, and Unit succeed when

applied to appropriate values.
[E-Cast-Fn] Casts to function types are more involved. The casts of Sage only retain

their target type (an experimental design decision) so we require the following
partial function domain returns the domain of a function value. The rule casts
a function v to type x : S → T by creating a new function:
λx : S. hT i v (hDi x)
where D = domain(v) is the domain type of the function v. Just as in λH , this
new function takes a value x of type S, casts it to D, applies the given function
v, and casts the result to the desired result type T .
[E-Cast-Begin] and [E-Cast-End]: These two rules are analogous to those for λH . To

cast a term v to a refined type Refine T f , first v is cast to type T and the
predicate f (hT i v) is evaluated to determine its truth or falsity. If this check
succeeds, then the result of the cast is hT i v.4
[E-Cast-Type]: Casts to type * succeed only for special values of type *. With first-

class types, the programmer may write nonsensical function “types” such as
3 → 6, since this is syntactically valid. Suppose this type occurs in a cast
h3 → 6i. Though this term is ill-typed, the hybrid type checker may not be
able to determine that this is so (for example if 3 and 6 were the result of
4

There is clearly room for optimized evaluation strategies that duplicate less work when casting to
a refinement type, but the current presentation has been chosen for clarity.

207

a long-running computation) and may output the cast hh*i 3 → h*i 6i. Later
evaluation via [E-Cast-Fn] will expand the cast on the domain type to hh*i 3i
which is not a value, and will result in a failed cast.

7.5

The Sage Core Type System
The Sage type system is defined via the following judgments:

- The typing judgment Γ ⊢ e : T denotes that expression e has type T under the
assumptions in environment Γ.
- The subtyping judgment Γ ⊢ S <: T denotes that type S is a subtype of T under
the assumptions in environment Γ.
- The validity judgment Γ |= p denotes that p is valid in environment Γ. This
replaces the use of Γ ⊢ p ⇒ q in prior chapters.
All of the above judgments utilitize an environment Γ that binds variables to
types and, in some cases, to values. We assume that variables are bound at most once
in an environment and, as usual, we apply implicit α-renaming of bound variables to
maintain this assumption and to ensure that substitutions are capture-avoiding. In addition, we assume that the typing environment is well-formed according to the judgment
⊢ Γ.

208

7.5.1

Typing for Sage Core
The main typing judgment
Γ⊢e : T

assigns type T to term e in the environment Γ, and is defined by the rules in Figure 7.7.
Note that the type well-formedness judgment Γ ⊢ T from prior chapters is merged with
this judgment: Since types are terms, they are validated by being assigned the type *
via the typing judgment Γ ⊢ T : *. Thus, Int, Bool, and Unit all have type *. Also, *
itself has type *.5
[T-Prim]: The auxiliary function ty returns the type of the primitive k, as defined in

Figure 7.8. In Sage these types can be quite rich; note the heavy use of firstclass types.
Many types are precise as in λH ; an integer n has the precise type {m : Int | m = n}
denoting the singleton set {n}.
In particular, note the type of if. The “then” parameter to if is a thunk
of type ({d : Unit | p} → X). That thunk can be invoked only if the domain
{d : Unit | p} is inhabited, i.e., only if the test expression p evaluates to true.
Thus the type of if precisely specifies its behavior; Sage has a path-sensitive
type system.
5
The relationship * : * results in a paradox known as Girard’s Paradox [Girard 1972; Coquand 1986;
Hurkens 1995] which allows one to construct a non-terminating term that is a proof of ⊥ (falsity, or
the empty type). When type theory is proposed as a foundation for mathematics, the solution is to
introduce a stratification of * into *n indexed by a natural number, and insist that *n : *m only if
n < m. However this complexity is not needed since the paradox (not the only one in Sage) does not
affect the use of a language for programming.

209

Figure 7.7: Type Rules for Sage Core

Γ ::=

Environments:
empty environment
environment extension
environment term extension

∅
Γ, x : T
Γ, x = v : T

⊢Γ

Well-formed environment

⊢∅

⊢Γ

[We-Empty]

⊢Γ

Γ⊢T : *

Γ⊢T : *

⊢ Γ, x : T

Γ⊢e : T

⊢ Γ, x : T = e

[We-Ext1]

[We-Ext2]

Γ⊢e : T

Type rules

Γ ⊢ k : ty(k)
Γ⊢S : *

(x : T ) ∈ Γ or

[T-Const]

Γ, x : S ⊢ e : T

Γ ⊢ (λx : S. e) : (x : S → T )

Γ ⊢ x : {y : T | y = x}
Γ⊢S : *

[T-Fun]

Γ⊢e : S

Γ ⊢ f e : [x 7→ e] T

Γ, (x = v : S) ⊢ e : T

Γ ⊢ let x : S = v in e : [x 7→ v] T

[T-Let]

210

[T-Var]

Γ, x : S ⊢ T : *

Γ ⊢ (x : S → T ) : *

Γ ⊢ f : (x : S → T )
Γ⊢v : S

(x = v : T ) ∈ Γ

[T-Arrow]

[T-App]

Γ⊢e : S

Γ ⊢ S <: T

Γ⊢e : T

[T-Sub]

Figure 7.8: Example types of Sage constants

*
Unit
Bool
Int
Dynamic
Refine

:
:
:
:
:
:

*
*
*
*
*
X : * → (X → Bool) → *

unit
true
false
not
n
+
=

:
:
:
:
:
:
:

Unit
{b : Bool | b}
{b : Bool | not b}
b : Bool → {b′ : Bool | b′ = not b}
{m : Int | m = n}
n : Int → m : Int → {z : Int | z = n + m}
x : Dynamic → y : Dynamic → {b : Bool | b = (x = y)}

if : X : * → p : Bool →
({d : Unit | p} → X) →
({d : Unit | not p} → X) →
X
fix : X : * → (X → X) → X
cast : X : * → Dynamic → X
Checking : X : * → f : (X → Bool) → x : X → {p : Bool | p ⇒ f x} → X

211

[T-Var]: For a variable x we extract the type T of x from the environment, and assign

to x the singleton refinement type6 {y : T | y = x}.
[T-Fun]: For a function λx : S. e we infer the type T of e in an extended environment

and return the dependent function type x : S → T , where x may occur free in
T.
[T-Arrow]: The type x : S → T is itself a term, which is assigned type *, provided that

both S and T have type * in appropriate environments.
[T-App]: This rule is completely standard for function application in dependent types.7

For an application (f e) we first check that f has a function type x : S → T
and that e is in the domain of f . The result type is T with all occurrences of
the formal parameter x replaced by the actual parameter e.
[T-Let]: For the term let x : S = v in e we first check that the type of the bound value

v is S. Then e is typed in an environment that contains both the type and the
value of x.8 The precise bindings from let expressions are used in the subtype
judgment, as described below.
[T-Sub]: Subtyping is allowed at any point in a typing derivation via this general sub-

sumptoin rule.
6

The necessity of this “selfification” was discovered prior to the work of Chapter 6 and was influenced
more by Ou et al. [2004] but seems prescient in retrospect.
7
The compositionality-restoring techniques of Chapter 6 were developed after Sage and have not
been attempted in this more challenging context.
8
In the surface syntax of Sage a non-value may still be bound to an expression as in
let x:S = d in e but this is desugared into (λx : S. e) d.

212

7.5.2

Subtyping for Sage Core
The subtype judgment
Γ ⊢ S <: T

states that S is a subtype of T in the environment Γ, and it is defined as the greatest
solution (to permit infinite types) to the collection of subtype rules in Figure 7.9.

[S-Refl]: Every type is a subtype of itself, axiomatically.

[S-Dyn]: Every type is a subtype of the type Dynamic, which functions essentially as

a “top” type in the static type system, though its algorithmic implementation
during hybrid type checking differs.
[S-Fun]: Subtyping between function types is entirely standard.

[S-Var]: A variable may be hygienically replaced with the value to which it is bound

during subtyping.
[S-Eval-L] and [S-Eval-R]: The subtype relation is closed under evaluation of terms in

arbitrary positions.
[S-Ref-L]: If S is a subtype of T , then any refinement of S is also a subtype of T .
[S-Ref-R]: When S is a subtype of T we invoke the validity judgment Γ |= f x, discussed

below, to determine if f x is valid for all values x of type S. If so, then S is a
subtype of Refine T f .

213

Figure 7.9: Subtyping Rules for Sage Core

Γ ⊢ S <: T

Subtype rules
[S-Refl]

[S-Dyn]

Γ ⊢ T <: T

Γ ⊢ T <: Dynamic

Γ ⊢ T1 <: S1

Γ, x : T1 ⊢ S2 <: T2

[S-Fun]

Γ ⊢ (x : S1 → S2 ) <: (x : T1 → T2 )
Γ, [x 7→ v] Γ′ ⊢ [x 7→ v] S <: [x 7→ v] T

dye
[S-Var]

′

Γ, x = v : U, Γ ⊢ S <: T
dye

Γ ⊢ S <: C[d]

Γ ⊢ C[d] <: T

Γ ⊢ C[e] <: T
Γ ⊢ S <: T

[S-Eval-R]

Γ ⊢ S <: C[e]

Γ ⊢ (Refine S f ) <: T

Γ ⊢ S <: T

[S-Eval-L]

Γ, x : S |= f x

Γ ⊢ S <: (Refine T f )

214

[S-Ref-R]

[S-Ref-L]

7.5.3

Validity for Sage Core
Our type system is parameterized with respect to the validity judgment
Γ |= p

which defines the validity of term p in an environment Γ. Chapter 3 demonstrates one
way to construct such a judgment using denotational semantics. Since this chapter is
concerned with the implementation scenario where validity checking will be implemented
by an off-the-shelf theorem prover, it is more appropriate and useful to discuss the
axioms to which an implementation must adhere. In the following, all environments
and terms are assumed to be well-formed.

Requirement 7.1 (Validity Axioms) A validity judgment for Sage must obey the
following axioms.
1. Faithfulness: ∅ |= true and ∅ 6|= false.9
2. Hypothesis: If (x : {y : S | p}) ∈ Γ then Γ |= [y 7→ x] p.
3. Weakening: If Γ, Γ′′ |= p then Γ, Γ′ , Γ′′ |= p.
4. Substitution: If Γ, (x : S), Γ′ |= p and Γ ⊢ e : S then Γ, [x 7→ e] Γ′ |= [x 7→ e] p.
5. Definition: Γ, (x = v : S), Γ′ |= p if and only if Γ, [x 7→ v] Γ′ |= [x 7→ v] p.
6. Preservation: If e

∗

e′ , then Γ |= C[e] if and only if Γ |= C[e′ ].

9
Early presentations of this material erroneously included an environment in this axiom. We extend
thanks to Michael Greenberg for pointing this out.

215

7. Narrowing: If Γ, (x : T ), Γ′ |= p and Γ ⊢ S <: T then Γ, (x : S), Γ′ |= p.
In addition to the undecidability induced by theorem proving, the subtype
judgment may require an unbounded amount of evaluation during type checking. These
decidability limitations motivate the development of the hybrid type checking techniques
of the following section.
The Sage type system guarantees type soundness through the standard lemmas progress (well-typed programs can only get stuck due to failed casts) and preservation (evaluation of a term preserves its type). The proofs are lengthy but standard;
for detail consult Gronski et al. [2006].

7.6

Hybrid Type Checking for Sage
Hybrid type checking for Sage, as for λH , relies on the cooperation between a

subtyping algorithm and a cast insertion algorithm, as well as an algorithmic theorem
proving judgment. The key judgments are:
- The cast insertion judgment Γ  d ֒→ e : T elaborates the source term d, in
environment Γ, to a safe term e adding casts where necessary and synthesizing T .
- The cast insertion and checking judgment Γ  d ֒→ e ↓ T takes as an input
the desired type T and ensures that e has type T by adding necessary casts as
determined by the conclusions from the subtyping algorithm.
- The algorithmic subtyping judgment Γ a S <: T checks for a subtyping relationship between S and T and has a three-valued result a, as usual for HTC.
216

- The algorithmic validity judgment, also called “theorem proving”, Γ |=aalg p attempts to prove or refute p, and has a three-valued result a as well.
- The database of counterexamples Γ ⊢×
db S <: T refutes subtyping judgments for
which a witness has been gathered via a runtime failure.

The cast insertion rules guarantee that the resulting program is well-typed,
and thus it can only go wrong due to failed casts. In addition, this property permits
type-directed optimizations on compiled code.

7.6.1

Cast Insertion and Checking

The only judgment that directly inserts casts,
Γ  d ֒→ e ↓ T
is defined identically for Sage and for λH .
[CC-OK]: When checking a whether term d of type S may be considered to have type

T , if the S can be proven a subtype of T then the term is unchanged.
[CC-Chk]: If, however, the subtype relationship can be neither proven nor refuted, then

the check is deferred until run time by inserting the cast hT i.
If subtyping is refuted, the lack of an inference rule forces a static rejection of
the program.

217

Figure 7.10: Cast Insertion and Checking for Sage Core

Γ  d ֒→ e ↓ T

Cast Insertion and Checking rules
Γ  d ֒→ e : S

√

Γ  S <: T
[CC-Ok]

Γ  d ֒→ e ↓ T
Γ  d ֒→ e : S

Γ ? S <: T
[CC-Chk]

Γ  d ֒→ (hT i e) ↓ T

7.6.2

Cast Insertion and Synthesis
The cast insertion and checking judgment is applied for a full program by the

cast insertion and synthesis judgment
Γ  d ֒→ e : T
defined by rules in Figure 7.11. Many are similar to the corresponding type rules or
analogous to the rules from Chapter 4, but a few have interesting new consequences due
to Sage’s additional features.
[C-Var]: This rule provides selfification of variables analogously to the type rule [T-Var].
[C-Prim]: Primitives are assumed to be well-typed, so no casts are needed.
[C-Fun]: For a function (λx : S. e) the input annotation S is, itself, a term, and is

checked against type *.
218

Figure 7.11: Cast Insertion for Sage Core

Cast Insertion rules
(x : T ) ∈ Γ or

Γ  d ֒→ e : T
(x = e : T ) ∈ Γ

[C-Var]

[C-Prim]

Γ  x ֒→ x : {y : T | y = x}

Γ  k ֒→ k : ty(k)

Γ  S ֒→ S ′ ↓ *

Γ, x : S ′  e ֒→ e′ : T
[C-Fun]

Γ  (λx : S. e) ֒→ (λx : S ′ . e′ ) : (x : S ′ → T )
Γ  S ֒→ S ′ ↓ *

Γ, x : S ′  T ֒→ T ′ ↓ *
′

[C-Arrow]

′

Γ  (x : S → T ) ֒→ (x : S → T ) : *
Γ  f ֒→ f ′ : U

Γ  e ֒→ e′ ↓ S

[C-App1]

Γ  e ֒→ e′ ↓ Dynamic

[C-App2]

unrefine(U ) = x : S → T
′

′

′

Γ  f e ֒→ f e : [x 7→ e ] T
Γ  f ֒→ f ′ ↓ (Dynamic → Dynamic)

Γ  f e ֒→ f ′ e′ : Dynamic
Γ  S ֒→ S ′ ↓ *

Γ  v ֒→ v ′ ↓ S ′

Γ, (x = v ′ : S ′ )  e ֒→ e′ : T
[C-Let]
′

′

′

Γ  let x : S = v in e ֒→ let x : S = v in e

219

′

: [x 7→ v ] T

′

[C-Arrow]: For a function type x : S → T , the two component types S and T are

checked against type *.
[C-Let]: The term let x : S = v in e is processed by recursively processing v, S and e

in appropriate environments.
[C-App1] and [C-App2]: The rules for function application are more interesting. The

rule [C-App1] processes an application f e by processing the function f to some
term f ′ of some type U . The type U may be a function type embedded inside
refinements as in
{x : (Int → Int) | :}x(0) > 0
In order to extract the domain and codomain of the function, we use unrefine
to remove any outer refinements of U before checking the type of the argument
e against the expected type. Formally, unrefine is defined as follows:
unrefine : Term → Term
unrefine(x : S → T )

=

x:S → T

unrefine(Refine T f )

=

unrefine(T )

unrefine(S)

=

unrefine(S ′ )

if S

S′

The last clause permits S to be simplified via evaluation while removing outer
refinements. Given the expressiveness of the type system, this evaluation may
not converge within a given time bound. Hence, to ensure that our compiler
accepts all (arbitrarily complicated) well-typed programs, the rule [C-App2]
provides a backup compilation strategy for applications that requires less static
220

analysis, but performs more dynamic checking. This rule checks that the function expression has the most general function type Dynamic → Dynamic, and
correspondingly coerces e to type Dynamic, resulting in an application with
type Dynamic.

7.6.3

Algorithmic Subtyping

For any subtype query Γ ⊢ S <: T , the algorithmic subtyping judgment
Γ a S <: T
√
returns a result a ∈ { , ×, ?} depending on whether the algorithm succeeds in proving
√
( ) or refuting (×) the subtype query, or whether it cannot decide the query (?).
Our algorithm conservatively approximates the subtype specification in Figure 7.7. However, special care must be taken in the treatment of Dynamic. Since we
would like values of type Dynamic to be implicitly cast to other types, such as Int, the
subtype algorithm should conclude Γ ? Dynamic <: Int (forcing a cast from Dynamic
to Int), even though clearly Γ 6⊢ Dynamic <: Int.
A naı̈ve subtype algorithm could always return the result “?” and thus trivially satisfy these requirements, but more precise results enable Sage to verify more
properties and to detect more errors statically.
The rules in Figure 7.12 illustrate the multiple sorts of reasoning that our
algorithm uses. However, these rules are not syntax directed so they do not define an
algorithm without some more description. The algorithm attempts to apply the rules

221

in the order in which they are presented in the figure, falling into three main categories:
1. The counterexample database is checked first, so that subtyping can be immediately rejected if a counterexample is known.
2. Then the structural rules are tried in order to attempt to decompose the subtyping problem into smaller queries. If types have incompatible shapes, then the
algorithm returns “×”.
3. Finally, if none of the above apply, the type is evaluated for some finite number of
steps to try to reduce it to a form where other rules apply. The form of evaluation
is specialized to only perform useful reductions: In particular, we do not expand
fixpoints unless they appear in function position.
4. If no rule applies, the algorithm returns “?”

[AS-Db]: Before applying any other rules, the algorithm attempts to refute that Γ ⊢

S <: T by querying the database of previously refuted subtype relationships.
The judgment Γ ⊢×
db S <: T indicates that the database includes an entry
stating that S is not a subtype of T in an environment Γ′ , where Γ and Γ′
are compatible in the sense that they include the same bindings for the free
variables in S and T . This compatibility requirement ensures that we only
re-use a refutation in a typing context in which it is meaningful.
[AS-Eval-L] and [AS-Eval-R]: These two rules evaluate the terms representing types.

The algorithm only applies these two rules a bounded number of times before
222

Figure 7.12: Algorithmic Subtyping for Sage Core

Γ a S <: T

Algorithmic subtyping rules
Γ ⊢×
db S <: T

[AS-Db]

Γ  S <: T
Γ a T1 <: S1

[AS-Refl]

√

×

Γ  T <: T

Γ, x : T1 b S2 <: T2
[AS-Fun]

[AS-Dyn-L]

Γ ? Dynamic <: T

Γ a⊓b (x : S1 → S2 ) <: (x : T1 → T2 )

Γ a S <: T
[AS-Dyn-R]

√

√
a ∈ { , ?}

[AS-Ref-L]

a

Γ  S <: Dynamic

Γ  (Refine S f ) <: T

Γ a S <: T
Γ

a⊓b

Γ, x : S |=balg f x

[AS-Ref-R]

S <: (Refine T f )

Γ, [x 7→ v] F a [x 7→ v] S <: [x 7→ v] T

[AS-Var]

a

Γ, x = v : u, F  S <: T
e y e′

e y e′

Γ a D[e′ ] <: T

Γ a S <: D2 [e′ ]

[AS-Eval-L]

[AS-Eval-R]

a

a

Γ  D[e] <: T
D ::= • | N D

Γ  S <: D2 [e]
where N is a normal form

223

timing out and forcing the algorithm to use a different rule or return “?”. This
prevents non-terminating computation as well as infinite unrolling of recursive
types.
[AS-Dyn-L] and [AS-Dyn-R]: These rules ensure that any type can be considered a

subtype of Dynamic and that converting from Dynamic to any type requires an
explicit coercion.
[AS-Ref-R]: This rule for checking whether S is a subtype of a specific refinement type

relies on a theorem-proving algorithm, Γ |=aalg p, for checking validity. This
algorithm is an approximation of some validity judgment Γ |= p satisfying the
√
axioms in Requirement 7.1. As with subtyping, the result a ∈ { , ?, ×} indicates whether or not the theorem prover could prove or refute the validity of p.
The algorithmic theorem proving judgment must be conservative with respect
to the logic it is approximating, as captured in the following requirement:

Requirement 7.2 (Algorithmic Theorem Proving)
√

1. If Γ |=alg p then Γ |= p.
′ ′
2. If Γ |=×
alg p then ∀Γ , p obtained from Γ and p by replacing each occurrence

of the type Dynamic by any type, we have that Γ′ 6|= p′ .
Our current implementation of this theorem-proving algorithm translates the
query Γ |=aalg p into input for the Simplify theorem prover [Detlefs et al. 2005].

224

For example, the query
x : {x : Int | x ≥ 0} |=aalg x + x ≥ 0
is translated into the Simplify query:

(IMPLIES (>= x 0) (>= (+ x x) 0))

for which Simplify returns Valid. Given the incompleteness of Simplify (and
other theorem provers), care must be taken in how the Simplify results are
interpreted. For example, for the translated version of the query
x : Int |=aalg x ∗ x ≥ 0
Simplify returns Invalid, because it is incomplete for arbitrary multiplication.
In this case, the Sage theorem prover returns the result “?” to indicate that
the validity of the query is unknown. We currently assume that the theorem
prover is complete for linear integer arithmetic. Simplify has very effective
heuristics for integer arithmetic, but does not fully satisfy this specification.
More recent SMT provers are explicit about when they mean “maybe” instead
of erroneously saying “no”.
An effective and mature hybrid type checking implementation will rely on
compile-time evaluation of terms, a counterexample database, and a theorem
prover that returns three-valued results.
Assuming that Γ |=aalg p satisfies Requirement 7.2 and that Γ ⊢×
db S <: T only if Γ 6⊢
225

S <: T (i.e.the database has not been corrupted), it is straight-forward to show that
the subtype algorithm Γ a S <: T satisfies Lemma 7.3.
Lemma 7.3 (Algorithmic Subtyping)
√

1. If Γ  S <: T then Γ ⊢ S <: T .
2. If Γ × T1 <: T2 then ∀Γ′ , S1 , S2 that are obtained from Γ, T1 , T2 by replacing the
type Dynamic by any type, we have that Γ′ 6⊢ S1 <: S2 .

7.6.4

Walkthrough of hybrid type checking in Sage
To illustrate in detail the process of hybrid type checking for Sage, this section

walks through an example, revisiting printf. Consider the following term
e

def

=

printf "%d" 4

For this term, the rule [C-App1] will first compile the subexpression (printf "%d") via
the following cast insertion judgment (based on the type of printf from Section 7.2.3):
∅  (printf "%d") ֒→ (printf "%d") : (Printf Args "%d")
The rule [C-App1] then calls the function unrefine to evaluate (Printf Args "%d") to
the normal form Int → Unit. Since 4 has type Int, the term e is therefore accepted as
is; no casts are needed.
However, the computation for (Printf Args "%d") may not terminate within
a preset time limit. In this case, rule [C-App2] applies to e:
(hDynamic → Dynamici(printf "%d")) 4
226

At run time, (printf "%d") will evaluate to some function (λx : Int. e′ ) that expects
an Int, yielding the application:
(hDynamic → Dynamici(λx : Int. e′ )) 4
The rule [E-Cast-Fn] then reduces this term to:
(λx : Dynamic. hDynamici((λx : Int. e′ ) (hIntix))) 4
where the nested cast hIntix dynamically ensures that the next argument to printf
must be an integer.

7.7

The Sage Architecture
The overall architecture of the Sage system is shown in Figure 7.13. The sub-

typing algorithm described formally earlier is where the novelty of the implementation
architecture lies, specifically in the interplay between a counterexample database and
an automated theorem prover.

7.7.1

Theorem Prover
Testing subtyping between refinement types reduces to testing implication be-

tween refinement predicates. In order to reason about these predicates, the subtype
algorithm translates each implication between Sage predicates into a validity test of
a logical formula, which can be passed to an automated theorem prover. Some Sage
terms may not readily translate to the language of the prover, in which case we may
227

Figure 7.13: Sage Architecture
Program
w/ Spec

Subtype
Algorithm

Theorem
Prover

Counter
Example
Database

Sage
Compiler

Static
Type Error

Compiled
Program

Sage
Run-Time
Recompile
X,Y,Z,...
Output

228

Dynamic
Type Error
w/ Counter
Example

either return ? or approximate the formula by one that is stronger (then a

√

answer

implies soundness, while × from the prover must be translated to ?) or one that is
weaker (in this case it is

7.7.2

√

that must be translated to ?).

Counter-Example Database
Suppose Sage inserts the cast (hT i e) because it cannot prove or refute some

subtype test Γ ⊢ S <: T . If that cast fails, the run time inserts an entry into the
database asserting that Γ 6⊢ S <: T (the syntactic representation of which is is Γ ⊢×
db
S <: T ) The subtype algorithm consults this database during type checking and will
subsequently reject any program that relies on S being a subtype of T . Thus, dynamic
type errors actually improve the ability of the Sage type checker to detect type errors
statically.
Moreover, when such a cast fails, Sage will report a list of known programs
that contain a cast that this counterexample refutes, since these programs may also fail
at run time. Thus, the counter-example database functions somewhat like a regression
test suite, in that it can detect errors in previously type-checked programs.
Function casts must be treated with care to ensure blame is assigned appropriately upon failure [Findler and Felleisen 2002]. In particular, if a cast inserted during
the lazy evaluation of a function cast fails, an entry for the original, top-level function
cast is inserted into the database in addition to the “smaller” cast on the argument or
return value. The “larger” counterexample more directly corresponds to the error in
the original source code, while the “smaller” counterexample refutes potentially more
229

subtyping queries. The details of this process have not been formally modeled.
Currently we prototype via a local database, but in practice this would take the
form of a “crash report” sent back to a program’s maintainer. Such reports have become
ubiquitous subsequent to this research; this confluence of our experiments and social
currents has opened up an exciting avenue by which to integrate our techniques into
common practice. Over time, we predict that the database will grow to be a valuable
repository of common but invalid subtype relationships, leading to further improvements
in the checker’s precision and less reliance on inserted casts.

7.8

Experimental Results
Our prototype Sage implementation consists of roughly 5,000 lines of OCaml

code, including parsing, desugaring, primitive definitions, operational semantics, and
cast insertion. The theorem prover used is Simplify [Detlefs et al. 2005] and the counterexample database is prototyped using a simple local file and serialized OCaml data
structures. The complete implementation is available at http://sage.soe.ucsc.edu/.
We evaluated the Sage language and implementation using the benchmarks
listed in Figure 7.1. The program arith.sage defines and uses a number of mathematical functions, such as min, abs, and mod, where refinement types provide precise specifications. The programs bst.sage and heap.sage implement and use binary search trees
and heaps, and the program polylist.sage defines and manipulates polymorphic lists.
The types of these data structures ensure that every operation preserves key invariants.

230

The program stlc.sage implements a type checker and evaluator for the simply-typed
lambda calculus (STLC), where Sage types specify that evaluating an STLC-term preserves its STLC-type. We also include the sorting algorithm mergesort.sage, as well
as the regexp.sage and printf.sage examples discussed earlier.
Figure 7.1 characterizes the performance of the subtype algorithm on these
benchmarks. We consider two configurations of this algorithm, both with and without
the theorem prover. For each configuration, the figure shows the number of subtyping
judgments proved (denoted by

√

), refuted (denoted by ×), and left undecided (denoted

by ?). The benchmarks are all well-typed, so no subtype queries are refuted. Note that
the theorem prover enables Sage to decide many more subtype queries. In particular,
many of the benchmarks include complex refinement types that use integer arithmetic
to specify ordering and structure invariants; theorem proving is particularly helpful in
verifying these benchmarks.
Our subtyping algorithm verifies a large majority of subtype tests. Only a small
number of undecided queries result in casts. For example, in regexp.sage, Sage cannot statically verify subtyping relations involving regular expressions (they are checked
dynamically) but it statically verifies all other subtype judgments. Some complicated
tests in stlc.sage and printf.sage must also be checked dynamically.
Despite the use of a theorem prover, type-checking times for these benchmarks
is quite manageable. On a 3GHz Pentium 4 Xeon processor running Linux 2.6.14,
type-checking required fewer than 10 seconds for each of the benchmarks, except for
polylist.sage which took approximately 18 seconds. We also measured the number of
231

Lines Without Prover
√
Benchmark
?
×
of code
arith.sage
45
132 13
0
bst.sage
62
344 28
0
69
322 34
0
heap.sage
mergesort.sage
80
437 31
0
397 2338
5
0
polylist.sage
228
321
1
0
printf.sage
regexp.sage
113
391
2
0
227
677 11
0
stlc.sage
Total
1221 4962 125
0

With Prover
√
? ×
145
0
0
372
0
0
356
0
0
468
0
0
2343
0
0
321
1
0
391
2
0
677 11
0
5073 14
0

Table 7.1: Subtyping Algorithm Statistics

evaluation steps required during each subtype test. We found that 83% of the subtype
tests required no evaluation, 91% required five or fewer steps, and only a handful of
the the tests in our benchmarks required more than 50 evaluation steps. Of course,
evaluation steps are not good measurements for run-time performance, as a mature
implementation may have a highly optimized method of normalizing types, but this
serves as a qualitative indication that the amount of evaluation is extremely small or
none.

232

Chapter 8
Contemporaneous Related Work

Subsequent to and simultaneous with the work presented here, many of the
fields with which this dissertation is in conversation have experienced advances from
other research. This chapter discusses this related work in four categories, roughly
parallel to those from Chapter 2: Dynamic contracts (Section 8.1), static contract
checking (Section 8.2), dependent/refinement types (Section 8.3), and gradual types
(Section 8.4).

8.1

Dynamic Contracts
The higher-order contracts of Findler and Felleisen [2002] have seen many ad-

vances since the research presented in this dissertation was undertaken. Let us consider
them in three broad categories: foundations for higher-order contracts and blame, explorations of contracts in typed languages, and static checking of contracts.

233

8.1.1

Foundations for untyped higher-order contracts and blame
Findler and Felleisen [2002] does not directly address the question of what it

means for a program to satisfy a contract. Instead, the meaning of contract satisfaction
is left implicit in the semantics of contract checking: If the contract does not blame a
piece of code for a violation, then that code obeys the contract. Subsequent examination
of the meaning of contract satisfaction and blame is closely related to the dynamic
checking of executable refinement types. In turn, the correspondence between refined
notions of correctness for dynamic checking and the firmly established definition of type
soundness is a primary contribution of variants of λH , especially λHTC
H .
Blume and McAllester [2006] and Findler and Blume [2006] explore denotational models of contracts in an untyped setting, proposing non-tautological, implementationindependent, definitions of contract satisfaction. In the process, they correct some
missed contract violations from Findler and Felleisen [2002]. The denotational semantics for executable refinement types of Chapter 3 are inspired by these denotational
models, but made simpler by leveraging the simply-typed λ-calculus.
In all of the above research on contracts, a contract is annotated with two
blame labels: One for the term it is meant to describe, and one for its context. If the
term violates its contract, then its label receives the blame. For example, the term 3 does
not satisfy the contract {x : Int | x = 4} so it will be blamed if the contract is applied.
On the other hand, if the context violates the contract of a term, then the context’s
label receives blame. For example, if f has the contract {x : Int | x > 0} → Int and the

234

context attempts to evaluate f 0, then the context is to blame for the resulting contract
violation.
Gronski and Flanagan [2007] question whether both blame labels are necessary, and answer ambivalently. They exhibit a mapping from a language λC with dynamic contracts that are independent of the type system to a language like λHTC
with
H
type casts that are tightly integrated into the type system. In λC , contract checks are
′

of the form hT i l,l – they have one contract and two blame labels. In their variant of
l
λHTC
H , type casts are of the form hT ⊳ Si – they have two types per cast but just one

blame label. A single blame label suffices per cast, but each contract check from λC is
interpreted as two composed casts carrying each label. In essence, the contract check
hT i l,l

′

is translated into the composed type casts
′

hT ⊳ ⌊T ⌋i l ◦ h⌊T ⌋ ⊳ T i l
where one cast only ever blames the term to which the contract is applied and one
cast only every blames its context. This decomposition corresponds with the reasoning
of Hinze et al. [2006] and Wadler and Findler [2009], each of whom disentangle the
“positive” and “negative” components of a contract.
Greenberg et al. [2012] follows up Gronski and Flanagan [2007] with an examination of whether a domain contract is rechecked when the input to a function appears
in the codomain contract, yielding distinct contract disciplines. When the domain contract is not rechecked, the system is called “lax”. A function contract (hx : T1 → T2 i f ) e
235

in in the lax system reduces to
h[x 7→ e] T2 i (f (hT1 i e))
When function contracts are reechecked, the system is called “picky”. In the picky
system, the same contract check reduces to
h[x 7→ hT1 i e] T2 i (f (hT1 i e))
The lax discipline misses violations of T1 in the codomain T2 , thus may lead to erroneous crashes; it is incorrect regardless of blame. Dimoulas and Felleisen [2011],
Dimoulas et al. [2011] and Dimoulas et al. [2012] further examine the two-label contract disciplines and expose that while the lax discipline misses some violations, the
picky system may blame the wrong module. This is resolved by introducing a third
blame label for the contract itself, treating it as an independent party mediating the
interaction between a term and its context. Notably, the lax/picky distinction is impossible in λHTC
H . Supposing f has original type x : S1 → S2 , the analogous term
(hx : T1 → T2 ⊳ x : S1 → S2 i f ) e reduces to
h[x 7→ e] T2 ⊳ [x 7→ hS1 ⊳ T1 i e] S2 i (f (hS1 ⊳ T1 i e))
This enforcement of a function contract is entirely derived from the types; there is no
choice of where to recheck the domain contract. The domain contract T1 is not rechecked
in T2 , because the type system makes any violation impossible. Conversely, it must be
rechecked in S2 , otherwise this term would be ill-typed. If S2 and T2 are function types,
then this does result in checks that are not present in the lax system, but fewer than
236

in the picky system. The type system has yielded an integral insight into exactly which
checks are needed, and why.

8.1.2

Higher-order contracts for lazy, typed languages
The foundational work for contracts took place in an untyped setting, but this

dissertation is rooted firmly in type theory. The theory of executable refinement types
provides one possible foundation for contracts in a typed setting. But alongside this
theoretical work, other research on the foundations of typed contracts has been driven
by implementations of typed higher-order contracts, generally as a library rather than
as a language construct.
Hinze et al. [2006] presents an implementation of contracts as a library for
Haskell. Unfortunately, applying a contract to a term can change the semantics of
the term by altering its strictness. Chitil et al. [2004], Chitil and Huch [2007a,b], and
Chitil [2011] develop a sound semantics for lazy assertions (which do not include blame
or higher-order functions) but Degen et al. [2009, 2012] demonstrates some cases in
which lazy assertions are not suitable for use in contracts due to incomplete contract
monitoring. Chitil [2012] presents an implementation of contracts including blame and
“picky” semantics in Haskell and briefly claims that it is easy to incorporate the “indy”
monitoring of Dimoulas et al. [2011].
The feasibility of implementing typed contracts as a library is a testament to
the power of modern type systems and languages, but executable refinement relate the
correctness of the contract system to the correctness of the type system in a way that
237

is not possible when contracts are implemented as a library. Hybrid type checking, for
example, would add safety while improving performance, but requires contracts and
types to share their specification language.
Executable refinement types are related to the discussion of laziness only to
the extent that laziness is inextricably related to concerns of complete monitoring, but
some of the properties promoted in arguing for the correctness of the implementations
may be interesting to consider both for the semantics of contract enforcement in λHTC
H
and for the denotational interpretation of executable refinement types.

8.1.3

Parametric polymorphism in contracts
The languages in this dissertation lack parametric polymorphism. While func-

tions in Sage may take a type as a parameter, the property of relational parametricity is
certainly not ensured. Parametric polymorphism is common in modern typed programming languages, but it is generally a feature of a static type system. The foundation of
dynamic enforcement of parametric polymorphism is more recent.
Pierce and Sumii [2000] and Sumii and Pierce [2004] connect the dynamic sealing of Morris [1973a,b] with parametricity using bisimulation, and this technique was
refined by Sangiorgi et al. [2007]. In these systems, polymorphic values are essentially
encrypted so that it is not possible to violate parametricity.
Guha et al. [2007] present initial approaches to using blame instead of encryption to enforce parametricity, then Matthews and Ahmed [2008] and Ahmed et al.
[2009] combine the technique with gradual typing (discussed below in Section 8.4).
238

Belo et al. [2011] supports a combination of executable refinement types and
polymorphic contracts, including refinements of any type. Unfortunately their refinement system is considerably weakened to only include closed refinements, and their type
system is noncompositional in a fundamental way, so the result is not directly applicable
nor comparable.
Combining the techniques presented in this dissertation and advances in dynamic enforcement of parametric polymorphism is a promising approach both to the
theoretical goal of building a unified theory of static and dynamic polymorphism and towards the pragmatic goal of an effective language with both hybrid types and guarantees
of parametricity.

8.2

Static Contract Checking
The field of static contract checking is broad and not all directly relevant, so

this section reviews only the most closely related work, and some highlights from the rest
of the field of “SMT as a platform” [Bierman et al. 2010]. In particular, ESC/Haskell
and Liquid Types are in direct conversation with work on hybrid type checking and
executable refinement types

8.2.1

ESC/Haskell
Xu [2006] introduces ESC/Haskell, named by analogy with ESC/Java, that

statically checks contract annotations in Haskell programs. ESC/Haskell verifies specifications by first inserting the symbol BAD where a contract failure would occur, then
239

attempting to eliminate all occurrences of BAD with inlining, symbolic evaluations, and
counterexample-guided unrolling. If the symbol remains in the program, then the program is rejected.
In order to discuss the correctness of ESC/Haskell, Xu et al. [2009] gives a
declarative definition of what it means for a term to satisfy a contract, similar to those
discussed in Section 8.1 and the denotational model of Section 3.3, and proves that
if ESC/Haskell eliminates all occurrences of BAD and the term is well-typed (like us,
leveraging the underlying type system in a crucial way) then the term satisfies the
contract.
Xu [2012] then changes context to O’Caml, a strict language, and makes two
significant changes: It leverages an SMT solver to determine reachability of code and
allows BAD to be left in the program optionally and checked at runtime. In the latter
situation no program is rejected statically, unlike HTC.

8.2.2

Liquid Types
Rondon et al. [2008] introduces logically qualified types, or “liquid types” for

short, which are comparable to executable refinement types in that they are based upon
predicates written in the programming language at hand, but given meaning by translation to SMT1 terms. These predicates are combined with the abstract interpretation
technique of predicate abstraction to automatically infer refinements including loop invariants, which are verified statically. A programmer omits all refinements from their
1

Satifiability Modulo Theories, or SMT, is a now-standard technique for automated theorem provers.

240

program but provides a set of template logical qualifiers such as {x > ⋆, ⋆ > x, x ≥ ⋆}
which are instantiated by replacing ⋆ with arbitrary program variables until constraints
are satisfied or the program is proven to be ill-typed.2 In addition to supporting nearly
full type reconstruction for programs whose predicates fall within the provided template language, Rondon et al. [2008] includes an implementation for O’Caml and hence
support ML-style polymorphism. Terauchi [2010] demonstrates how to omit the set
of template logical qualifiers by using counterexample-guided refinement and an interpolating theorem prover to generate new templates until either a program is found to
not be typable (vs the templates simply being too coarse) or a suitably fine-grained
refinement is discovered. Rondon et al. [2010] applies liquid types to C, demonstrating
the generality of the approach.
Kawaguchi et al. [2009] incorporates a modified form of dependent sum type
to use the liquid types technology for verifying recursively-defined data types. They
also automatically convert “measures”, designated functions defined via catamorphism,
into refinements on the constructors of a data type, the initial refinement design of
Freeman and Pfenning [1991].
Jhala et al. [2011] explains the underlying technology in another way by reframing type reconstruction steps analogous to those from Knowles and Flanagan [2007]
and Rondon et al. [2008] as a translation from a higher-order functional program to a
first-order imperative program whose verification implies the verification of the source
program. Chugh et al. [2012b] embeds the typing and subtyping relations themselves
2

The term “liquid” type plays two roles: both as name for the whole system, but also for types
whose refinements fall within the language of logical qualifiers.

241

into the SMT logic, in order to reason about them within refinement predicates in a
dynamically typed language.
Vazou et al. [2013] adds the ability for a function to be polymorphic over refinement predicates, so that even monomorphic functions such as max : [Int] → Int may be
given types such as [{x : Int | isP rime x}] → {x : Int | isP rime x}. While one could already write max as a function of type p : (Int → Int) → {x : Int | p x} → {x : Int | p x}
with equivalent proof obligations, this is extremely cumbersome and requires anticipation of all places where a refinement may be added.

8.2.3

Other work
Bengtson et al. [2008] augments the F# language with refinement predicates

written directly in an SMT-compatible constraint language to statically verify security
properties of programs such as encryption protocols and access control implementations.
Bierman et al. [2010] embeds the type relation of a small functional calculus
into the logic of SMT and shows that subtyping can then be implemented as implication
between predicate interpretations of types.

8.3

Dependent/Refinement Types
This section briefly discusses the continuation of the work on refinement types

other than those expressed as logical predicates ornamenting structural types. Programming directly in powerful languages based on Type Theory is now also becoming more
common, and each of these allows the direct expression of many of the ideas promoted
242

in refinement type systems.
The Stardust language [Dunfield 2007] serves as a good summary of the field, as
well as a novel system, by combining datasort refinements [Freeman and Pfenning 1991]
and index refinements [Xi and Pfenning 1999] into single language that also includes
intersection types [Davies and Pfenning 2000] and union types [Dunfield and Pfenning
2003, 2004].
Epigram [McBride and McKinna 2004], Agda [Norell 2009], and Coq [The Coq development team
2012] are each languages based on Type Theory where via propositions-as-types correspondence programmers may explicitly prove correctness of their programs (or any
other property they may choose). Proofs are generally quite challenging and manual,
but proponents often profess a great appreciation of the process of structuring their
programs to be provable. Each of these languages supports and promotes programming
with indexed types.
Atkey et al. [2011] and Dagand and McBride [2012] examine the foundations
of when a particular recursive function over a recursively-defined type may be expressed as an index to its recursive formulation, offering a foundational look at what
Kawaguchi et al. [2009] calls a “measure”.
Ωmega [Sheard 2005, 2007] is a proposal that combines indexed types and
user-defined kinds with common extensions of Haskell, specifically generalized algebraic
data types (GADTs, also a feature of all systems built on Type Theory) and typelevel functions, to provide many of the benefits of programming in Type Theory while
retaining a familiar Haskell-esque flavor.
243

Concoqtion [Fogarty et al. 2007] layers Coq proofs atop MetaOCaml to allow
manual proofs of precise specifications. Hoare Type Theory and its language YNot
[Nanevski et al. 2006, 2007, 2008, 2011, 2013] also blend Coq proofs into a programming language but specifically add monadic side-effecting computations which may be
reasoned about with Hoare-style axiomatic semantics that can be related to facts proven
about program values.
The balance between manual proof, automation and assurance is at stake in
all such systems. A program, viewed as a document explicating the problem it solves,
may be enhanced by explicit proofs, or its meaning may be obscured. One may also
ask whether it is necessary for the program to be written in the same language used for
carrying out proofs. Could a checker of executable refinement types generate proofs in
Coq or another metatheoretical framework? In what manner can manual proof or other
forms of automation be integrated into hybrid type checking?

8.4

Gradual Typing
Gradual typing [Siek and Taha 2006] is so named because it addresses the

problem of mixing typed and untyped program text while being able to recover the
root cause of errors. The formal techniques of gradual typing resemble those of hybrid
type checking, while the use of blame is derived directly from research on higher-order
contracts. Thus gradual typing is more closely related than it may appear at first.
Unlike refinement types, gradual typing is based not on subtyping but on an

244

idea of type consistency. Two types are consistent if they can be unified by replacing
occurrences of Dynamic in their syntax with arbitrary other types. For example Int →
Dynamic is consistent when Int → Int but also with simply Dynamic. Unlike subtyping,
compatibility cannot be transitive or else all types would be compatible. Gradual typing
formalizes an idea that is implicit is Sage’s algorithmic treatment of Dynamic.
Siek and Taha [2007] applies gradual typing to an object-oriented language,
adding subtyping alongside consistency. Siek and Vachharajani [2008] adds unificationbased type inference. Notably, many naı̈ve approaches to adding unification fail to
maintain the “gradual” flavor. Rastogi et al. [2012] explores dataflow-based type inference for gradual typing in the context of ActionScript. Sagonas and Luna [2008] applies
gradual typing to existing Erlang codebases, while Ren et al. [2013] does the same for
Ruby. Wadler and Findler [2009] examines the use of blame in a language blending
statically typed code and dynamically typed code, with contracts as an intermediary,
and proves that only the dynamically typed portion can ever receive blame. By a careful
tracking of blame labels, a new syntactic technique is used to prove soundness of a gradual typing system somewhat simpler than λH . Ahmed et al. [2009] incorporates recent
research on polymorphic contracts into a gradually typed language. Ina and Igarashi
[2009, 2011] apply this to a more mainstream object-oriented setting where polymorphism is referred to as “generics”.
Swamy et al. [2009] attempts a unified theory of type-directed coercion insertion. This would, if successful, apply to both hybrid type checking and gradual typing.
Subtyping, where it appears in HTC, is replaced by coercion generation. Instead of a
245

subsumption rule, the theory features coercion generation at those same points, in a
reorganization of the same reasoning. Consider this rule as an illustration:
Γ  hT ⊳ Si ֒→ c

Γ  d ֒→ e : S

Γ  d ֒→ hhcii(e) ↓ T
where Γ  hT ⊳ Si ֒→ c states that in order to coerce a term of type S to type T ,
the coercion c must be applied. In the event that c is the identity, hhcii(e) elides its
application, resulting in just e.3 This framework can generate a system equivalent to
hybrid type checking via the following two rules for generating coercions
√

Γ ? S <: T

Γ  S <: T

Γ  hT ⊳ Si ֒→ hT ⊳ Si

Γ  hT ⊳ Si ֒→ id

where id is a designated symbol and
hh hT ⊳ Si ii(e)

def

=

hT ⊳ Si e

hhidii(e)

def

e

=

Gradual typing may be similarly generated by using consistency checks to determine
the coercion.
Implementation of gradual typing faces performance challenges from the use of
higher-order coercions and also simply having dynamically typed portions. Siek and Wadler
[2009] and Herman et al. [2010] address the problem of the proliferation of function contract cast wrappers, which pose a serious problem to adoption of higher-order constracts
3

The operation hhcii is a metafunction carried out on the syntax, not a term of the language.

246

due to memory consumption. Allende and Fabry [2011] proposes some initial steps towards recovering type-based optimizations for programs using gradual typing.
Bayne et al. [2011] allows a programmer to work while their program is in
an ill-typed state by optionally deferring type errors until run time. Their proposal
works by replacing ill-typed terms by ⊥, which may be assigned any type. The Glasgow
Haskell Compiler now incorporates this feature. In a hybrid type checking system, this
may be viewed as optionally using a theorem prover that converts all “no” answers to
“maybe” during development.
Wolff et al. [2011] blends gradual typing with typestate, where imperative update may change the type of an imperative reference cell. In the context of executable
refinement types, this may present a way to temporarily abandon refinements and then
dynamically check that they have been restored.
Politz et al. [2012] takes a complementary approach to gradual typing: Instead
of offering a single form of soundness and allowing the program to omit types here and
there to control how much they take advantage of the guarantee, their system offers control over what guarantee the programmer wants and then enforces types appropriately.
This obviously applies to HTC, and especially Sage. Any HTC system incorporating
may present itself as different subtyping algorithms that are more or less confident in
their rejections of programs.

247

Chapter 9
Conclusion

9.1

Summary
Types are a proven and widespread language for describing programs rigor-

ously, but most type system express only very coarse specifications. Refinement types
increase the expressiveness of types, but remain limited in order to ensure decidable
and efficient checking. Dynamic contracts and extended static checking each support
more expressive specification languages that structural type system, but suffer from
incomplete checking and excessive false positives, respectively.
Executable refinement types enrich traditional type systems with precise specifications as expressive as those of dynamic contracts and extended static checking.
This expressiveness renders traditional forms of type checking and type reconstruction
undecidable.
This dissertation has introduced and explored executable refinement types

248

from theoretical and practical perspectives, yielding the following contributions:

- Executable refinement types may be interpreted via a lightweight denotational
semantics as sets of terms of the simply-typed λ-calculus. Based on this denotational semantics, it follows that implication and subtyping are proper preorders
and that the type system of λH is sound. In addition to type safety, this dissertation presents a new definition of extensional equivalence for dependently typed
calculi and proves that for λH extensional equivalence implies contextual equivalence.
- Chapter 4 defines hybrid type checking, a technique combining static type checking with dynamic contract checking, and applies it to executable refinement types
via the calculus λHTC
H . By employing hybrid type checking, we may ensure that all
specification violations are caught either statically or dynamically. Furthermore,
for any decidable approximation of the λHTC
type system, there is some program
H
which hybrid type checking rejects statically that the decidable approximation fails
to reject. Using our new proof extensional equivalence, we have quickly proved
that whenever a program is well-typed, all inserted run-time checks succeed.
- Type reconstruction as traditionally defined conflates the reconstruction of omitted types with the checking of well-typedness of a program. This conflation obscures useful distinctions between the difficulty of the type reconstruction problem
and the type checking problem, and renders the definition useless for undecidable
type systems. This dissertation proposes a novel definition of type reconstruction
249

that applies to undecidable type systems while coincides with the old definition for
decidable type systems. Using this new definition, a type reconstruction algorithm
for executable refinement types is presented.
- Standard formulations of dependent types rely on non-compositional reasoning,
violating the abstraction that types represent. We have shown how to restore
compositionality by a new use of existential types. Even with this formulation
that is restricted to compositional reasoning, type checking can still captures the
complete semantics of a term – the precision of the system is entirely parameterized
by the precision of the types of primitives. When all predicates are written in a
decidable language, compositional type checking of executable refinement types is
decidable.
- Sage is a prototype implementation of hybrid type checking for executable refinement types. Experiments show that type checking time – including use of an
SMT solver – is short and that the number of run-time checks inserted for many
examples is small. Sage also demonstrates an architecture whereby any run-time
failure is fed back into the static portion of the hybrid type checker and becomes
a static type error: Every run-time failure can happen at most once.

9.2

Open Avenues
In addition to laying a foundation for future work that has yet to be con-

ceived, this dissertation suggests concrete directions for research related to executable
250

refinement types.

9.2.1

Parametric Polymorphism
Parametric polymorphism is standard in modern type theories, and combines

well with static refinement type systems such as Dependent ML and Liquid Types. But
parametricity for dynamic contracts is still advancing.
The addition of parametric polymorphism demands a new development parallel
to this dissertation: While types of λH are easily interpreted as sets of ST LC terms,
does this approach immediately generalize to System F? How is extensional equivalence
affected? What are the consequences for hybrid type checking when the semantics of
relationally parametric contracts must be related to the semantics of polymorphic types?

9.2.2

Multiple Representations For Specifications
Executable refinement types are easy for certain purposes, such as translation

to an SMT prover, or communicating with other humans. But indexed types enforce
many interesting specifications by carefully architecture types for data constructors.
Atkey et al. [2011], Dagand and McBride [2012], and Vazou et al. [2013] all investigate
how to relate refinements on data constructors to global properties of a data structure.
This problem is far from solved, and integral to making executable refinement types
more usable. What specifications do we wish to write and which specifications do we
hope for an implementation to derive from them? Can this be automated sufficiently
that we can always write simply the global property of interest?

251

More generally, the cited works study equivalences between representations
that are best for different purposes: Some representations are more effective for static
checking, others more effective for human comprehension. The practice of hybrid type
checking introduces a third goal to start to consider: effective run-time checking. This
means using state-of-the-art data structures, potentially mutable data, and probably
much more obscure code. Dynamic contract systems support arbitrary specifications,
but connecting this representation to the former two remains incomplete.

9.2.3

Contract Properties
This dissertation has focused on establishing that executable refinement types

enjoy the desirable properties expected of a type system. However, contract systems
have other correctness properties, such as blame-correctness and complete monitoring.
When a counterexample is reported back to an implementation such as Sage, how does
correct blame affect e.g.the error message to report?

9.2.4

Compositionality
Compositionality of the sort analyzed in Chapter 6 is a property that de-

notational semantics generally enjoys, and closely parallels homomorphisms between
semantics (where a type system is a morphism between the syntactic identity semantics and the semantics that maps each term to its type). Especially considering this
in light of abstract interpretation may yield insight into the relationship between type
systems and abstract interpretation, further leading to advances in connections between

252

syntactic and semantic approaches to programming language theory.

9.2.5

Improving hybrid type checking
Our presentation of hybrid type checking is only proven to insert sufficiently

many casts to ensure safety. But, in fact, some components of casts are extraneous:
In a function subtyping question, if only the domain type needs a cast, the function
will nonetheless be fully wrapped. How to define the smallest number of casts, and
prove that an HTC system is optimal, is a moderately interesting theoretical question.
More realistically, an HTC implementation may simply informally develop techniques
for inserting casts that do not perform needless checks.

9.2.6

Side Effects
Side effects in contracts are a huge issue not directly part of this research

thread, but are urgent in this context due to the connection of dynamic contracts (where
side effects could be used on a “buyer beware” basis) and static semantics (where side
effects do not readily translate into simple specifications). JML actually had this issue
as well, because you want to use existing libraries. An alternative is to develop logical
libraries that do not depend on side effects. But these will generally not be efficient
to execute, again raising the problem of connecting efficient execution with effective
verification.

253

9.2.7

Error messages, Testing, and Counterexamples
Type error message in modern languages can already be difficult to interpret.

When a type incompatibility results from the inability to prove an implication between
refinement predicates – which may be very large, especially when they are generated
via type reconstruction – the problem is exacerbated. Mathematically, a constructive
way to refute a proposition is by providing a counterexample, which would also yield a
simple way to communicate an error message. But SMT is nonconstructive by design,
so new theorem proving techniques may need to be applied or developed in order to
generate readable error messages.
Counterexamples have a larger role to play. In Sage, they strengthen the
type checker by refuting more subtyping queries statically. Another good source of
counterexamples aside from a counterexample-producing theorem prover is a testing
process. Randomized testing tools such as QuickCheck [Claessen and Hughes 2000] and
DART [Godefroid et al. 2005] may be effective for enhancing the static capabilities of a
hybrid type checking system.

9.2.8

Large-Scale Implementation for Empirical Work
Sage demonstrates that executable refinement types can be incorporated into

a realistic programming language, but this has not yet been attempted. How powerful
does the counterexample database become when it receives crash reports from all users
of a system? How fully are proposed benefits of precise specification realized? For what
sorts of programs are executable refinement types most useful?
254

9.3

Closing Remarks
The theory of executable refinement types is sound and versatile. It combines

the strengths of type theory and dynamic contracts, benefiting from advances in each.
This exploration of executable refinement types has yielded not only new algorithms to
address the standard problems of type systems, but also new definitions of the problems. Through these new definitions have come new approaches to type checking, type
reconstruction, compositionality, and system architecture.
However, most of the executable refinement types of this dissertation are based
upon the simply-typed λ-calculus; their significance should not be overstated. They lack
parametric polymorphism, side-effects, and type classes, to name a few other features
we may want in a programming language. They are a foundation upon which to build
systems that more closely resemble today’s programming practice.
Conversely, programming practice should be influenced to move towards concepts with well-developed foundations. Concepts such as lexical scoping, higher-order
functions, structural types, and careful management of side effects all open the door
from common practice to the state of the art in programming languages, including
executable refinement types.
Just as this dissertation could take for granted the simply-typed λ-calculus as
a basis, future work may take for granted that executable refinement types are sound
in theory and effective in practice. New theories may build up from this point, and
practice may build towards it.

255

Bibliography

M. Abadi, L. Cardelli, P.-L. Curien, and J.-J. Levy. Explicit substitutions. In Proceedings of the 17th ACM SIGPLAN-SIGACT Symposium on Principles of Programming
Languages, POPL ’90, page 31–46, New York, NY, USA, 1990. ACM.
M. Abadi, L. Cardelli, B. Pierce, and G. Plotkin. Dynamic typing in a statically
typed language. ACM Transactions on Programming Languages and Systems, 13
(2):237–268, Apr. 1991.
A. Ahmed, R. B. Findler, J. Matthews, and P. Wadler. Blame for all. In Proceedings for
the 1st workshop on Script to Program Evolution, STOP ’09, page 1–13, New York,
NY, USA, 2009. ACM.
A. Aiken and E. L. Wimmers. Type inclusion constraints and type inference. In Proceedings of the conference on Functional programming languages and computer architecture, FPCA ’93, page 31–41, New York, NY, USA, 1993. ACM.
A. Aiken, E. L. Wimmers, and T. K. Lakshman. Soft typing with conditional types.
In Proceedings of the 21st ACM SIGPLAN-SIGACT symposium on Principles of
programming languages, POPL ’94, page 163–173, New York, NY, USA, 1994. ACM.
256

E. Allende and J. Fabry. Application optimization when using gradual typing. In
Proceedings of the 6th Workshop on Implementation, Compilation, Optimization of
Object-Oriented Languages, Programs and Systems, ICOOOLPS ’11, page 3:1–3:6,
New York, NY, USA, 2011. ACM.
R. Atkey, P. Johann, and N. Ghani. When is a type refinement an inductive type? In
Proceedings of the 14th international conference on Foundations of software science
and computational structures: part of the joint European conferences on theory and
practice of software, FOSSACS’11/ETAPS’11, page 72–87, Berlin, Heidelberg, 2011.
Springer-Verlag.
L. Augustsson. Cayenne - a language with dependent types. In Proceedings of the third
ACM SIGPLAN international conference on Functional programming, ICFP ’98, page
239–250, New York, NY, USA, 1998. ACM.
R. J. R. Back. A calculus of refinements for program derivations. Acta Inf., 25(6):
593–624, Aug. 1988.
H. Barendregt. An introduction to generalized type systems. Journal of Functional
Programming, 1(2):125–154, Apr. 1991.
M. Barnett, K. R. M. Leino, and W. Schulte. The spec# programming system: an
overview. In Proceedings of the 2004 international conference on Construction and
Analysis of Safe, Secure, and Interoperable Smart Devices, CASSIS’04, page 49–69,
Berlin, Heidelberg, 2005. Springer-Verlag.

257

M. Bayne, R. Cook, and M. D. Ernst. Always-available static and dynamic feedback.
In Proceedings of the 33rd International Conference on Software Engineering, ICSE
’11, page 521–530, New York, NY, USA, 2011. ACM.
J. F. Belo, M. Greenberg, A. Igarashi, and B. C. Pierce. Polymorphic contracts. In
G. Barthe, editor, Programming Languages and Systems, number 6602 in Lecture
Notes in Computer Science, pages 18–37. Springer Berlin Heidelberg, Jan. 2011.
J. Bengtson, K. Bhargavan, C. Fournet, A. D. Gordon, and S. Maffeis. Refinement types
for secure implementations. In Proceedings of the 2008 21st IEEE Computer Security
Foundations Symposium, CSF ’08, page 17–32, Washington, DC, USA, 2008. IEEE
Computer Society.
G. M. Bierman, A. D. Gordon, C. Hri\ctcu, and D. Langworthy. Semantic subtyping
with an SMT solver. In Proceedings of the 15th ACM SIGPLAN international conference on Functional programming, ICFP ’10, page 105–116, New York, NY, USA,
2010. ACM.
M. Blume and D. McAllester. Sound and complete models of contracts. J. Funct.
Program., 16(4-5):375–414, July 2006.
V. Breazu-Tannen, T. Coquand, C. A. Gunter, and A. Scedrov. Inheritance as implicit
coercion. Information and Computation, 93(1):172–221, July 1991.
K. B. Bruce, L. Cardelli, and B. C. Pierce. Comparing object encodings. Information
and Computation, 155(1-2):108–133, Nov. 1999.
258

L. Burdy, Y. Cheon, D. R. Cok, M. D. Ernst, J. R. Kiniry, G. T. Leavens, K. R. M.
Leino, and E. Poll. An overview of JML tools and applications. International Journal
of Software Tools for Technology Transfer, 7(3):212–232, June 2005.
L. Cardelli. Phase distinctions in type theory. Technical report, 1988.
O. Chitil. A semantics for lazy assertions. In Proceedings of the 20th ACM SIGPLAN
workshop on Partial evaluation and program manipulation, PEPM ’11, page 141–150,
New York, NY, USA, 2011. ACM.
O. Chitil. Practical typed lazy contracts. SIGPLAN Not., 47(9):67–76, Sept. 2012.
O. Chitil and F. Huch. Monadic, prompt lazy assertions in haskell. In Proceedings of
the 5th Asian Conference on Programming Languages and Systems, APLAS’07, page
38–53, Berlin, Heidelberg, 2007a. Springer-Verlag.
O. Chitil and F. Huch. A pattern logic for prompt lazy assertions in haskell. In Proceedings of the 18th International Conference on Implementation and Application of
Functional Languages, IFL’06, page 126–144, Berlin, Heidelberg, 2007b. SpringerVerlag.
O. Chitil, D. McNeill, and C. Runciman. Lazy assertions. In Proceedings of the 15th
International Conference on Implementation of Functional Languages, IFL’03, page
1–19, Berlin, Heidelberg, 2004. Springer-Verlag.
R. Chugh, D. Herman, and R. Jhala. Dependent types for JavaScript. In Proceedings of

259

the ACM international conference on Object oriented programming systems languages
and applications, OOPSLA ’12, page 587–606, New York, NY, USA, 2012a. ACM.
R. Chugh, P. M. Rondon, and R. Jhala. Nested refinements: a logic for duck typing.
SIGPLAN Not., 47(1):231–244, Jan. 2012b.
A. Church. A formulation of the simple theory of types. The Journal of Symbolic Logic,
5(2):56, June 1940.
K. Claessen and J. Hughes. QuickCheck: a lightweight tool for random testing of haskell
programs. In Proceedings of the Fifth ACM SIGPLAN International Conference on
Functional Programming, ICFP ’00, page 268–279, New York, NY, USA, 2000. ACM.
T. Coquand. An analysis of girard’s paradox. In In Symposium on Logic in Computer
Science, page 227–236. IEEE Computer Society Press, 1986.
P. Cousot and R. Cousot. Formal language, grammar and set-constraint-based program
analysis by abstract interpretation. In Proceedings of the seventh international conference on Functional programming languages and computer architecture, FPCA ’95,
page 170–181, New York, NY, USA, 1995. ACM.
S. Cui, K. Donnelly, and H. Xi. ATS: a language that combines programming with
theorem proving. In Proceedings of the 5th international conference on Frontiers of
Combining Systems, FroCoS’05, page 310–320, Berlin, Heidelberg, 2005. SpringerVerlag.
H. Curry and R. Feys. Combinatory Logic, volume 1. North-Holland, 1958.
260

H. B. Curry. Functionality in combinatory logic. Proceedings of the National Academy of
Sciences of the United States of America, 20(11):584–590, Nov. 1934. PMID: 16577644
PMCID: PMC1076489.
P.-E. Dagand and C. McBride. Transporting functions across ornaments. In Proceedings
of the 17th ACM SIGPLAN International Conference on Functional Programming,
ICFP ’12, page 103–114, New York, NY, USA, 2012. ACM.
R. Davies and F. Pfenning. Intersection types and computational effects. In Proceedings
of the Fifth ACM SIGPLAN International Conference on Functional Programming,
ICFP ’00, page 198–208, New York, NY, USA, 2000. ACM.
M. Degen, P. Thiemann, and S. Wehr. True lies: Lazy contracts for lazy languages. In
Arbeitstagung Programmiersprachen (ATPS’09), 2009.
M. Degen, P. Thiemann, and S. Wehr. The interaction of contracts and laziness. In
Proceedings of the ACM SIGPLAN 2012 workshop on Partial evaluation and program
manipulation, PEPM ’12, page 97–106, New York, NY, USA, 2012. ACM.
E. Denney.

Refinement types for specification.

In Proceedings of the IFIP

TC2/WG2.2,2.3 International Conference on Programming Concepts and Methods,
PROCOMET ’98, page 148–166, London, UK, UK, 1998. Chapman &amp; Hall, Ltd.
D. Detlefs, G. Nelson, and J. B. Saxe. Simplify: a theorem prover for program checking.
Journal of the ACM, 52(3):365–473, May 2005.

261

E. W. Dijkstra. A Discipline of Programming. Prentice Hall PTR, Upper Saddle River,
NJ, USA, 1st edition, 1976.
C. Dimoulas and M. Felleisen. On contract satisfaction in a higher-order world. ACM
Transactions on Programming Languages and Systems, 33(5):16:1–16:29, Nov. 2011.
C. Dimoulas, R. B. Findler, C. Flanagan, and M. Felleisen. Correct blame for contracts:
No more scapegoating. In Proceedings of the 38th Annual ACM SIGPLAN-SIGACT
Symposium on Principles of Programming Languages, POPL ’11, page 215–226, New
York, NY, USA, 2011. ACM.
C. Dimoulas, S. Tobin-Hochstadt, and M. Felleisen. Complete monitors for behavioral
contracts. In Proceedings of the 21st European conference on Programming Languages
and Systems, ESOP’12, page 214–233, Berlin, Heidelberg, 2012. Springer-Verlag.
D. Dreyer, K. Crary, and R. Harper. A type system for higher-order modules. In Proceedings of the 30th ACM SIGPLAN-SIGACT Symposium on Principles of Programming
Languages, POPL ’03, page 236–249, New York, NY, USA, 2003. ACM.
J. Dunfield. Refined typechecking with stardust. In Proceedings of the 2007 workshop
on Programming languages meets program verification, PLPV ’07, page 21–32, New
York, NY, USA, 2007. ACM.
J. Dunfield and F. Pfenning. Type assignment for intersections and unions in call-byvalue languages. In Proceedings of the 6th International conference on Foundations
of Software Science and Computation Structures and joint European conference on
262

Theory and practice of software, FOSSACS’03/ETAPS’03, page 250–266, Berlin, Heidelberg, 2003. Springer-Verlag.
J. Dunfield and F. Pfenning.

Tridirectional typechecking.

In Proceedings of the

31st ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages,
POPL ’04, page 281–292, New York, NY, USA, 2004. ACM.
M. Escardó. Synthetic topology of data types and classical spaces. In Electronic Notes
in Theoretical Computer Science, page 2004. Elsevier, 2004.
M. Fagan. Soft typing: an approach to type checking for dynamically typed languages.
PhD thesis, Rice University, Houston, TX, USA, 1991. UMI Order No. GAX91-36021.
M. Fahndrich and A. Aiken. Making set-constraint program analyses scale. Technical
report, University of California at Berkeley, Berkeley, CA, USA, 1996.
R. B. Findler and M. Blume. Contracts as pairs of projections. In Proceedings of the
8th international conference on Functional and Logic Programming, FLOPS’06, page
226–241, Berlin, Heidelberg, 2006. Springer-Verlag.
R. B. Findler and M. Felleisen. Contracts for higher-order functions. In Proceedings
of the seventh ACM SIGPLAN international conference on Functional programming,
ICFP ’02, page 48–59, New York, NY, USA, 2002. ACM.
C. Flanagan. Hybrid type checking. In Conference Record of the 33rd ACM SIGPLANSIGACT Symposium on Principles of Programming Languages, POPL ’06, page
245–256, New York, NY, USA, 2006. ACM.
263

C. Flanagan and M. Felleisen. Componential set-based analysis. In Proceedings of the
ACM SIGPLAN 1997 conference on Programming language design and implementation, PLDI ’97, page 235–248, New York, NY, USA, 1997. ACM.
C. Flanagan, M. Flatt, S. Krishnamurthi, S. Weirich, and M. Felleisen. Catching bugs in
the web of program invariants. In Proceedings of the ACM SIGPLAN 1996 Conference
on Programming Language Design and Implementation, PLDI ’96, page 23–32, New
York, NY, USA, 1996. ACM.
C. Flanagan, K. R. M. Leino, M. Lillibridge, G. Nelson, J. B. Saxe, and R. Stata. Extended static checking for java. In Proceedings of the ACM SIGPLAN 2002 Conference
on Programming language design and implementation, PLDI ’02, page 234–245, New
York, NY, USA, 2002. ACM.
R. Floyd. Assigning meaning to programs. In Proceedings of the Symposium in Applied
Mathematics: Mathematical Aspects of Computer Science, pages 19–32, 1967.
S. Fogarty, E. Pasalic, J. Siek, and W. Taha. Concoqtion: indexed types now! In Proceedings of the 2007 ACM SIGPLAN symposium on Partial evaluation and semanticsbased program manipulation, PEPM ’07, page 112–121, New York, NY, USA, 2007.
ACM.
T. Freeman and F. Pfenning. Refinement types for ML. In Proceedings of the ACM
SIGPLAN 1991 conference on Programming language design and implementation,
PLDI ’91, page 268–277, New York, NY, USA, 1991. ACM.

264

A. Frisch, G. Castagna, and V. Benzaken. Semantic subtyping: Dealing set-theoretically
with function, union, intersection, and negation types. Journal of the ACM, 55(4):
19:1–19:64, Sept. 2008.
Y.-C. Fuh and P. Mishra. Type inference with subtypes. Theoretical Computer Science,
73(2):155–175, Jan. 1988.
J.-Y. Girard. Interprétation fonctionnelle et élimination des coupures de l’arithmétique
d’ordre supérieur. 1972.
P. Godefroid, N. Klarlund, and K. Sen. DART: directed automated random testing. In
Proceedings of the 2005 ACM SIGPLAN conference on Programming language design
and implementation, PLDI ’05, page 213–223, New York, NY, USA, 2005. ACM.
M. Greenberg, B. c. Pierce, and S. Weirich. Contracts made manifest. Journal of
Functional Programming, 22(3):225–274, May 2012.
J. Gronski and C. Flanagan. Unifying hybrid types and contracts. In In Eighth Symposium on Trends in Functional Programming, 2007.
J. Gronski, K. Knowles, A. Tomb, S. N. Freund, and C. Flanagan. Sage: Hybrid
checking for flexible specifications. In Proceedings of the 7th workshop on Scheme and
functional programming, Portland, OR, 2006. ACM.
D. Grossman. Existential types for imperative languages. In Proceedings of the 11th European Symposium on Programming Languages and Systems, ESOP ’02, page 21–35,
London, UK, UK, 2002. Springer-Verlag.
265

A. Guha, J. Matthews, R. B. Findler, and S. Krishnamurthi. Relationally-parametric
polymorphic contracts. In Proceedings of the 2007 symposium on Dynamic languages,
DLS ’07, page 29–40, New York, NY, USA, 2007. ACM.
R. Harper and M. Lillibridge. A type-theoretic approach to higher-order modules with
sharing. In Proceedings of the 21st ACM SIGPLAN-SIGACT symposium on Principles of programming languages, POPL ’94, page 123–137, New York, NY, USA, 1994.
ACM.
S. Hayashi. Logic of refinement types. In Proceedings of the international workshop on
Types for proofs and programs, TYPES ’93, page 108–126, Secaucus, NJ, USA, 1993.
Springer-Verlag New York, Inc.
N. C. Heintze. Set based program analysis. PhD thesis, Carnegie Mellon University,
Pittsburgh, PA, USA, 1992. UMI Order No. GAX93-22866.
F. Henglein. Dynamic typing: syntax and proof theory. In Selected papers of the
symposium on Fourth European symposium on programming, ESOP’92, page 197–230,
Amsterdam, The Netherlands, The Netherlands, 1994. Elsevier Science Publishers B.
V.
D. Herman, A. Tomb, and C. Flanagan. Space-efficient gradual typing. Higher Order
Symbolic Compututation, 23(2):167–189, June 2010.
R. Hinze, J. Jeuring, and A. Löh. Typed contracts for functional programming. In In

266

FLOPS ’06: Functional and Logic Programming: 8th International Symposium, page
208–225. Springer-Verlag, 2006.
M. Hoang and J. C. Mitchell. Lower bounds on type inference with subtypes. In Proceedings of the 22nd ACM SIGPLAN-SIGACT symposium on Principles of programming
languages, POPL ’95, page 176–185, New York, NY, USA, 1995. ACM.
C. A. R. Hoare. An axiomatic basis for computer programming. Communications of
the ACM, 12(10):576–580, Oct. 1969.
R. C. Holt and J. R. Cordy. The turing programming language. Communications of
the ACM, 31(12):1410–1423, Dec. 1988.
W. A. Howard. The formulae-as-types notion of construction. To HB Curry: essays on
combinatory logic, lambda calculus and formalism, 44:479—490, 1980.
A. J. C. Hurkens. A simplification of girard’s paradox. In Proceedings of the Second
International Conference on Typed Lambda Calculi and Applications, TLCA ’95, page
266–278, London, UK, UK, 1995. Springer-Verlag.
L. Ina and A. Igarashi. Towards gradual typing for generics. In Proceedings for the 1st
workshop on Script to Program Evolution, STOP ’09, page 17–29, New York, NY,
USA, 2009. ACM.
L. Ina and A. Igarashi. Gradual typing for generics. In Proceedings of the 2011 ACM
international conference on Object oriented programming systems languages and applications, OOPSLA ’11, page 609–624, New York, NY, USA, 2011. ACM.
267

R. Jhala, R. Majumdar, and A. Rybalchenko. HMC: verifying functional programs using
abstract interpreters. In Proceedings of the 23rd international conference on Computer
aided verification, CAV’11, page 470–485, Berlin, Heidelberg, 2011. Springer-Verlag.
M. Kawaguchi, P. Rondon, and R. Jhala. Type-based data structure verification. In
Proceedings of the 2009 ACM SIGPLAN conference on Programming language design
and implementation, PLDI ’09, page 304–315, New York, NY, USA, 2009. ACM.
K. Knowles and C. Flanagan. Type reconstruction for general refinement types. In Proceedings of the 16th European conference on Programming, ESOP’07, page 505–519,
Berlin, Heidelberg, 2007. Springer-Verlag.
K. Knowles and C. Flanagan. Compositional reasoning and decidable checking for dependent contract types. In Proceedings of the 3rd workshop on Programming languages
meets program verification, PLPV ’09, page 27–38, New York, NY, USA, 2009. ACM.
K. Knowles and C. Flanagan. Hybrid type checking. ACM Transactions on Programming Languages and Systems, 32(2):6:1–6:34, Feb. 2010.
M. Kölling and J. Rosenberg. Blue - a language for teaching object-oriented programming. SIGCSE Bulletins, 28(1):190–194, Mar. 1996.
J. Lambek and P. J. Scott. Introduction to higher order categorical logic. Cambridge
University Press, New York, NY, USA, 1986.
G. T. Leavens and Y. Cheon. Design by Contract with JML. 2006.

268

X. Leroy, D. Doligez, A. Frisch, J. Garrigue, D. Rémy, and J. Vouillon. The OCaml
system release 4.01. Technical report, Sept. 2013.
P. Lincoln and J. C. Mitchell. Algorithmic aspects of type inference with subtypes.
In Proceedings of the 19th ACM SIGPLAN-SIGACT symposium on Principles of
programming languages, POPL ’92, page 293–304, New York, NY, USA, 1992. ACM.
D. C. Luckham. Programming with Specifications: An Introduction to Anna, a Language
for Specifying ADA Programs. Springer-Verlag New York, Inc., Secaucus, NJ, USA,
1990.
Y. Mandelbaum, D. Walker, and R. Harper. An effective theory of type refinements.
In Proceedings of the eighth ACM SIGPLAN international conference on Functional
programming, ICFP ’03, page 213–225, New York, NY, USA, 2003. ACM.
S. Marlow. Haskell 2010 language report, 2010.
P. Martin-Löf. An intuitionistic theory of types: Predicative part. In H.E. Rose and J.C.
Shepherdson, editor, Studies in Logic and the Foundations of Mathematics, volume
Volume 80 of Logic Colloquium ’73 Proceedings of the Logic Colloquium, pages 73–118.
Elsevier, 1975.
J. Matthews and A. Ahmed. Parametric polymorphism through run-time sealing or, theorems for low, low prices! In Proceedings of the Theory and Practice of Software, 17th
European Conference on Programming Languages and Systems, ESOP’08/ETAPS’08,
page 16–31, Berlin, Heidelberg, 2008. Springer-Verlag.
269

C. McBride. Faking it (simulating dependent types in haskell). Journal of Functional
Programming, 12(5):375–392, July 2002.
C. McBride and J. McKinna. The view from the left. Journal of Functional Programming, 14(1):69–111, Jan. 2004.
B. Meyer. Object-Oriented Software Construction. Prentice-Hall, Inc., Upper Saddle
River, NJ, USA, 1st edition, 1988.
B. Meyer. Eiffel: the language. Prentice-Hall, Inc., Upper Saddle River, NJ, USA, 1992.
J. C. Mitchell. Coercion and type inference. In Proceedings of the 11th ACM SIGACTSIGPLAN symposium on Principles of programming languages, POPL ’84, page
175–185, New York, NY, USA, 1984. ACM.
J. C. Mitchell and G. D. Plotkin. Abstract types have existential type. ACM Transactions on Programming Languages and Systems, 10(3):470–502, July 1988.
J. H. Morris. Protection in programming languages. Commun. ACM, 16(1):15–21, Jan.
1973a.
J. H. Morris. Types are not sets. In Proceedings of the 1st annual ACM SIGACTSIGPLAN symposium on Principles of programming languages, POPL ’73, page
120–124, New York, NY, USA, 1973b. ACM.
G. Morrisett, D. Walker, K. Crary, and N. Glew. From system f to typed assembly language. ACM Transactions on Programming Languages and Systems, 21(3):527–568,
May 1999.
270

A. Nanevski, A. Ahmed, G. Morrisett, and L. Birkedal. Abstract predicates and mutable adts in hoare type theory. In Proceedings of the 16th European conference on
Programming, ESOP’07, page 189–204, Berlin, Heidelberg, 2007. Springer-Verlag.
A. Nanevski, A. Banerjee, and D. Garg. Verification of information flow and access
control policies with dependent types. In Proceedings of the 2011 IEEE Symposium
on Security and Privacy, SP ’11, page 165–179, Washington, DC, USA, 2011. IEEE
Computer Society.
A. Nanevski, A. Banerjee, and D. Garg. Dependent type theory for verification of
information flow and access control policies. ACM Transactions on Programming
Languages and Systems, 35(2):6:1–6:41, July 2013.
A. Nanevski, G. Morrisett, and L. Birkedal. Polymorphism and separation in hoare type
theory. In Proceedings of the eleventh ACM SIGPLAN international conference on
Functional programming, ICFP ’06, page 62–73, New York, NY, USA, 2006. ACM.
A. Nanevski, G. Morrisett, A. Shinnar, P. Govereau, and L. Birkedal. Ynot: dependent
types for imperative programs. In Proceedings of the 13th ACM SIGPLAN international conference on Functional programming, ICFP ’08, page 229–240, New York,
NY, USA, 2008. ACM.
G. C. Necula, J. Condit, M. Harren, S. McPeak, and W. Weimer. CCured: type-safe
retrofitting of legacy software. ACM Transactions on Programming Languages and
Systems, 27(3):477–526, May 2005.

271

U. Norell. Dependently typed programming in agda. In Proceedings of the 4th international workshop on Types in language design and implementation, TLDI ’09, page
1–2, New York, NY, USA, 2009. ACM.
M. Odersky, M. Sulzmann, and M. Wehr. Type inference with constrained types. Theory
and Practice of Object Systems, 5(1):35–55, Jan. 1999.
X. Ou, G. Tan, Y. Mandelbaum, and D. Walker. Dynamic typing with dependent types.
In J.-J. Levy, E. W. Mayr, and J. C. Mitchell, editors, Exploring New Frontiers of
Theoretical Informatics, number 155 in IFIP International Federation for Information
Processing, pages 437–450. Springer US, Jan. 2004.
D. L. Parnas. On the criteria to be used in decomposing systems into modules. Communications of the ACM, 15(12):1053–1058, Dec. 1972a.
D. L. Parnas. A technique for software module specification with examples. Communications of the ACM, 15(5):330–336, May 1972b.
B. Pierce and E. Sumii. Relating cryptography and polymorphism. 2000.
B. C. Pierce. Types and programming languages. MIT Press, Cambridge, MA, USA,
2002.
B. C. Pierce. Advanced Topics in Types and Programming Languages. The MIT Press,
2004.
B. C. Pierce and D. N. Turner. Local type inference. In Proceedings of the 25th ACM

272

SIGPLAN-SIGACT symposium on Principles of programming languages, POPL ’98,
page 252–265, New York, NY, USA, 1998. ACM.
J. G. Politz, H. Quay-de la Vallee, and S. Krishnamurthi. Progressive types. In Proceedings of the ACM international symposium on New ideas, new paradigms, and
reflections on programming and software, Onward! ’12, page 55–66, New York, NY,
USA, 2012. ACM.
F. Pottier.

Simplifying subtyping constraints.

In Proceedings of the First ACM

SIGPLAN International Conference on Functional Programming, ICFP ’96, page
122–133, New York, NY, USA, 1996. ACM.
A. Rastogi, A. Chaudhuri, and B. Hosmer. The ins and outs of gradual type inference. In
Proceedings of the 39th annual ACM SIGPLAN-SIGACT symposium on Principles of
programming languages, POPL ’12, page 481–494, New York, NY, USA, 2012. ACM.
B. M. Ren, J. Toman, T. S. Strickland, and J. S. Foster. The ruby type checker. In
Proceedings of the 28th Annual ACM Symposium on Applied Computing, SAC ’13,
page 1565–1572, New York, NY, USA, 2013. ACM.
P. Rondon, A. Bakst, M. Kawaguchi, and R. Jhala. CSolve: verifying c with liquid types.
In Proceedings of the 24th international conference on Computer Aided Verification,
CAV’12, page 744–750, Berlin, Heidelberg, 2012. Springer-Verlag.
P. M. Rondon, M. Kawaguchi, and R. Jhala. Low-level liquid types. In Proceedings of

273

the 37th Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming
Languages, POPL ’10, page 131–144, New York, NY, USA, 2010. ACM.
P. M. Rondon, M. Kawaguci, and R. Jhala. Liquid types. In Proceedings of the 2008
ACM SIGPLAN conference on Programming language design and implementation,
PLDI ’08, page 159–169, New York, NY, USA, 2008. ACM.
J. Rushby, S. Owre, and N. Shankar. Subtypes for specifications: Predicate subtyping
in PVS. IEEE Transactions on Software Engineering, 24(9):709–720, Sept. 1998.
K. Sagonas and D. Luna. Gradual typing of erlang programs: a wrangler experience. In
Proceedings of the 7th ACM SIGPLAN workshop on ERLANG, ERLANG ’08, page
73–82, New York, NY, USA, 2008. ACM.
D. Sangiorgi, N. Kobayashi, and E. Sumii. Logical bisimulations and functional languages. In Proceedings of the 2007 international conference on Fundamentals of software engineering, FSEN’07, page 364–379, Berlin, Heidelberg, 2007. Springer-Verlag.
D. S. Scott. A type-theoretical alternative to ISWIM, CUCH, OWHY. 1969.
T. Sheard. Putting curry-howard to work. In Proceedings of the 2005 ACM SIGPLAN
workshop on Haskell, Haskell ’05, page 74–85, New York, NY, USA, 2005. ACM.
T. Sheard. Type-level computation using narrowing in &#937;mega. Electron. Notes
Theor. Comput. Sci., 174(7):105–128, June 2007.
J. Siek and W. Taha. Gradual typing for functional languages. In Proceedings of the
7th workshop on Scheme and functional programmin, 2006.
274

J. Siek and W. Taha. Gradual typing for objects. In Proceedings of the 21st European
conference on Object-Oriented Programming, ECOOP’07, page 2–27, Berlin, Heidelberg, 2007. Springer-Verlag.
J. G. Siek and M. Vachharajani. Gradual typing with unification-based inference. In
Proceedings of the 2008 symposium on Dynamic languages, DLS ’08, page 7:1–7:12,
New York, NY, USA, 2008. ACM.
J. G. Siek and P. Wadler. Threesomes, with and without blame. In Proceedings for the
1st workshop on Script to Program Evolution, STOP ’09, page 34–46, New York, NY,
USA, 2009. ACM.
M. Sperber, R. K. Dybvig, M. Flatt, A. V. Straaten, R. Kelsey, W. Clinger, J. Rees,
R. B. Findler, and J. Matthews. The revised report on the algorithmic language
scheme. Technical report, Sept. 2007.
R. Statman. Logical relations and the typed lambda-calculus. pages 85–97, 1985.
C. A. Stone and R. Harper. Deciding type equivalence in a language with singleton
kinds. In Proceedings of the 27th ACM SIGPLAN-SIGACT symposium on Principles
of programming languages, POPL ’00, page 214–227, New York, NY, USA, 2000.
ACM.
D. Stoutamire and S. Omohundro. The sather 1.1 specification. 1996.
E. Sumii and B. C. Pierce. A bisimulation for dynamic sealing. SIGPLAN Notices, 39
(1):161–172, Jan. 2004.
275

N. Swamy, M. Hicks, and G. M. Bierman. A theory of typed coercions and its applications. In Proceedings of the 14th ACM SIGPLAN international conference on
Functional programming, ICFP ’09, page 329–340, New York, NY, USA, 2009. ACM.
D. Tarditi, G. Morrisett, P. Cheng, C. Stone, R. Harper, and P. Lee. TIL: a type-directed
optimizing compiler for ML. In Proceedings of the ACM SIGPLAN 1996 conference
on Programming language design and implementation, PLDI ’96, page 181–192, New
York, NY, USA, 1996. ACM.
T. Terauchi. Dependent types from counterexamples. In Proceedings of the 37th Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages,
POPL ’10, page 119–130, New York, NY, USA, 2010. ACM.
S. Thatte. Quasi-static typing. In Proceedings of the 17th ACM SIGPLAN-SIGACT
symposium on Principles of programming languages, POPL ’90, page 367–381, New
York, NY, USA, 1990. ACM.
The Coq development team. The coq proof assistant reference manual. Technical report,
2012.
N. Vazou, P. M. Rondon, and R. Jhala. Abstract refinement types. In Proceedings of
the 22nd European conference on Programming Languages and Systems, ESOP’13,
page 209–228, Berlin, Heidelberg, 2013. Springer-Verlag.
P. Wadler and R. B. Findler. Well-typed programs can’t be blamed. In Proceedings
of the 18th European Symposium on Programming Languages and Systems: Held as
276

Part of the Joint European Conferences on Theory and Practice of Software, ETAPS
2009, ESOP ’09, page 1–16, Berlin, Heidelberg, 2009. Springer-Verlag.
G. Winskel. The formal semantics of programming languages: an introduction. MIT
Press, Cambridge, MA, USA, 1993.
R. Wolff, R. Garcia, É. Tanter, and J. Aldrich. Gradual typestate. In Proceedings
of the 25th European conference on Object-oriented programming, ECOOP’11, page
459–483, Berlin, Heidelberg, 2011. Springer-Verlag.
A. K. Wright and R. Cartwright. A practical soft type system for scheme. SIGPLAN
Lisp Pointers, VII(3):250–262, July 1994.
H. Xi. Imperative programming with dependent types. In Proceedings of the 15th Annual
IEEE Symposium on Logic in Computer Science, LICS ’00, page 375–, Washington,
DC, USA, 2000. IEEE Computer Society.
H. Xi and F. Pfenning. Dependent types in practical programming. In Proceedings of the
26th ACM SIGPLAN-SIGACT symposium on Principles of programming languages,
POPL ’99, page 214–227, New York, NY, USA, 1999. ACM.
D. N. Xu. Extended static checking for haskell. In Proceedings of the 2006 ACM
SIGPLAN workshop on Haskell, Haskell ’06, page 48–59, New York, NY, USA, 2006.
ACM.
D. N. Xu. Hybrid contract checking via symbolic simplification. In Proceedings of

277

the ACM SIGPLAN 2012 workshop on Partial evaluation and program manipulation,
PEPM ’12, page 107–116, New York, NY, USA, 2012. ACM.
D. N. Xu, S. Peyton Jones, and K. Claessen. Static contract checking for haskell. In
Proceedings of the 36th Annual ACM SIGPLAN-SIGACT Symposium on Principles
of Programming Languages, POPL ’09, page 41–52, New York, NY, USA, 2009. ACM.
C. Zenger. Indexed types. Theoretical Compututer Science, 187(1-2):147–165, Nov.
1997.

278

