Elaborating dependent (co)pattern matching: No pattern left behind

In a dependently typed language, we can guarantee correctness of our programmes by providing formal proofs. To check them, the typechecker elaborates these programs and proofs into a lowlevel core language. However, this core language is by nature hard to understand by mere humans, so how can we know we proved the right thing? This question occurs in particular for dependent copattern matching, a powerful language construct for writing programmes and proofs by dependent case analysis and mixed induction/coinduction. A definition by copattern matching consists of a list of clauses that are elaborated to a case tree, which can be further translated to primitive eliminators. In previous work this second step has received a lot of attention, but the first step has been mostly ignored so far. We present an algorithm elaborating definitions by dependent copattern matching to a core language with inductive data types, coinductive record types, an identity type, and constants defined by well-typed case trees. To ensure correctness, we prove that elaboration preserves the firstmatch semantics of the user clauses. Based on this theoretical work, we reimplement the algorithm used by Agda to check left-hand sides of definitions by pattern matching. The new implementation is at the same time more general and less complex, and fixes a number of bugs and usability issues with the old version. Thus, we take another step towards the formally verified implementation of a practical dependently typed language.


Introduction
Dependently typed functional languages such as Agda (2017), Coq (INRIA, 2017), Idris (2013), and Lean (de Moura et al., 2015) combine programming and proving into one language, so they should be at the same time expressive enough to be useful and simple enough to be sound. These apparently contradictory requirements are addressed by having two languages: a high-level surface language that focuses on expressivity and a small core language that focuses on simplicity. The main role of the typechecker is to elaborate the high-level surface language into the low-level core.
Since the difference between the surface and core languages can be quite large, the elaboration process can be, well, elaborate. If there is an error in the elaboration process, 2 J. Cockx and A. Abel our programme or proof may still be accepted by the system but its meaning is not what was intended (Pollack, 1998). In particular, the statement of a theorem may depend on the correct behaviour of some defined function, so if something went wrong in the elaboration of these definitions, the theorem statement may not be what it seems. As an extreme example, we may think we have proven an interesting theorem when in fact, we have only proven something trivial. This may be detected in a later phase when trying to use this proof, or it may not be detected at all. Unfortunately, there is no bulletproof way to avoid such problems: each part of the elaboration process has to be verified independently to make sure it produces something sensible.
One important part of the elaboration process is the elaboration of definitions by dependent pattern matching (Coquand, 1992). Dependent pattern matching provides a convenient high-level interface to the low-level constructions of case splitting, structural induction, and specialization by unification. The elaboration of dependent pattern matching goes in two steps: first the list of clauses given by the user is translated to a case tree, and then the case tree is further translated to a term that only uses the primitive data type eliminators. 2 The second step has been studied in detail and is known to preserve the semantics of the case tree precisely (Goguen et al., 2006;Cockx, 2017). In contrast, the first step has received much less attention.
The goal of this paper is to formally describe an elaboration process of definitions by dependent pattern matching to a well-typed case tree for a realistic dependently typed language. Compared to the elaboration processes described by Norell (2007) and Sozeau (2010), we make the following improvements: • We include both pattern and copattern matching.
• We are more flexible in the placement of forced patterns. • We prove that the translation preserves the first-match semantics of the user clauses.
We discuss each of these improvements in more detail below.
Copatterns. Copatterns provide a convenient way to define and reason about infinite structures such as streams . They can be nested and mixed with regular patterns. Elaboration of definitions by copattern matching has been studied for simply typed languages by Setzer et al. (2014), but so far the combination of copatterns with general dependent types has not been studied in detail, even though it has already been implemented in Agda.
One complication when dealing with copatterns in a dependently typed language is that the type of a projection can depend on the values of the previous projections. For example, define the coinductive type CoNat of possibly infinite natural numbers by the two projections iszero : Bool and pred : iszero≡ Bool false → CoNat. We use copatterns to define the co-natural number cozero: cozero : CoNat cozero .iszero = true cozero .pred ∅ Here, the constant cozero is being defined with the field iszero equal to true (and no value for pred).
To refute the proof of cozero .iszero≡ Bool false with an absurd pattern ∅, the typechecker needs to know already that cozero .iszero = true, so it needs to check the clauses in the right order.
This example also shows that with mixed pattern/copattern matching, some clauses can have more arguments than others, so the typechecker has to deal with variable arity. This means that we need to consider introducing a new argument as an explicit node in the constructed case tree.
Flexible placement of forced patterns. When giving a definition by dependent pattern matching that involves forced patterns, 3 there are often multiple positions where to place them. For example, in the proof of symmetry of equality: it should not matter if we instead write sym x x refl = refl. In fact, we even allow the apparently non-linear definition sym x x refl = refl.
Our elaboration algorithm addresses this by treating forced patterns as laziness annotations: they guarantee that the function will not match against a certain argument. This allows the user to be free in the placement of the forced patterns. For example, it is always allowed to write zero instead of zero , or x instead of x .
With our elaboration algorithm, it is easy to extend the pattern syntax with forced constructor patterns such as suc n (Brady et al. (2003)'s presupposed-constructor patterns). These allow the user to annotate that the function should not match on the argument but still bind some of the arguments of the constructor.
Preservation of first-match semantics. Like Augustsson (1985) and Norell (2007), we allow the clauses of a definition by pattern matching to overlap and use the first-match semantics in the construction of the case tree. For example, when constructing a case tree from the definition: we do not get max x zero = x but only max (suc x ) zero = suc x . This makes a difference for dependent type checking where we evaluate open terms with free variables like x. In this paper, we provide a proof that the translation from a list of clauses to a case tree preserves the first-match semantics of the clauses. More precisely, we prove that if the arguments given to a function match a clause and all previous clauses produce a mismatch, 4 then the case tree produced by elaborating the clauses also computes for the given arguments and the result is the same as the one given by the clause. 4 J. Cockx and A. Abel

Contributions
• We present a dependently typed core language with inductive data types, coinductive record types, and an identity type. The language is focused (Andreoli, 1992;Zeilberger, 2008;Krishnaswami, 2009): terms of our language correspond to the non-invertible rules to introduce and eliminate these types, while the invertible rules constitute case trees. • We are the first to present a coverage checking algorithm for fully dependent copatterns. Our algorithm desugars deep copattern matching to well-typed case trees in our core language. • We prove correctness: if the desugaring succeeds, then the behaviour of the case tree corresponds precisely to the first-match semantics of the given clauses. • We have implemented a new version of the algorithm used by Agda for checking the left-hand sides of a definition by dependent (co)pattern matching. This re-implementation has been released 5 as part of Agda 2.5.4; it uncovers and fixes multiple issues in the old implementation (Agda issue, 2017a(Agda issue, ,b,c,d, 2018a. Our algorithm could also be used by other implementations of dependent pattern matching such as Idris (2013), Lean (de Moura et al., 2015), and the Equations package for Coq (Sozeau, 2010).
Compared to the conference version of this paper (Cockx & Abel, 2018), we add the following: • We give a more fine-grained small-step semantics for our core language, which allows us to state and prove type preservation without assuming normalization (see Theorem 17). 6 • We add a discussion on the treatment of catch-all clauses by the algorithm described in this paper and how it differs from the implementation used by Agda (see Section 2.6). • We include the detailed proofs that were omitted from the conference version.
This paper was born out of a practical need that arose while reimplementing the elaboration algorithm for Agda: it was not clear to us what exactly we wanted to implement, and we did not find sufficiently precise answers in the existing literature. Our main goal in this paper is therefore to give a precise description of the language, the elaboration algorithm, and the high-level properties we expect them to have. This also means we do not focus on fully developing the metatheory of the language or giving detailed proofs for all the basic properties one would expect.
We start by introducing definitions by dependent (co)pattern matching and our elaboration algorithm to a case tree by a number of examples in Section 2. We then describe our core language in Section 3: the syntax, the rules for typing and equality, and the evaluation 5 Agda 2.5.4 released on 2018/06/02, changelog: https://hackage.haskell.org/package/Agda-2.5.4/ changelog. 6 In most dependently typed languages, a termination checker ensures normalization of recursive functions.
Likewise, a productivity checker can ensure normalization in the presence of infinite structures defined by copatterns. However, by proving preservation independently from normalization, we allow our approach to be extended to languages without such checks (such as Haskell), while at the same time making the metatheory more modular. Elaborating dependent (co)pattern matching 5 rules. In Section 4, we give the syntax and rules for case trees, and prove that a function defined by a well-typed case tree satisfies type preservation and progress. Finally, in Section 5, we describe the rules for elaborating a definition by dependent (co)pattern matching to a well-typed case tree, and prove that this translation preserves the computational meaning of the given clauses. Section 6 discusses related work, and Section 7 concludes.

Elaborating dependent (co)pattern matching by example
Before we move on to the general description of our core language and the elaboration process, we give some examples of definitions by (co)pattern matching and how our algorithm elaborates them to a case tree. The elaboration works on a configuration P | u : C, called a lhs problem, consisting of: • A context , i.e. a list of variables annotated with types. Initially, is the empty context ε. • The current target type C. This type may depend on variables bound in . Initially, C is the type of the function being defined. • A representation of the 'self' object u, which is a term of type C in context .
Initially, u is the function being defined itself. In each leaf of the case tree, u will represent the left-hand side of the clause, where certain variables might be specialized due to overlap with previous clauses. • A list of partially deconstructed user clauses P. Initially, these are the clauses as written by the user.
These four pieces of data together describe the current state of elaborating the definition. The elaboration algorithm transforms this state step by step until the user clauses are deconstructed completely. In the examples below, we annotate each step with a label such as SPLITCON or INTRO, linking it to the general rules given in Section 5.

A first example: Maximum of two numbers
Let us define a function max : N → N → N by pattern matching as in the introduction (3). The initial lhs problem is P 0 | max : N → N → N where: The first operation we need is to introduce a new variable m (rule INTRO). It transforms the initial problem into (m : N) P 1 | max m : N → N where: This operation strips the first user pattern from each clause and replaces it by a constraint m / ? p that it should be equal to the newly introduced variable m. We write these constraints between brackets in front of each individual clause.

6
J. Cockx and A. Abel The next operation we need is to perform a case analysis on the variable m (rule SPLITCON). 7 This transforms the problem into two subproblems P 2 | max zero : N → N and ( p : N) P 3 | max (suc p) : N → N where: We simplify the constraints as follows: clauses with absurd constraints (such as zero / ? suc k) are removed; trivial constraints (such as zero / ? zero) are dropped; and constraints between equal constructors (such as suc p / ? suc k) are simplified (i.e. to p / ? k): We continue applying these operations INTRO and SPLITCON (introducing a new variable and case analysis on a variable) until the first clause has no more user patterns and no more constraints where the user-written pattern on the left is a constructor. For example, for P 2 , we get after one more introduction step (n : N) P 4 | max zero n : N where: We solve the remaining constraint in the first clause by instantiating j := n. This means we are done and we have max zero n = j[n / j] = n (rule DONE). 8 The second clause of P 4 still has unsolved constraints, but this is not a problem since it is not used for the construction of this branch of the case tree. Similarly, elaborating the lhs problem ( p : N) P 3 | max (suc p) : N → N (with rules INTRO, SPLITCON, and DONE) gives us max (suc p) zero = suc p and max (suc p) (suc q) = suc (max p q).
We record the operations used when elaborating the clauses in a case tree. Our syntax for case trees is close to the normal term syntax in other languages: λx. for introducing a new variable and case x {} for a case split. For max, we get the following case tree: 7 At this point, we could also introduce the variable for the second argument of max, the elaboration algorithm is free to choose either option. 8 Since the DONE rule only looks at the first remaining clause, there may be unreachable clauses that are not used in any of the branches of the case tree. However, detection of unreachable clauses can easily be added as an additional check if desired.

Copattern matching
Next, we take a look at how to elaborate definitions using copatterns. For the cozero example (1), we have the initial lhs problem P 0 | cozero : CoNat where: Here, we need a new operation to split on the result type CoNat (rule COSPLIT). This produces two subproblems P 1 | cozero .iszero and P 2 | cozero .pred : cozero .iszero≡ Bool false → CoNat where: The first problem is solved immediately with cozero .iszero = true (rule DONE). In the second problem, we introduce the variable x : cozero .iszero≡ Bool false (rule INTRO) and note that cozero .iszero = true from the previous branch, hence x : true≡ Bool false. Since true≡ Bool false is an empty type (technically, since unification of true with false results in a conflict), we can perform a case split on x with zero cases (rule SPLITEMPTY), solving the problem.
In the resulting case tree, the syntax for a split on the result type is record{}, with one subtree for each field of the record type: Just as pattern matching is elaborated to a case, an elimination form, copattern matching is elaborated to a record, an introduction form.
For the next examples, we omit the details of the elaboration process and only show the definition by pattern matching and the resulting case tree.

Mixing patterns and copatterns
Consider the type CStream of C streams: potentially infinite streams of numbers that end on a zero. We define this as a record where the tail field has two extra arguments enforcing that we can only take the tail if the head is suc m for some m: Here, the name self is bound to the current record instance, allowing later projections to depend on prior projections. In particular, the type of tail depends on the value of head for the self object.
at https://www.cambridge.org/core/terms. https://doi.org/10.1017/S0956796819000182 8 J. Cockx and A. Abel Now consider the function countdown that creates a C stream counting down from a given number n: Because the type of tail depends on the value of head, it is again important to check the clauses in the right order: to check the pattern ∅ in the second clause, the typechecker needs to know that countdown zero .head = zero (and likewise that countdown (suc m) .head = suc m to check refl in the third clause). Our elaboration algorithm applies the rules INTRO, COSPLIT, SPLITCON, SPLITEMPTY, SPLITEQ, and DONE in sequence to translate this definition to the following case tree: Note the extra annotation 1 m after the case split on p : suc m ≡ N suc n . This rather technical addition is necessary to define the operational semantics of case trees (see Figure 8). It is used to remember which arguments correspond to forced patterns and can thus safely be dropped. In the current example, it reflects the fact that the argument n is forced to be equal to m by the case split on refl : suc n ≡ N suc m. Thus, evaluation of the function only depends on the value of m.

Inferring forced patterns
This example is based on issue #2896 on the Agda bug tracker (Agda issue, 2018a). The problem was that Agda's old elaboration algorithm threw away a part of the pattern written by the user. This meant the definition could be elaborated to a different case tree from the one intended by the user. The (simplified) example consists of the following data type D and function foo: The old algorithm would ignore the pattern suc k in the definition of foo because it corresponds to a forced pattern after the case split on refl. This lead to the variable k occurring in the case tree despite it not being bound anywhere. Our elaboration instead produces the following case tree (using rules INTRO, SPLITCON, SPLITEQ, and DONE): Note that the variable k has been substituted by m in the leaf of the case tree. Even though this case tree does not match on the suc constructor, it implements the same computational behaviour as the clause in the definition of foo because the first argument of c is forced to be suc m by the typing rules. This example also shows another feature supported by our elaboration algorithm, namely that two different variables m and n in the user syntax may correspond to the same variable m in the core syntax. In effect, n is treated as a let-bound variable with value m.

Preservation of first-match semantics
This example is based on issue #2964 on the Agda bug tracker (Agda issue, 2018b). The problem was that Agda was using a too-liberal version of the first-match semantics that was not preserved by the translation to a case tree. The problem occurred for the following definition: This function is elaborated (both by Agda's old algorithm and by ours) to the following case tree (using rules INTRO, SPLITCON, SPLITEQ, and DONE): According to the (liberal) first-match semantics, we should have f Bool false y p = false for any y : Bool and p : Bool ≡ Set Bool, but this is not true for the case tree since evaluation gets stuck on the variable y. Another possibility is to start the case tree by a split on p (after introducing all the variables), but this case tree still gets stuck on the variable p. In fact, there is no well-typed case tree that implements the first-match semantics of these clauses since we cannot perform a case split on x : A before splitting on p.
One radical solution for this problem would be to only allow case trees where the case splits are performed in order from left to right. However, this would mean the typechecker must reject many definitions such as f in this example, because the type of x is not known to be a data type until the case split on A ≡ Set Bool. Instead, we choose to keep the elaboration as it is and restrict the first-match semantics of clauses (see Sections 3.4 and 5.3). In particular, we use a more conservative matching algorithm that only produces a mismatch when not only at least one pattern is a mismatch (as usual), but also matching is not stuck for any of the other patterns. In the example of f, this change means that we can only go to the second clause once all three arguments x, y, and p are constructors, and at least one of them produces a mismatch. In particular, this means f Bool false y p no longer computes to false. This is reasonable since there exists a valid case tree that does not have this computation rule.

Expanding catch-all clauses
Compared to the implementation in Agda, the algorithm for elaborating definitions by dependent (co)pattern matching to a case tree in this paper is more liberal with respect to catch-all cases. Specifically, Agda includes an extra pass where each clause is typechecked individually before elaborating the whole set of clauses to a case tree. In contrast, the algorithm presented here only requires that the type of each right-hand side is correct after all case splitting is finished.
Let us consider an example where this makes a difference. Let Bin be the data type of binary numbers 1, 10, 11, 100, 101, . . . and consider the function isOne: data Bin : Set where one : Bin For example, the number 9 is represented as 1+2* (2* (2* one)). Now consider the following soundness proof isOneSound below: Following the algorithm of Section 5.2, this definition is elaborated to the following case tree: On the other hand, this definition is rejected by Agda because the second clause is ill-typed: x is not a constructor so the term isOne x does not reduce to either true or false, hence Agda cannot conclude that isOne x ≡ Bool true is an empty type. One advantage of the approach in this paper is that it allows for shorter definitions of some functions. For example, the shortest possible definition of isOneSound in Agda requires three clauses. As another example, proving decidable equality for a data type D with n constructors in general requires n 2 cases in Agda, while it only requires n + 1 cases with the elaboration algorithm presented here: here, the final clause is expanded into n · (n − 1) leaves of the case tree corresponding to the cases where the two arguments are applications of different constructors.
However, it is not clear how this late checking of the right-hand side should interact with other Agda features beyond the scope of this paper, such as interaction holes used for interactively developing Agda programmes. If such an interaction hole occurs in the right-hand side of a clause that corresponds to multiple branches of the case tree, then it may have multiple incompatible types. At this point, it is unclear how to display this information in a way that makes sense to the Agda user. For this reason, we have decided to leave the initial pass over the individual clauses in place for now.
The behaviour of Agda can be simulated by the elaboration judgement described in this paper by additionally requiring that each clause individually can be elaborated to a case tree. These individual case trees can then be 'merged' into one bigger case tree by following the first-match semantics. Alternatively, we can run the elaboration algorithm again on the whole set of clauses (as is currently done in the Agda implementation).

Core language
In this section, we introduce a basic type theory for studying definitions by dependent (co)pattern matching. It has support for dependent function types, an infinite hierarchy of predicative universes, equality types, inductive data types, and coinductive records.
To keep the work in this paper as simple as possible, we leave out many features commonly included in dependently typed languages, such as lambda expressions and inductive families of data types (other than the equality type). These features can nevertheless be encoded in our language, see Section 3.5 for details.
Note also that we do not include any rules for η-equality, neither for lambda expressions (which do not exist) nor for records (which can be coinductive hence do not satisfy η). Section 3.5 discusses how our language could be extended with η-rules.

Syntax of the core type theory
Expression syntax. Expressions of our type theory are almost identical to Agda's internal term language. All function applications are in spine-normal form, so the head symbol of an application is exposed, be it variable x, data D or record type R, or defined symbol f. We generalize applications to eliminations e by including projections .π in spinesē. Any expression is in weak head normal form but fē, which is computed via pattern matching (see Section 3.4): Any expression but cū or refl can be a type; the first five weak head normal forms are definitely types. Any type has in turn a type, specifically some universe Set . Syntax is coloured according to the Agda conventions: primitives and defined symbols are blue, constructors are green, and projections are pink: Binary application u e is defined as a partial function on the syntax: for variables and functions it is defined by (xē) e = x (ē, e) and (fē) e = f (ē, e), respectively, otherwise it is undefined. The expression syntax does not include anonymous functions or record expressions, but these can be defined in terms of definitions by (co)pattern matching (see Section 3.5).
Pattern syntax. Patterns are generated from variables and constructors. In addition, we have forced and absurd patterns. Since we are matching spines, we also consider projections as patterns, or more precisely, as copatterns: absurd pattern q ::= p application copattern | .π projection copattern (27) Forced patterns (Brady et al., 2003) appear with dependent types; they are either entirely forced arguments u , which are Agda's dot patterns, or only the constructor is forced c p. An argument can be forced by a match against refl somewhere in the surrounding (co)pattern. However, sometimes we want to bind variables in a forced argument; in this case, we revert to forced constructors. Absurd patterns 9 are used to indicate that the type at this place is empty, i.e. no constructor can possibly match. They are also used to indicate an empty copattern split, i.e. a copattern split on a record type with no projections. This allows us in particular to define the unique element tt of the unit record, which has no projections at all, by the clause tt ∅ = impossible.
The pattern variables PV(q) is the list of variables inq that appear outside forcing brackets · . By removing the forcing brackets, patterns p embed into terms p , and copatterns q into eliminations q , except for the absurd pattern ∅: Telescopes and contexts. Constructors take a list of arguments whose types can depend on all previous arguments. The constructor parameters are given as a list x 1 :A 1 , . . . , x n :A n This list can be conceived as a cons-list, then it is called a telescope (de Bruijn, 1991), or as a snoc-list, then we call it a context: Context and telescopes can be regarded as finite maps from variables to types, and we require x ∈ dom( ) and x ∈ dom( ) in the above grammars. We implicitly convert between contexts and telescopes, but there are still some conceptual differences. Contexts are always closed, i.e. its types only refer to variables bound prior in the same context. In contrast, we allow open telescopes whose types can also refer to some surrounding context. Telescopes can be naturally thought of as context extensions, and if is a context and a telescope in context where dom( ) and dom( ) are disjoint, then defined by ε = and ((x:A) ) = ( (x:A)) is a new valid context. We embed telescopes in the syntax of declarations, but contexts are used in typing rules exclusively.
Given a telescope , letˆ be without the types, i.e. the variables of in order. Similarly, we writeˆ for the variables bound in the context . Further, we define → C as the iterated dependent function type via ε → C = C and (x: Declaration syntax. A development in our core type theory is a list of declarations, of which there are three kinds: data type, record type, and function declarations (see Figure 1). The input to the type checker is a list of unchecked declarations decl , and the output a list of checked declarations decl ⊕ , called a signature .
A data type D can be parameterized by telescope and inhabits one of the universes Set . Each of its constructors c i (although there might be none) takes a telescope i of arguments that can refer to the parameters in . The full type of c i could be i → Dˆ , but we never apply constructors to the data parameters explicitly. A record type R can be thought of as data type with a single constructor; its fields π 1 :A 1 , . . . , π n :A n would be the constructor arguments. The field list behaves similar to a telescope, the type of each field can depend on the value of the previous fields. However, these values are referred to via self .π i where variable self is a placeholder for the value of the whole record. 10 The full type of projection π i could be (self : Rˆ ) → A i , but like for constructors, we do not apply a projection explicitly to the record parameters.
Even though we do not spell out the conditions for ensuring totality in this paper, like positivity, termination, and productivity checking, data types, when recursive, should be thought of as inductive types, and record types, when recursive, as coinductive types . Thus, there is no dedicated constructor for records; instead, concrete records are defined by what their projections compute.
Such definitions are subsumed under the last alternative dubbed function declaration. More precisely, these are definitions by copattern matching which include both function and record definitions. Each clause defining the constant f : A consists of a list of copatterns q (including patterns and projections) and right-hand side rhs. The copatterns eliminate type A into the type of the rhs which is either a term u or the special keyword impossible, in case one of the copatterns q i contains an absurd pattern ∅. The intended semantics is that if an application fē matches a left-hand side fq with substitution σ , then fē reduces to rhs under σ . For efficient computation of matching, we require linearity of pattern variables for checked clauses: each variable inq occurs only once in a non-forced position. Note that checked/unchecked declarations can only mention checked/unchecked clauses, respectively.
While checking declarations, the typechecker builds up a signature of already checked declarations or parts of declarations. Checked clauses are the elaboration (Sections 2 and 5) of the corresponding unchecked clauses: they are non-overlapping and supplemented by a telescope holding the types of the pattern variables and the type B of left-and right-hand side. Further, checked clauses do not contain absurd patterns.
In the signature, the last entry might be incomplete, e.g. a data type missing some constructors, a record type missing some fields, or a function missing some clauses. During checking a declaration, we might add already checked parts of the declaration, dubbed snippets, to the signature: Adding a snippet Z to a signature , written , Z is a always defined if Z is a data or record type or function signature; in this case, the corresponding declaration is appended to . Adding a constructor signature constructor c c : D is only defined if the last declaration in is (data D : Set where con) and c is not part of con yet. Analogous conditions apply when adding projection snippets. Function clauses can be added if the Table 1. List of typing and equality judgements of our core type theory.

Judgement Explanation Rules
Context is well formed.
In , telescope is well formed and bounded by level . Figure 4  ; u : A In , term u has type A. Figure 2  ; ū : In , term listū instantiates telescope .
In , head u of type A is eliminated viaē to type B. last declaration of is a function declaration with the same name. We trust the formal definition of , Z to the imagination of the reader. The conditions ensure that we do not add new constructors to a data type that is already complete or new fields to a completed record declaration. Such additions could destroy coverage for functions that have already been checked. Late addition of function clauses would not pose a problem, but since we require all function definitions to be total, any additional clauses would anyway be unreachable.
Membership of a snippet is written Z ∈ and a decidable property with the obvious definition. These operations on the signature will be used in the inference rules of our type theory. Since we only refer to a constructor c in conjunction with its data type D, constructors can be overloaded, and likewise projections.

Typing and equality
Our type theory employs the basic typing and equality judgements listed in Table 1. In all these judgements, the signature is fixed, thus we usually omit it, e.g. in the inferences rules.
We further define some shorthands for type-level judgements when we do not care about the universe level : In the inference rules, we make use of substitutions. Substitutions σ , τ , ν are partial maps from variable names to terms with a finite domain. If dom(σ ) and dom(τ ) are disjoint, then σ τ denotes the union of these maps. We write the substitution that maps the variables x 1 , . . . , x n to the terms v 1 , . . . , v n (and is undefined for all other variables) by [v 1 / x 1 ; . . . ; v n / x n ]. In particular, the empty substitution [ ] is undefined for all variables.
In particular, the identity substitution 1 := [ˆ / ] maps all variables in to themselves. We also use the identity substitution as a weakening substitution, allowing us to forget about all variables that are not in . If x ∈ dom(σ ), then σ \x is defined by removing x from the domain of σ .
Application of a substitution σ to a term u is written as uσ and is defined as usual by replacing all (free) variables in u by their values given by σ , avoiding variable capture via  suitable renaming of bound variables. Like function application, this is a partial operation on the syntax; for instance, (x .π )[c / x] is undefined as constructors cannot be the head of an elimination. Thus, when a substitution appears in an inference rule, its definedness is an implicit premise of the rule. Also, such pathological cases are ruled out by typing. Welltyped substitutions can always be applied to well-typed terms (established in Lemma 3). Substitution composition σ ; τ shall map the variable x to the term (xσ )τ . Note the difference between σ ; τ and σ τ : the former applies first σ and then τ in sequence, while the latter applies σ and τ in parallel to disjoint parts of the context. Application of a substitution to a pattern pσ is defined as p σ .
In addition to substitutions on terms, we also make use of substitutions on patterns called pattern substitutions. A pattern substitution ρ assigns to each variable a pattern. We reuse the same syntax for pattern substitutions as for normal substitutions. Any pattern substitution ρ can be used as a normal substitution ρ defined by x ρ = xρ .
The rules for the typing judgement t : A are listed in Figure 2. The type formation rules introduce an infinite hierarchy of predicative universes Set without cumulativity. The formation rules for data and record types make use of the judgement ū : to type argument lists, same for the constructor rule, which introduces a data type. Further, refl introduces the equality type. All expressions involved in these rules are fully applied,   but this changes when we come to the elimination rules. The types of heads, i.e. variables x or defined constants f are found in the context or signature.
The rules for applying heads u to spinesē, judgement | u : A ē : C , are presented in Figure 3. For checking arguments, the type of the head is sufficient, and it needs to be a function type. To check projections, we also need the value u of the head that replaces self in the type of the projection. We may need to convert the type of the head to a function or record type to apply these rules, hence, we supply a suitable conversion rule. The result type C of this judgement need not be converted here, it can be converted in the typing judgement for expressions.
Remark 1 (Focused syntax). The reader may have observed that our expressions cover only the non-invertible rules in the sense of focusing (Andreoli, 1992), given that we consider data types as multiplicative disjunctions and record types as additive conjunctions: terms introduce data and eliminate records and functions. The invertible rules, i.e. elimination for data and equality and introduction for function space and records are covered by pattern matching (Section 3.4) and, equivalently, case trees (Section 4). This matches our intuition that all the information/choice resides with the non-invertible rules, the terms, while the choice-free pattern matching corresponding to the invertible rules only sets the stage for the decisions taken in the terms. Figure 4 defines judgement for telescope formation. The level is an upper bound for the universe levels of the types that comprise the telescope. In particular, if we consider a telescope as a nested -type, then is an upper bound for the universe that hosts  this type. This is important when checking that the level of a data type is sufficiently high for the level of data it contains ( Figure 5).
Definitional equality u = u : A is induced by rewriting function applications according to the function clauses. It is the least typed congruence over the axiom: If fq → v is a defining clause of function f, then each instance arising from a well-typed substitution σ is a valid equation. The full list of congruence and equivalence rules is given in Figure A.2 in Appendix A., together with congruence rules for applications ( Figure A.3) and lists of terms ( Figure A.4). As usual in dependent type theory, definitional equality on types A = B : Set is used for type conversion.

Signature well-formedness
A signature extends 0 if we can go from 0 to by adding valid snippets Z, i.e. new data types, record types, and defined constants, but new constructors/projections/clauses only for not yet completed definitions in . A signature is well formed if it is a valid extension of the empty signature ε. Formally, we define signature extension 0 ⊆ via snippet typing Z by the rules in Figure 5, and signature well-formedness as ε ⊆ . Recall that the rules for extending the signature with a constructor (resp. projection or clause) can only be used when the corresponding data type (resp. record type or definition) is the last thing in the signature, by definition of extending the signature with a snippet , Z. When adding a constructor or projection, it is ensured that the stored data is not too big in terms of universe level ; this preserves predicativity. However, the parameters of a data or record type of level can be big, they may exceed .
All typing and equality judgements are monotone in the signature, thus, remain valid under signature extensions.
Lemma 5 (Signature extension preserves inferences). If ; u : A and ⊆ , then also ; u : A (and likewise for other judgements).
Remark 6 (Progress and preservation). The rules for extending a signature with a function definition given by a list of clauses are not strong enough to guarantee the usual properties of a language such as type preservation and progress. For example, we could define a function with no clauses at all (violating progress), or we could add a clause where all patterns are forced patterns (violating type preservation). We prove type preservation and progress only for functions that correspond to a well-typed case tree as defined in Section 4.

Pattern matching and evaluation rules
We define a small-step evaluation relation for our core language. This relation is not used by the typing judgement, but it serves as a reference point when proving the correctness of the operational semantics of case trees (Lemma 12). Since our language does not contain syntax for lambda abstraction, there is no rule for β-reduction. Almost all terms are their own weak head normal form; the only exception are applications fē. Formally, small-step evaluation u −→ v is defined as the congruence closure of the following rule: The small-step semantics are related to the definitional equality judgement by the notion of respectfulness (see Definition 13).  Evaluation of defined symbols relies on matching [ē /q] ⇒ σ ⊥ ( Figure 6). Herein, σ ⊥ is either a substitution σ with dom(σ ) = PV(q) or the error value ⊥ for mismatch. Join of lifted substitutions σ ⊥ τ ⊥ is ⊥ if one of the operands is ⊥, otherwise the join σ τ .
A pattern variable x matches any term v, producing singleton substitution [v / x]. Likewise for a forced pattern u , but it does not bind any pattern variables. Projections .π only match themselves, and so do constructors cp, but they require successful matching [ū /p] ⇒ σ of the arguments. For forced constructors c 1 p, the constructor equality test is skipped, as it is ensured by typing. Constructor (c 1 = c 2 ) and projection (.π 1 = .π 2 ) mismatches produce ⊥. We do not need to match against the absurd pattern; user clauses with absurd matches are never added to the signature. Recall that absurd patterns are not contained in clauses of the signature, thus, we need not consider them in the matching algorithm. Evaluating a function that eliminates absurdity will be stuck for lack of matching clauses.
A priori, requiring that [ē /q] ⇒ σ is very similar to asking that q σ =ē, but there are two key differences: 1. The matching judgement [ē /q] ⇒ σ ignores the forced patterns u and the constructor names in forced constructor patterns c p. This is important to give an efficient implementation of matching as it means we do not have to check equality of arbitrary terms. 2. The matching judgement makes a difference between a mismatch and a stuck match. For example, we have [suc n / zero] ⇒ ⊥ but [m + n / zero] ⇒ ⊥. Mere (in)equality cannot distinguish between the two situations.
For the purpose of the evaluation judgement, we would not need to track definite mismatch separately from getting stuck. However, for the first-match semantics (Augustsson, 1985) we do: there, a function should reduce with the first clause that matches while all previous clauses produce a mismatch. If matching a clause is stuck, we must not try the next one.
The first-match semantics is also the reason why either [e / q] ⇒ ⊥ or [ē /q] ⇒ ⊥ alone is not sufficient to derive [eē / qq] ⇒ ⊥, i.e. mismatch does not dominate stuckness, nor does it short-cut matching. Suppose a function and defined by the clauses true true → true and x y → false. If mismatch dominated stuckness, then both open terms and false y and and x false would reduce to false. However, there is no case tree that accomplishes this. We have to split on the first or the second variable; either way, one of the two open terms will be stuck. We cannot even decree left-to-right splitting: see Section 2.5 for a definition that is impossible to elaborate to a case tree using a left-to-right splitting order. Thus, we require our pattern match semantics to be faithful with any possible elaboration of clauses into case trees (see Theorem 22). 11

Other language features
In comparison to dependently typed programming languages like Agda and Idris, our core language seems rather reduced. In the following, we discuss how some popular features could be translated to our core language.
Lambda abstractions and η-equality: A lambda abstraction λx. t in context can be lifted to the top level and encoded as auxiliary function fˆ x → t. We obtain extensionality (η) by adding the following rule to definitional equality:

Record expressions:
Likewise, a record value record{π =v} in can be turned into an auxiliary definition by copattern matching with clauses (fˆ .π i → v i ) i . We could add an η-law that considers two values of record type R definitionally equal if they are so under each projection of R. However, to maintain decidability of definitional equality, this should only applied to non-recursive records, as recursive records model coinductive types which do not admit η. Indexed data types can be defined as regular (parameterized) data types with extra arguments to each constructor containing equality proofs for the indices. For example, Vec A n can be defined as follows: cons n x xs .head n refl = x cons n x xs .tail n refl = xs Mutual recursion can be simulated by nested recursion as long as we do not define checks for positivity and termination. Wildcard patterns can be written as variable patterns with a fresh name. Note that an unused variable may stand for either a wildcard or a forced pattern. In the latter case, our algorithm treats it as a let-bound variable in the right-hand side of the clause. Record patterns would make sense for inductive records with η. Without changes to the core language, we can represent them by first turning deep matching into shallow matching, along the lines of Setzer et al. (2014), and then turn record matches on the left-hand side into projection applications on the right-hand side.
Other type-level features such as cumulativity or a (predicative or impredicative) Prop universe are orthogonal to the work in this paper and could be added without much trouble. This concludes the presentation of our core language.

Case trees
From a user perspective, it is nice to be able to define a function by a list of clauses, but for a core language this representation of functions leaves much to be desired: it is hard to see whether a set of clauses is covering all cases (Coquand, 1992), and evaluating the clauses directly can be slow for deeply nested patterns (Cardelli, 1984). Recall that for type-checking dependent types, we need to decide equality of open terms which requires computing weak head normal forms efficiently. Thus, instead of using clauses, we represent functions by a case tree in our core language. In this section, we give a concrete syntax for case trees and give typing and evaluation rules for them. We also prove that a function defined by a case tree enjoys good properties such as type preservation and progress: bring argument x in scope | record{π 1 → Q 1 ; . . . ; π n → Q n } splitting on projections | case x {c 1ˆ 1 → Q 1 ; . . . ; c nˆ n → Q n } match on data x | case x {refl → τ Q} match on equality proof x Note that empty case and empty record are allowed, to cover the empty data type and the unit type, i.e. the record without fields. Remark 7 (Focusing). Case trees allow us to introduce functions and records, and eliminate data. In the sense of focusing, this corresponds to the invertible rules for implication, additive conjunction, and multiplicative disjunction. (See typing rules in Figure 7.)

Case tree typing
A case tree Q for a defined constant f : A is well typed in signature if f := Q : A . In this judgement, is the signature in which case tree Q for function f : A is well typed, and is the output signature which is extended with the function clauses corresponding to case tree Q. Note that the absence of a local context in this proposition implies that we only use case trees for top-level definitions. 12 Case tree typing is established by the generalized judgement ; fq := Q : A (Figure 7) that considers a case tree Q for the instance fq of the function in a context of the pattern variables ofq. The typing rules presented here capture the well-formedness of the output of the elaboration algorithm. In Figure 10, this judgement will be extended to an algorithmic version that takes the user clauses as additional input. We have the following rules for ; fq := Q : A :

J. Cockx and A. Abel
CTDONE A leaf of a case tree consists of a right-hand side v which needs to be of the same type C of the corresponding left-hand side fq and may only refer to the pattern variables ofq. If this is the case, the clause fq → v is added to the signature. CTINTRO If the left-hand side fq is of function type (x : A) → B, we can extend it by variable pattern x. The corresponding case tree is function introduction λx. Q. CTCOSPLIT If the left-hand side is of record type Rv with projections π i , we can do result splitting and extend it by copattern .π i for all i. We have record{π 1 → Q 1 ; . . . ; π n → Q n } (where n ≥ 0) as the corresponding case tree, and we check each subtree Q i for left-hand side fq .π i in the signature i−1 which includes the clauses for the branches j < i. Note that these previous clauses may be needed to check the current case, since we have dependent records (Section 2.2). CTSPLITCON If left-hand side fq contains a variable x of data type Dv, we can split on x and consider all possible constructors c i fully applied to fresh variables, generating the case tree case x {c 1ˆ 1 → Q 1 ; . . . ; c nˆ n → Q n }. The branch Q i is checked for a refined left-hand side where x has been substituted by c iˆ i in a context where x has been replaced by the new pattern variables i . Note also the threading of signatures as in rule CTCOSPLIT. 13 The rules CTSPLITEQ and CTSPLITABSURDEQ are explained in the next section.

Unification: Splitting on the identity type
To split on an equality proof x : u ≡ B v, we try to unify u and v. Unification has three possible outcomes: either it ends in a positive success and finds a most general unifier (m.g.u.); then we can build a case tree case x {refl → ·} (rule CTSPLITEQ). Or it ends in a negative success with a disunifier; then we may build the case tree case x {} (rule CTSPLITABSURDEQ). Finally, it may end in a failure with neither a m.g.u. nor a disunifier, e.g. for equality y + z ≡ N y + z ; then elaboration fails. In fact, in our setting, we need a refinement of m.g.u.s we call strong unifiers. Compared to the usual notion of m.g.u., a strong unifier has additional restrictions on the computational behaviour of the substitutions between the original context and the reduced ones. We recall the definitions of a strong unifier and a disunifier from Cockx et al. (2016), here translated to the language of this paper and specialized to the case of a single equation: τ ; σ = 1 : (recall that 1 is the identity substitution [ˆ / ]) 3. For any context 0 and substitution σ 0 such that 0 σ 0 : (x : u ≡ A v) and 0 xσ 0 = refl : uσ 0 ≡ Aσ 0 vσ 0 , we have 0 σ ; τ ; σ 0 = σ 0 : (x : u ≡ A v). Since we use the substitution σ for the construction of the left-hand side of clauses, we require unification to output not just a substitution but a pattern substitution ρ. The only properly matching pattern in ρ is xρ = refl; all the other patterns yρ are either a forced pattern t (if unification instantiates y with t) or the variable y itself (if unification leaves y untouched).
We thus assume we have access to a proof relevant unification algorithm specified by the following judgements: ρ, τ ) ensures that xρ = refl and the triple ( , ρ , τ ) is a strong unifier. Additionally, ⊆ , yτ = y, and yρ = y for all y ∈ , and yρ is a forced pattern for all variables y ∈ \ . • A negative success ; x u = ? v : A ⇒ NO ensures that there exists a disunifier of u and v.
Remark 10. During the unification of u with v, each step either instantiates one variable from (e.g. the solution step) or leaves it untouched (e.g. the injectivity step). We thus have the invariant that the variables in form a subset of the variables in . In effect, the substitution τ makes the variables instantiated by unification go 'out of scope' after a match on refl. This property ceases to hold in a language with η-equality for record types and unification rules for η-expanding a variable such as the ones given by Cockx et al. (2016). In particular, τ may contain not only variables but also projections applied to those variables.

Operational semantics
If a function f is defined by a case tree Q, then we can compute the application of f to eliminationsē via the judgement Qσē −→ v (Figure 8) with σ = [ ]. The substitution σ acts as an accumulator, collecting the values for each of the variables introduced by a λ or by the constructor arguments in a case x {...}. In particular, when evaluating a case tree of the form case x {refl → τ Q}, the substitution τ is used to remove any bindings in σ that correspond to forced patterns.
The operational sematics of case trees give us an efficient way to evaluate functions by pattern matching. Since case trees are guaranteed to be covering, their operational sematics is also essential in the proof of progress (in particular Lemma 19).

Properties
If a function f is defined by a well-typed case tree, then it enjoys certain good properties such as type preservation and progress. The goal of this section is to state and prove these properties. First, we need some basic lemmata.
Lemma 11 (Well-typed case trees preserve signature well-formedness). Let be a well-formed signature where definition f : A where cls ⊕ is the last declaration in and let Q be a case tree such that ; fq := Q : C where and ; | f : A q : C. Then is also well formed.
Proof By induction on ; fq := Q : C .
The following lemma implies that once the typechecker has completed checking a definition, we can replace the clauses of that definition by the case tree. This gives us more efficient evaluation of the function and guarantees that evaluation is deterministic. In the statement of the lemma, we use the notation [ē /q] −→ * σ ⊥ to state thatē −→ * ē and [ē /q] ⇒ σ ⊥ for someē .

Lemma 12 (Simulation lemma). Consider a case tree Q such that 0 ;
fq := Q : C , let σ be a substitution whose domain is the pattern variables ofq, and letē be some eliminations. If Qσē −→ t then there is some pattern substitution ρ and copatternsq such that clause fqρq → v : A is in but not in 0 and t = vθē 2 where [qσē 1 /qρq ] −→ * θ andē =ē 1ē2 .
In the other direction, we start again by induction on Q: • In case Q = v, we have the single clause clause fq → v : A which is of the right form with ρ = 1 andq = ε. If [qσē 1 /q] ⇒ θ , then we have σ = θ andē 1 = ε, so Qσē 1ē2 −→ vθē 2 . • In case Q = λx. Q , we get from the induction hypothesis that any clause in \ 0 is of the form clause f (q x)ρq → v : A, which is of the right form if we take ρ = ρ\x as the new ρ andq = xρq as the newq . Moreover, if [qσē 1 /qρ q ] ⇒ θ , thenē 1 = uē 1 and . . . ; c nˆ n → Q n }, we get from the induction hypothesis that any clause in \ 0 is of the form clause fqρ i ρq → v : A for some ρ i = 1 1 [c iˆ i / x] 1 2 . This is of the right form if we take ρ = ρ i ρ as the new ρ (and keepq the same). Moreover, if [qσē 1 /qρ i ρq ] ⇒ θ , then we have xσ = c iū from the definition of matching. Let σ = σ \x [ū / i σ ], then we also have [qρ i σ ē 1 /qρ i ρq ] ⇒ θ . From the induction hypothesis, it now follows that Q i σ ē 1ē2 −→ vθē 2 , hence also Qσē 1ē2 −→ vθē 2 . • In case Q = record{π 1 → Q 1 ; . . . ; π n → Q n }, we get from the induction hypothesis that any clause in \ 0 is of the form clause fqρ .π iq → v : A. This is of the right form if we takeq = .π iq as the newq (and keep ρ the same). Moreover, if [qσē 1 /qρ .π iq ] ⇒ θ , thenē 1 = .π iē 1 . The induction hypothesis gives us that Q i σē 1ē 2 −→ vθē 2 , hence also Qσē 1ē2 −→ vθē 2 . • In case Q = case x {refl → τ Q} , we get from the induction hypothesis that any clause in \ 0 is of the form clause fqρ ρ → v : A where ρ and τ are produced by unification. This is of the right form if we take ρ = ρ ; ρ as the new ρ (and keepq the same). Moreover, if [qσē 1 /qρ ρq ] ⇒ θ , then we have xσ = refl from the definition of matching. Let σ = τ ; σ , then we have xρ σ = refl and for all other pattern variables y ofq, either yρ is a forced pattern or yρ = y and yσ = yσ . By matching, it follows that also [qρ σ ē 1 /qρ ρq ] ⇒ θ . From the induction hypothesis, it now follows that Q σ ē 1ē2 −→ vθē 2 , hence also Qσē 1ē2 −→ vθē 2 . • In case Q = case x {}, we have = 0 so there are no new clauses to worry about.
at https://www.cambridge.org/core/terms. https://doi.org/10.1017/S0956796819000182 Downloaded from https://www.cambridge.org/core. Technische Universiteit Delft, on 03 Feb 2020 at 08:20:08, subject to the Cambridge Core terms of use, available J. Cockx and A. Abel Before adding a clause fq → v to the signature, we have to make sure that the copatterns q only use forced patterns in places where it is justified: otherwise we might have [ē /q] ⇒ σ but q σ =ē. This is captured in the notion of a respectful pattern (Goguen et al., 2006). Intuitively, a pattern is respectful if any well-typed term that matches the accessible part of the pattern also matches the inaccessible parts. Here, we generalize the definition to the case where we do not yet know that all reductions in the signature are necessarily type-preserving. This requires us to first define respectfulness of a signature.
In particular, this means ; w : A, so evaluation with signature is type-preserving. It is immediately clear that the empty signature is respectful, since it does not contain any clauses.
Definition 14 (Respectful copatterns). Letq be a list of copatterns such that ; | u : A q : C where u and A are closed (i.e. do not depend on ). We callq respectful in signature if the following holds: for any signature extension ⊆ and any eliminations Being respectful is stable under signature extension by definition: ifq is respectful in and ⊆ , thenq is also respectful in .
Lemma 15 (Signatures with respectful clauses are respectful). If is a well-formed signature such that all clauses in have respectful copatterns in , then is respectful.
Lemma 16 (Well-typed case trees have respectful clauses). Consider a respectful signature 0 and a case tree Q such that 0 ; fq := Q : C andq is respectful in 0 . Then all clauses in \ 0 have respectful patterns in .
Proof By induction on the derivation of 0 ; fq := Q : C : • In case Q = v, we have a single new clause clause fq → v : C. Sinceq is respectful in 0 by assumption, it is also respectful in = 0 , clause fq → v : C.
• In case Q = λx. Q , we know from the typing rule of λx. that 0 ; C = (x : A ) → B : Set . and 0 ; (x : A) fq x := Q : B . Sinceq is respectful, it follows that q x is also respectful, so the result follows from the induction hypothesis.
Theorem 17 (Type preservation). If all functions in a signature are given by well-typed case trees, then is respectful. B σ 0 : Set , soē = wē for some term w : A σ 0 and eliminations | fqσ 0 w : ē : B. By induction, we now have that either σ 0 orē takes a step, or else . . . ; c nˆ n → Q n }, we have x : Dv ∈ , hence either xσ 0 takes a step (so we are in the first case) or else xσ 0 is a weak head normal form c iū for some constructor c i of D. In the latter case, we have by induction that either σ 0 [ū / i σ ] orē takes a step, or else . . . ; π n → Q n }, we have 0 ; C = Rv : Set . Hence, we have 0 Cσ 0 = Rvσ 0 : Set , soē = .π iē for some field π i of R. By induction either σ 0 orē takes a step, or else we get a v such that hence either xσ 0 takes a step (so we are in the first case) or else xσ 0 = refl. From the inductive hypothesis together follows that either τ ; σ 0 orē takes a step, or else Q (τ ; σ 0 )ē −→ v. By construction, yτ = y for each variable y in (see Remark 10), so if τ ; σ 0 takes a step then so does σ 0 . On the other hand, if Q (τ ; σ 0 )ē −→ v, then we also have Qσ 0ē −→ v so we are in the third case.
• If Q = case x {}, we have x : u ≡ E v ∈ , so either xσ 0 takes a step or it is equal to refl. But u ≡ E v is equivalent to the empty type by unification, so the latter case is impossible.

Elaboration
In the previous two sections, we have described a core language with inductive data types, coinductive records, identity types, and functions defined by well-typed case trees. On the other hand, we also have a surface language consisting of declarations of data types, record types, and functions by dependent (co)pattern matching. In this section, we show how to elaborate a programme in this surface language to a well-formed signature in the core language.
The main goal of this section is to describe the elaboration of a definition given by a set of (unchecked) clauses to a well-typed case tree and prove that this translation (if it succeeds) preserves the first-match semantics of the given clauses. Before we dive into this, we first describe the elaboration for data and record types. Figure 9 gives the rules for checking declarations, constructors, and projections. These rules are designed to correspond closely to those for signature extension in Figure 5. Consequentially, if and decl , then also .

From clauses to a case tree
In Section 2, we showed how our elaboration algorithm works in a number of examples, here we describe it in general. The inputs to the algorithm are the following:  • A signature containing previous declarations, as well as clauses for the branches of the case tree that have already been checked. • A context containing the types of the pattern variables: dom( ) = PV(q).
• The function f currently being checked. • The copatternsq for the current branch of the case tree. • The refined target type C of the current branch. • The user input P, which is described below.
The outputs of the algorithm are a signature extending with new clauses and a welltyped case tree Q such that ; fq := Q : C . We represent the user input P to the algorithm as an (ordered) list of partially decomposed clauses, called a left-hand side problem or lhs problem for short. Each partially decomposed clause is of the form [E]q → rhs, where E is an (unordered) set of constraints {w k / ? p k : A k | k = 1 . . . l} between a pattern p k and a term w k ,q is a list of copatterns, and rhs is a right-hand side. In the special case E is empty, we have a complete clause written asq → rhs.

Judgement/definition Explanation Rules
; P | fq := Q : C User input P is elaborated to the case tree Q which Figure 10 implements fq, extends with the clauses of Q.
The constraints E are solved by substitution σ . Figure 11 P (x : A) Update user input P by introducing new argument x. Figure 12 P .π Update user input P by applying projection .π. Figure 13 Pσ ⇒ P Applying substitution σ to user input P produces P . Figure 14 v / ?p : ⇒ E ⊥ Constraintsv / ?p can be simplified to E ⊥ . Figure 15  ; ∅ : A Type A is a caseless type. Figure 16 Elaboration of an lhs problem to a well-typed case tree is defined by the judgements in Table 2, which are explained further in the following paragraphs. The main elaboration judgement ; P | fq := Q : C is defined in Figure 10. This judgement is designed as an algorithmic version of the typing judgement for case trees ; fq := Q : C , where the extra user input P guides the construction of the case tree. In particular, the clauses in P serve as the user-provided definition of fq. Each of the rules in Figure 10 is a refined version of one of the rules in Figure 7, so any case tree produced by this elaboration is well typed by construction. In particular, since well-typed case trees are guaranteed to be covering, this judgement doubles as a coverage checking algorithm for the clauses in P.
To check a definition of f : A with clausesq i → rhs i for i = 1 . . . n, the algorithm starts with = ε,q = ε, and P = {q i → rhs i | i = 1 . . . n}. If we obtain ; P | f := Q : A , then the function f can be implemented using the case tree Q. During elaboration, the algorithm maintains the invariants that is a well-formed signature, is a well-formed context, and ; f q : C. It also maintains the invariant that for each constraint w k / ? p k : A k in the lhs problem, we have ; w k : A k . The rules for ; P | fq := Q : C make use of some auxiliary operations for manipulating lhs problems: • After each step, the algorithm uses ; E ⇒ SOLVED(σ ) ( Figure 11) to check if the first user clause has no more (co)patterns, and all its constraints are solved. If this is the case, it returns a substitution σ assigning a well-typed value to each of the user-written pattern variables. • After introducing a new variable, the algorithm uses P (x : A) (Figure 12) to remove the first application pattern from each of the user clauses and to introduce a new constraint between the variable and the pattern. • After a copattern split on a record type, the algorithm uses P .π (Figure 13) to partition the clauses in the lhs problem according to the projection they belong to. • After a case split on a data type or an equality proof, the algorithm uses Pσ ⇒ P (Figure 14) to refine the constraints in the problem. It makes use of the judgements v / ? p : A ⇒ E ⊥ and v / ?p : ⇒ E ⊥ (Figure 15) to simplify the constraints if possible, and to filter out the clauses that definitely do not match the current branch (see Section 2.1 for an example). Elaborating dependent (co)pattern matching 33 Fig. 10. Rules for checking a list of clauses and elaborating them to a well-typed case tree.
• To check an absurd pattern ∅, the algorithm uses ; ∅ : A ( Figure 16) to ensure that the type of the pattern is a caseless type (Goguen et al., 2006), i.e. a type that is empty and cannot even contain constructor-headed terms in an open context. Our language has two kinds of caseless types: data types Dv with no constructors, and identity types u ≡ A v where ; x u = ? v : A ⇒ NO.
The following rules constitute the elaboration algorithm ; P | fq := Q : C : DONE applies when the first user clause in P has no more copatterns and all its constraints are solved according to ; E ⇒ SOLVED(σ ). If this is the case, then construction of the case tree is finished, adding the clause clause fq → vσ : C to the signature.       INTRO applies when C is a function type and all the user clauses have at least one application copattern. It constructs the case tree λx. Q, using P (x : A) to construct the subtree Q. COSPLIT applies when C is a record type and all the user clauses have at least one projection copattern. It constructs the case tree record{π 1 → Q 1 ; . . . ; π n → Q n }, using P .π i to construct the branch Q i corresponding to projection .π i . COSPLITEMPTY applies when C is a record type with no projections and the first clause starts with an absurd pattern. It then constructs the case tree record{}. SPLITCON applies when the first clause has a constraint of the form x / ? c jp and the type of x in is a data type. For each constructor c i of this data type, it constructs a pattern substitution ρ i replacing x by c i applied to fresh variables. It then constructs the case tree case x {c 1ˆ 1 → Q 1 ; . . . ; c nˆ n → Q n }, using Pρ i ⇒ P i to construct the branches Q i . SPLITEQ applies when the first clause has a constraint of the form x / ? refl and the type of x in is an identity type u ≡ A v. It tries to unify u with v, expecting a positive success. If unification succeeds with output ( 1 , ρ, τ ), it constructs the case tree case x {refl → τ Q}, using Pρ ⇒ P to construct the subtree Q. Here ρ and τ are lifted versions of ρ and τ over the part of the context that is untouched by unification. SPLITEMPTY applies when the first clause has a constraint of the form x / ? ∅, and the type of x is a caseless type according to ; ∅ : A. It then produces the case tree case x {}.
Remark 20 (Limitations). The algorithm does not detect unreachable clauses, we left that aspect out of the formal description. Further, SPLITEMPTY may leave some user patterns uninspected, which may then be ill-typed. However, an easy check whether the whole lhs f q is well typed as a term can rule out ill-typed patterns.

Preservation of first-match semantics
Now that we have described the elaboration algorithm from a list of clauses to a well-typed case tree, we can state and prove our main correctness theorem. We already know that elaboration always produces a well-typed case tree by construction (if it succeeds), and that well-typed case trees are type-preserving (Theorem 17) and cover all cases (Theorem 18). Now we prove that the case tree we get is the right one, i.e. that it corresponds to the definition written by the user. To prove this theorem, we assume that the clauses we get from the user have already been scope checked, i.e. each variable in the right-hand side of a clause is bound somewhere in the patterns on the left.

Definition 21.
A partially decomposed clause [E]q → v is well scoped if every free variable in v occurs at least once as a pattern variable in eitherq or in p for some constraint (w / ? p : A) ∈ E.
Theorem 22. Let P = {q i → rhs i | i = 1 . . . n} be a problem consisting of well-scoped clauses such that 0 P | f := Q : C and let ; | f : C ē : B be eliminations. Let i be the index of the first matching clause, i.e.: For the proof, we first need two basic properties of the auxiliary judgement v / ? p : Proof This follows directly from the rules of simplification ( Figure 15). Proof This follows directly from the rules of matching ( Figure 6) and simplification ( Figure 15).
The following lemma is the main component of the proof. It generalizes the statement of Theorem 22 to the case where the left-hand side has already been refined to fq and the user clauses have been partially decomposed. From this lemma, the main theorem follows directly by takingq = ε and E i = {} for i = 1 . . . n.
Proof By induction on the derivation of 0 ; 0 P | fq := Q : C : • For the DONE rule where Q = v 1 σ and 0 = , we haveq 1 = ε and rhs 1 = v 1 (i.e. rhs 1 is not impossible). We also get that σ = k σ k is a substitution such that ; 0 v 1 σ : C and [w k / p k ] ⇒ σ k and ; 0 . First, we show that i = 1, i.e. the first clause matches. Since [w k / p k ] ⇒ σ k , we cannot have [w k σ 0 / p k ] ⇒ ⊥, and sinceq 1 = ε, we also cannot have [ē /q 1 ] ⇒ ⊥. The only remaining possibility is that i is 1. This means we have [w k σ 0 / p k ] ⇒ θ k for each (w k / ? p k : A k ) ∈ E 1 and [ē /q 1 ] ⇒ θ 0 . Sinceq 1 = ε, we also haveē = ε and θ 0 = [ ]. To finish this case, we show that σ σ 0 = ( k σ k )σ 0 and θ = k θ k coincide on all free variables in v. Since the clause [E 1 ] → v 1 is well scoped, for each free variable x in v 1 , there is at least one constraint (w k / ? p k : A k ) ∈ E 1 such that x is a pattern variable of p k . Since we have both [w k / p k ] ⇒ σ k and [w k σ 0 / p k ] ⇒ θ k , we have xσ k σ 0 = xθ k . This holds for any free variable x in v 1 , so we have v 1 σ σ 0 = v 1 θ , finishing the proof for the base case.

Related work
Dependent pattern matching was introduced in the seminal work by Coquand (1992). It is used in the implementation of various dependently typed languages such as Agda (Norell, 2007), Idris (Brady, 2013), the Equations package for Coq (Sozeau, 2010), and Lean (de Moura et al., 2015).
Previous work by Norell (2007), Sozeau (2010), and Cockx (2017) also describe elaborations from clauses to a case tree, but in much less detail than presented here, and they do not support copatterns or provide a correctness proof. In cases where both our current algorithm and these previous algorithms succeed, we expect there is no difference between the resulting case trees. However, our current algorithm is much more flexible in the placement of dot patterns, so it accepts more definitions than was possible before (see Section 2.4). McBride & McKinna (2004) present an alternative syntax for dependent pattern matching where the user specifies the order of case splitting explicitly. This gives more control to the user, but obfuscates the equational presentation of the clauses. It would be interesting to see how to extend our syntax with optional splitting annotations for when more control is desired.
The translation from a case tree to primitive data type eliminators was pioneered by McBride (2000) and further detailed by Goguen et al. (2006) for type theory with uniqueness of identity proofs and Cockx (2017) in a theory without.
Forced patterns, as well as forced constructors, were introduced by Brady et al. (2003). Brady et al. focus mostly on the compilation process and the possibility to erase arguments and constructor tags, while we focus more on the process of typechecking a definition by pattern matching and the construction of a case tree.
Copatterns were introduced in the simply typed setting by  and subsequently used for unifying corecursion and recursion in System F ω . In the context of Isabelle/HOL, Blanchette et al. (2017) use copatterns as syntax for mixed recursive-corecursive definitions. Setzer et al. (2014) give an algorithm for elaborating a definition by mixed (co)pattern matching to a nested case expression, yet only for a simply typed language. Thibodeau et al. (2016) present a language with deep (co)pattern matching and a restricted form of dependent types. In their language, types can only depend on a user-defined domain with decidable equality and the types of record fields cannot depend on each other, thus, a self value is not needed for checking projections. They at https://www.cambridge.org/core/terms. https://doi.org/10.1017/S0956796819000182 Downloaded from https://www.cambridge.org/core. Technische Universiteit Delft, on 03 Feb 2020 at 08:20:08, subject to the Cambridge Core terms of use, available feature indexed data and record types in the surface language which are elaborated into non-indexed types via equality types, just as in our core language. Likewise, Laforgue and Régis-Gianas (2017) extend OCaml with copatterns, but they do not allow the types of record fields to depend on each other.
The connection between focusing (Andreoli, 1992) and pattern matching has been systematically explored by Zeilberger (2009). In Licata et al. (2008), copatterns ('destructor patterns') also appear in the context of simple typing with connectives from linear logic. Krishnaswami (2009) boils the connection to focusing down to usual non-linear types; however, he has no copatterns as he only considers the product type as multiplicative (tensor), not additive. Thibodeau et al. (2016) extend the connection to copatterns for indexed record types.
Elaborating a definition by pattern matching to a case tree (Augustsson, 1985) simultaneously typechecks the clauses and checks their coverage, so our algorithm has a lot in common with coverage checking algorithms. For example, Norell (2007) views the construction of a case tree as a part of coverage checking. Oury (2007) presents a similar algorithm for coverage checking and detecting useless cases in definitions by dependent pattern matching.

Conclusion
In this paper, we give a description of an elaboration algorithm for definitions by dependent copattern matching that is at the same time elegant enough to be intuitively understandable, simple enough to study formally, and detailed enough to serve as the basis for a practical implementation.
The main motivation for writing this paper was to get a clear idea of how to implement a good elaboration algorithm for dependent (co)pattern matching that works for a full-featured dependently typed language such as Agda. Working out the theory allowed us to uncover several potential issues and edge cases in the implementation. For instance, while working on the proof of Theorem 22, we were quite surprised to discover that it did not hold at first: matching was performed lazily from left to right, but the case tree produced by elaboration may not agree on this order! This problem was not just theoretical, but also manifested itself in the implementation of Agda as a violation of subject reduction (Agda issue, 2018b). Removing the shortcut rule from the definition of matching removed this behavioural divergence mismatch. The complete formalization of the elaboration algorithm in this paper lets us continue to work on the implementation with confidence.
Agda also has a number of features that are not described in this paper, such as nonrecursive record types with η equality and general indexed data types (not just the identity type). The implementation also has to deal with the insertion of implicit arguments, the presence of metavariables in the syntax, and reporting understandable errors when the algorithm fails. Based on our practical experience, we are confident that the algorithm presented here can be extended to deal with all of these features.