The cvc5 types for terms, sorts, solvers, etc. are all related to a notion of (term) manager responsible for allocating and indexing terms and sorts. These types cannot interact and will produce a runtime error if they do not agree on their manager.
This API abstracts away the term manager completely and enforces manager-consistency statically using a type-class called Ω. Virtually all types in this library have an implicit Ω argument that can be thought of as representing a specific term manager. On the sort type for instance:
The current scope: an instance stands for one term manager.
Only Env.run and its variants make one, so a sort, term or solver cannot leave the run it was
made in, nor meet one made in another run, nested runs included.
Ω is tied to cvc.lean's environment monad Env: Envα, or more precisely @Envinstα where (inst:Ω), corresponds to code that uses a specific term manager to produce a value of type α.
Cvc5's term manager handles term/sort perfect sharingMarginal noteAlso known as hash-consing: building the same term twice produces the same (reference to the same) term, meaning physical (reference) equality is structural equality..
The first problem Ω and Env solve is that manipulating a value v tied to manager m after m has been freed is unsafe as freeing m frees v, so v should only live as long as m is alive. So most features in this library produce an Env_ output.
Second, as mentioned above, values tied to different managers should never interact as that would cause a runtime error. By encoding a specific term manager through an Ω-value, cvc.lean can type-check that sorts/terms/solvers/etc. are compatible by making sure they agree on their Ω-value.
For example, the function building an array-sort makes sure the index and element sorts agree on their manager, and keeps track of the manager associated to the resulting sort by having all of them agree on their Ω.
Be careful that, in practice, EnvT works on monads that lift BaseIO so that it can call the cvc5 C++ API.Marginal noteAlso for maintaining its state efficiently as a mutable reference.Env is simply EnvTBaseIO.
The only way to run Env-code is through its run-like functions. The simplest one is Env.run and produces a BaseIO(Resα) where Res is Except specialized to cvc.lean's Error type.
Notice that this function quantifies the Ω-instance inst for code. This guarantees that whatever α is, it cannot mention inst in any way, so it is not possible for code to return, say, an Srt-value as it would require inst to escape its scope.Marginal noteThis design is heavily inspired by Lean's ST/ST.Ref.
Now, to manipulate Env or virtually any type from cvc.lean we need to have an Ω-instance in scope. We could have [Ω] as an argument of all our definitions, but it is often easier to have it as a variable.
We can now forget about Ω completely and write a few Srt-using Env functions:Marginal noteThe only time you should be reminded of Ω is when you are doing something memory-unsafe such as mixing terms/sorts/solvers.
Using a cvc.lean type after its manager has been freed is rejected due to the quantification of the Ω instance. While this guarantees no use-after-free, the error message is a regular lean type-error and its meaning is pretty obscure.
defmkSort:EnvSrt:=Srt.int#evaldoletsortWithDeadTermManager←Env.runIOType mismatchmkSorthas typeEnvSrtbut is expected to have typeEnv?m.6Note: The following definitions were not unfolded because their definition is not exposed:Srt ↦ 2mkSortprintln!"dangling pointer: {sortWithDeadTermManager}"
Type mismatchmkSorthas typeEnvSrtbut is expected to have typeEnv?m.6Note: The following definitions were not unfolded because their definition is not exposed:Srt ↦ 2
The note closing the message comes from Lean and can be ignored: every sort error of this kind carries it.
Using a sort in an environment different from its native one is also a type error.
defmkBoolSort:EnvSrt:=Srt.bool#evalEnv.runIOdo-- let's call `ω` the current environmentletint←Srt.intEnv.runIOdo-- we're now in a different environment `ω'`letbool←mkBoolSortletarray←synthesized type class instance is not definitionally equal to expression inferred by typing rules, synthesizedinst✝inferredinst✝¹int.arrayToApplication type mismatch: The argumentboolhas type@Srtinst✝but is expected to have type@Srtinst✝¹in the applicationint.arrayToboolNote: The following definitions were not unfolded because their definition is not exposed:Srt ↦ 6bool
Application type mismatch: The argumentboolhas type@Srtinst✝but is expected to have type@Srtinst✝¹in the applicationint.arrayToboolNote: The following definitions were not unfolded because their definition is not exposed:Srt ↦ 6