On this page:
5.1 Introduction
5.2 Assignment
5.3 Features to Implement
5.3.1 Type Environment
5.3.2 let
5.3.3 Lists
5.3.4 Error Catching
5.4 Grammar
5.4.1 Abstract Syntax
5.5 Testing Guidelines
5.6 Starter Code
5.7 What To Hand In
5 Type Checker

    5.1 Introduction

    5.2 Assignment

    5.3 Features to Implement

      5.3.1 Type Environment

      5.3.2 let

      5.3.3 Lists

      5.3.4 Error Catching

    5.4 Grammar

      5.4.1 Abstract Syntax

    5.5 Testing Guidelines

    5.6 Starter Code

    5.7 What To Hand In

5.1 Introduction

In this assignment, you will implement a static type checker for an extension of the Paret language, Typed Paret.

5.2 Assignment

As in Interpreter, we have provided a function parse, which consumes an expression in Typed Paret’s concrete syntax and returns the abstract syntax representation (Expr) of that expression.
You will implement one function called type-of:

type-of :: Expr -> Type

that consumes a Paret program in abstract syntax form. If the program is well-typed, type-of returns the Type of that program; otherwise, it raises an exception.
Once you have implemented type-of, you should use the provided type-check function (analogous to eval from Interpreter) to write test cases for your type checker in your testing file.
For simplicity, Typed Paret does not have syntactic sugar, so and and or have been removed from the language.

5.3 Features to Implement
5.3.1 Type Environment

The type language you will work with is
#lang racket
(define-type Type
  (t-num)
  (t-bool)
  (t-str)
  (t-fun [arg-type : Type]
         [return-type : Type])
  (t-list [elem-type : Type]))
which, respectively, represent the types of numbers, Booleans, strings, (one-argument) functions, and (homogenous) lists.
Just as how Interpreter had an Env for mapping identifiers to values, your interpreter should use a type environment (TEnv) to keep track of the types of identifiers in scope.
#lang racket
(define-type-alias TEnv (Hashof Symbol Type))
In type-of, if the program binds an identifier that is already bound, the new binding should take precedence. This is known as identifier shadowing. The new binding shadows the existing binding.

5.3.2 let

let is a new expression which should accept a single identifier-value pair and a body. let evaluates the value, binds it to the identifier, and evaluates the body with the newly bound identifier in scope. For example, the following should evaluate to 3:
#lang racket
(let (x 1) (+ x 2))
let should disallow recursive definitions. That is, in (let (<id> <expr>) <body>), <id> should be bound in <body> but not in <expr>.

5.3.3 Lists

Paret now contains support for lists via the constant empty and the operations link, is-empty, first, and rest. Lists in Paret are homogeneous: all elements in the list must have the same type.
A question to briefly consider: What is the Type of the empty list? It’s polymorphic: it can be a list of any type at all! Because it has no elements, there’s nothing to constrain its type. However, because our type checker is not polymorphic, we handle this with a simple expedient: we require that every empty list be annotated with a type for the elements (that will eventually be linked to it). Naturally, the elements that eventually are linked to it must be consistent with that annotated type.
Thus, our typed list semantics are as follows:
  • empty
    (empty : t) makes an empty list whose elements have type t.

  • link :: t, (List t) -> (List t)
    (link x y) appends the element x to the front of the list y.

  • first :: (List t) -> t
    (first x) returns the first element of x.

  • rest :: (List t) -> (List t)
    (rest x) returns the list x except for the first element of x.

  • is-empty :: (List t) -> Bool
    (is-empty x) returns true if x is empty; otherwise, it returns false.

Whenever these type signatures are violated, the type checker should raise an error.
Error-checking link should occur in the following manner: If the type of the second argument to link isn’t a (List t), the (error TypeError "error message here") should be raised. If the second argument to link is a (List t) but the type of the first argument is not t, then (error TypeError "error message here") should be raised as well. For example,
#lang racket
(link "hello" 3)
(link 2 (empty : Bool))
should all raise (error TypeError "error message here").

5.3.4 Error Catching

Your type-checker should raise a generic Racket error in all scenarios that types do not match. When raising an error, follow the syntax below:
(error TypeError "error message here")
Racket’s “error” function should be followed by a symbol, which is preceded by a single quotation mark. In this case, we put a string symbol “TypeError” after the error call, which allows us to raise an error, signifying that a TypeError has occurred. Here is the Documentation for the error function call in plait language.
The error your type-checker will raise are just like the error from Interpreter, but with a type instead of a value.
Your type-checker should type-check by performing a post-order traversal of the AST: first, traverse all of the children of an expression from left to right, then check the expression itself.

5.4 Grammar

The grammar of Typed Paret is as follows:

<expr> ::= <num>

         | <string>

         | true

         | false

         | (+ <expr> <expr>)

         | (++ <expr> <expr>)

         | (num= <expr> <expr>)

         | (str= <expr> <expr>)

         | (if <expr> <expr> <expr>)

         | <id>

         | (<expr> <expr>)

         | (lam (<id> : <type>) <expr>)

         | (let (<id> <expr>) <expr>)

         | (first <expr>)

         | (rest <expr>)

         | (is-empty <expr>)

         | (empty : <type>)

         | (link <expr> <expr>)

 

<type> ::= Num

         | Str

         | Bool

         | (List <type>)

         | (<type> -> <type>)

Our parse function expects and enforces spaces around : and ->, so keep that in mind when you’re writing test cases.

5.4.1 Abstract Syntax

Refer to Type Environment for the definitions of TEnv and Type.
#lang racket
(define-type Expr
  (e-num [x : Number])
  (e-bool [x : Boolean])
  (e-str [x : String])
  (e-op [op : Operator]
        [left : Expr]
        [right : Expr])
  (e-un-op [op : UnaryOperator]
           [expr : Expr])
  (e-if [cond : Expr]
        [consq : Expr]
        [altern : Expr])
  (e-lam [param : Symbol]
         [arg-type : Type]
         [body : Expr])
  (e-app [func : Expr]
         [arg : Expr])
  (e-id [name : Symbol])
  (e-let [id : Symbol]
         [value : Expr]
         [body : Expr])
  (e-empty [elem-type : Type]))
 
(define-type Operator
  (op-plus)
  (op-append)
  (op-num-eq)
  (op-str-eq)
  (op-link))
 
(define-type UnaryOperator
  (op-first)
  (op-rest)
  (op-is-empty))

5.5 Testing Guidelines

For the purposes of testing, we have defined a type-check function that calls parse and type-of for you. type-check consumes an expression in Paret’s concrete syntax (S-Exp) and returns a Paret Type:

type-check :: S-Exp -> Type

You should use type-check in your testing file when writing test cases. You should not directly test type-of in your testing file.

5.6 Starter Code

We’ve set up a GitHub Classroom assignment that contains all necessary stencil code and support code here. Your groups should automatically match those you created for OMac (and SMoLTalk).
We’ve provided starter code for your implementation at type-checker.rkt and support code at support.rkt. You are not allowed to change the signature of type-check and type-of, but you are welcome to add any helper functions that you need for your implementation.
We’ve also provided a stencil for your type-check test cases at type-checker-tests.rkt and testing support code at test-support.rkt. You should check that you can run your type-checker-tests.rkt file successfully in DrRacket before submitting—if you can’t, it means that a definition is missing or you’re trying to test a function that you shouldn’t be testing.

5.7 What To Hand In

You will submit two files for this assignment:
  • type-checker.rkt, which should be uploaded to the “Code” drop on Gradescope.

  • type-checker-tests.rkt, which should be uploaded to the “Tests” drop on Gradescope.

You may also select the entire tcheck repository to submit to both drops on Gradescope. You can update your submissions as many times as you want before the deadline. If you have a partner, you can also add them to your submission on Gradescope.
You can update your submissions as many times as you want before the deadline.