This directory contains "white-box" tests that call library functions directly, often using the Testutils utilities to construct terms by hand.
Many of these tests use the interaction facilities in Testutil.Pmp
and Testutil.Mcp
, which were written before it was possible to typecheck definitions of constants. (The difference between the two is in the parser they use, see below.) Thus, they add variables to the context rather than defining constants, and store terms and values in OCaml variables. The basic interaction functions in these packages are:
synth M
-- Synthesize a type from a termM
and return the corresponding "value" ofM
as well as the synthesized type (also a value).check M T
-- Check the termM
against the typeT
, which must be a value. Returns the value ofM
.assume "x" T
-- Assume a variablex
of typeT
(a value), and return that variable (as a value).equal R S
-- Assert that two values are definitionally equal. They must be synthesizing, since this does no type-directed checking.equal_at R S T
-- Assert that two valuesR
andS
are definitionally equal at the value typeT
(which they are assumed to belong to).unsyth M
-- Verify that the termM
doesn't synthesize.uncheck M T
-- Verify that the termM
doesn't typecheck at value typeT
.unequal R S
-- Assert that two synthesizing values are not definitionally equal.unequal_at R S T
-- Assert that two valuesR
andS
are not definitionally equal at the value typeT
.
In particular, a common idiom is
let theorem_ty, _ = synth "..."
let theorem = check "..." theorem_ty
That is, we first write the type of a theorem, synthesize it (its type will be Type
, which we discard), and then check a proof of that theorem against the resulting value. Of course this can be shortcut with
let theorem = check "..." (fst (synth "..."))
Since there wasn't a built-in notion of "definition", when a term will be used again later, it was convenient to give a name to its raw syntax also, e.g.
let rdefn = "..."
let defn = check rdefn defn_ty
Some of these tests also use the "poor man's parser" from Testutil.Pmp
, which is implemented as a DSL in OCaml with infix and prefix operators. This is largely for historical reasons, since these tests were written before the actual parser, but it also means those tests don't depend on the parser.
!!"x"
-- Use a variable (an OCaml string).!~"c"
-- Use a built-in constant, such asSig
orGel
(also an OCaml string).!."c"
-- Use a built-in datatype constructor (an OCaml string).UU
-- The universeM $ N
-- Function application (left-associative)."x" @-> M
-- Lambda-abstraction (right-associative). The variable must be an OCaml string.("x", M) @=> N
-- Pi-type (right-associative).M $. "fld"
-- Field access of a record (left-associative). The field is an OCaml string.struc [("fld1", M); ("fld2", N)]
-- Anonymous record (structure).M <:> N
-- Type ascription.id M X Y
-- Identity/bridge type.refl M
-- Reflexivity.sym M
-- Symmetry.
Here the associativities and precedence are determined by the uniform rules of OCaml, based on the first character of each infix operator. In particular, this means that @->
and @=>
bind tighter than $
, so you have to write "x" @-> (M $ !!"x")
but you can write !!"f" $ "x" @-> M
. (This is the opposite of the "real" parser described above.)