TypecheckingA Typechecker for STLC

The has_type relation of the STLC defines what it means for a term to belong to a type (in some context). But it doesn't, by itself, tell us how to check whether or not a term is well typed.
Fortunately, the rules defining has_type are syntax directed — they exactly follow the shape of the term. This makes it straightforward to translate the typing rules into clauses of a typechecking function that takes a term and a context and either returns the term's type or else signals that the term is not typable. This short chapter constructs such a function and proves it correct.

Require Import Coq.Bool.Bool.
Require Import Maps.
Require Import Smallstep.
Require Import Stlc.

Module STLCChecker.
Import STLC.

Comparing Types

First, we need a function to compare two types for equality...

Fixpoint beq_ty (T1 T2:ty) : bool :=
  match T1,T2 with
  | TBool, TBool
      true
  | TArrow T11 T12, TArrow T21 T22
      andb (beq_ty T11 T21) (beq_ty T12 T22)
  | _,_ ⇒
      false
  end.

... and we need to establish the usual two-way connection between the boolean result returned by beq_ty and the logical proposition that its inputs are equal.

Lemma beq_ty_refl : T1,
  beq_ty T1 T1 = true.
Proof.
  intros T1. induction T1; simpl.
    reflexivity.
    rewrite IHT1_1. rewrite IHT1_2. reflexivity. Qed.

Lemma beq_ty__eq : T1 T2,
  beq_ty T1 T2 = true T1 = T2.
Proof with auto.
  intros T1. induction T1; intros T2 Hbeq; destruct T2; inversion Hbeq.
  - (* T1=TBool *)
    reflexivity.
  - (* T1=TArrow T1_1 T1_2 *)
    rewrite andb_true_iff in H0. inversion H0 as [Hbeq1 Hbeq2].
    apply IHT1_1 in Hbeq1. apply IHT1_2 in Hbeq2. subst... Qed.

The Typechecker

The typechecker works by walking over the structure of the given term, returning either Some T or None. Each time we make a recursive call to find out the types of the subterms, we need to pattern-match on the results to make sure that they are not None. Also, in the tapp case, we use pattern matching to extract the left- and right-hand sides of the function's arrow type (and fail if the type of the function is not TArrow T11 T12 for some T1 and T2).

Fixpoint type_check (Γ:context) (t:tm) : option ty :=
  match t with
  | tvar xΓ x
  | tabs x T11 t12match type_check (update Γ x T11) t12 with
                          | Some T12Some (TArrow T11 T12)
                          | _None
                        end
  | tapp t1 t2match type_check Γ t1, type_check Γ t2 with
                      | Some (TArrow T11 T12),Some T2
                        if beq_ty T11 T2 then Some T12 else None
                      | _,_ ⇒ None
                    end
  | ttrueSome TBool
  | tfalseSome TBool
  | tif x t fmatch type_check Γ x with
                     | Some TBool
                       match type_check Γ t, type_check Γ f with
                         | Some T1, Some T2
                           if beq_ty T1 T2 then Some T1 else None
                         | _,_ ⇒ None
                       end
                     | _None
                   end
  end.

Properties

To verify that this typechecking algorithm is correct, we show that it is sound and complete for the original has_type relation — that is, type_check and has_type define the same partial function.

Theorem type_checking_sound : Γ t T,
  type_check Γ t = Some T has_type Γ t T.
Proof with eauto.
  intros Γ t. generalize dependent Γ.
  induction t; intros Γ T Htc; inversion Htc.
  - (* tvar *) eauto.
  - (* tapp *)
    remember (type_check Γ t1) as TO1.
    remember (type_check Γ t2) as TO2.
    destruct TO1 as [T1|]; try solve_by_invert;
    destruct T1 as [|T11 T12]; try solve_by_invert.
    destruct TO2 as [T2|]; try solve_by_invert.
    destruct (beq_ty T11 T2) eqn: Heqb;
    try solve_by_invert.
    apply beq_ty__eq in Heqb.
    inversion H0; subst...
  - (* tabs *)
    rename i into y. rename t into T1.
    remember (update Γ y T1) as G'.
    remember (type_check G' t0) as TO2.
    destruct TO2; try solve_by_invert.
    inversion H0; subst...
  - (* ttrue *) eauto.
  - (* tfalse *) eauto.
  - (* tif *)
    remember (type_check Γ t1) as TOc.
    remember (type_check Γ t2) as TO1.
    remember (type_check Γ t3) as TO2.
    destruct TOc as [Tc|]; try solve_by_invert.
    destruct Tc; try solve_by_invert.
    destruct TO1 as [T1|]; try solve_by_invert.
    destruct TO2 as [T2|]; try solve_by_invert.
    destruct (beq_ty T1 T2) eqn:Heqb;
    try solve_by_invert.
    apply beq_ty__eq in Heqb.
    inversion H0. subst. subst...
Qed.

Theorem type_checking_complete : Γ t T,
  has_type Γ t T type_check Γ t = Some T.
Proof with auto.
  intros Γ t T Hty.
  induction Hty; simpl.
  - (* T_Var *) eauto.
  - (* T_Abs *) rewrite IHHty...
  - (* T_App *)
    rewrite IHHty1. rewrite IHHty2.
    rewrite (beq_ty_refl T11)...
  - (* T_True *) eauto.
  - (* T_False *) eauto.
  - (* T_If *) rewrite IHHty1. rewrite IHHty2.
    rewrite IHHty3. rewrite (beq_ty_refl T)...
Qed.

End STLCChecker.