# Type Checking IV Lecture 10

## Review

### Quiz

Prove the following expression is correctly-typed

{(x, int)} |- <x + 2 * 3> : int


according to the SimpleC rules:

// literals: the symbols mean their equivalent mathmetical values for numbers and boolean true/false.

--------------
T : <n> : int

--------------
T : <b> : bool

--------------
T : <s> : string

// operators: the symbols for arithmetic, boolean, and relationship operators each have a function type

S |- <e1> : int     S |- <e2> : int     op \in { "+", "-", "*", "/" }
---------------------------------------------------------------------
S |- <e1 op e2> : int

S |- <e1> : bool     S |- <e2> : bool     op \in { "&&", "||" }
----------------------------------------------------------------
S |- <e1 op e2> : bool

S |- <e1> : bool
--------------------
S |- <!e1> : bool

S |- <e1> : int
--------------------
S |- <-e1> : int

// variables: variables assignments evaluate their right-hand side at define-time and are stored and looked up in a storage context.

S' = [(x, t)] + S
-----------------  [declaration]
S |- <x : t> : S'

t = lookup(x, S)
---------------    [substitution]
S |- <x> : t

S |- <e> : t1     t2 = lookup(x, S)    t1 = t2
---------------------------------------------  [assignment]
S |- <x = e;> : S


## The implementation

### Relevant skeleton files

• src/simplec/TypeChecker.java
• src/simplec/Type.java
• src/simplec/Scope.java

### src/simplec/TypeChecker.java

• You implement this for your project
• Visitors for each grammar construct

### src/simplec/Type.java

• Implemented for you
• Internal representation of types
• Singleton classes for int, bool, and string
• Construct for function types
• Type equality checker

### src/simplec/Scope.java

• Implemented for you
• The symbol table implementation
• Support for creating symbol tables in nested scopes

### Building and running your type checker

# from root of your repository
source configure.sh
cd src/simplec
make
java Compiler ../../tests/example.simplec


If "make" fails, be sure you have ANTLR in your CLASSPATH and PATH (which source configure.sh will do for you), check that you have no compilation errors in your *.java files, and be sure you are in the =src/simplec directory.

## Type.java

### References primitive types

• E.g, Type.INT

@Override
public Type visitNum(GrammarParser.NumContext ctx) {
info(ctx, String.format("number constants always have type int"));
return Type.INT;
}


Reference the singleton class for int, bool, or string within the Type namespace, e.g., Type.INT.

In this example, whenever we visit a number in our parse tree, it will always have type int according to the rules of SimpleC.

Use "info" to write out messages useful for debugging. These are output to standard error, so they will not interfere with the output of the compiler and will be ignored for grading.

### Handles construction of function types

• E.g., Type.FunctionType funType = new Type.FunctionType(paramTypes, returnType);

@Override
public Type visitDef(GrammarParser.DefContext ctx) {
String name = ctx.ID().getText();
if (scope.hasScope(name)) {
}
Type returnType = visit(ctx.type());
info(ctx, "create the function's scope and add its parameters");
Scope localScope = new Scope(scope);
if (null != ctx.formalParams()) {
for (GrammarParser.FormalParamContext formalParam : ctx.formalParams().formalParam()) {
String paramName = formalParam.ID().getText();
Type paramType = visit(formalParam.type());
if (localScope.hasSymbol(paramName)) {
Type.typeError(ctx, "function parameters must have unique names");
}
info(ctx, String.format("add parameter to the function-local scope %s : %s", paramName, paramType));
}
}
info(ctx, String.format("check for duplicate definitions of %s", name));
Type.FunctionType funType = new Type.FunctionType(paramTypes, returnType);
if (scope.hasSymbol(name)) {
Type declType = scope.getSymbol(name);
if (declType.equals(funType)) {
info(ctx, String.format("the function is declared as the same type in the current scope %s : %s", name, funType));
} else {
Type.typeError(ctx, String.format("%s's declaration doesn't match definition %s != %s", name, funType, declType));
}
} else {
info(ctx, String.format("add the function to the current scope %s : %s", name, funType));
}
info(ctx, "enter the function's local scope");
scope = localScope;
for (GrammarParser.DeclContext dctx : ctx.decl()) visit(dctx);
for (GrammarParser.StmtContext sctx : ctx.stmt()) visit(sctx);
scope = scope.getParent();
return Type.VOID;
}


This code gathers the return type, collects the parameters, creates and populates the local scope, then finally creates a function type out of the parameter types (paramTypes) and return type (returnType).

### Checking type equality

• E.g., Type.BOOL.equals(condition)

@Override
public Type visitWhile(GrammarParser.WhileContext ctx) {
Type condition = visit(ctx.expr());
info(ctx, String.format("check that the while condition is a bool %s", condition));
if (! Type.BOOL.equals(condition)) {
Type.typeError(ctx, "while condition should be bool");
}
visit(ctx.stmt());
return Type.VOID;
}


This code, from visitWhile, gets the type of the condition, then compares it against the singleton BOOL type.

## Scope.java

### Creating scopes and adding symbols

• this.scope = new Scope();
• This sets the current scope.
• this.scope.addSymbol("true", Type.BOOL);
@Override
public Type visitProgram(GrammarParser.ProgramContext ctx) {
// prepare the global scope
this.scope = new Scope();
// add the true and false keywords
// set the current function to null, since we are in the global scope
for (GrammarParser.ToplevelContext tctx : ctx.toplevel()) {
visit(tctx);
}
return Type.VOID;
}


This code creates the global scope and adds the builtin symbols for SimpleC, then visits all the top-level declarations and definitions.

Note that statements and declarations themselves return no type, so Type.VOID is returned.

### Creating a nested scope

• scope.addScope(name, localScope);
• scope = localScope;
• This sets the current scope for other visitor functions.
• scope = scope.getParent();

@Override
public Type visitDef(GrammarParser.DefContext ctx) {
String name = ctx.ID().getText();
if (scope.hasScope(name)) {
}
Type returnType = visit(ctx.type());
info(ctx, "create the function's scope and add its parameters");
Scope localScope = new Scope(scope);
if (null != ctx.formalParams()) {
for (GrammarParser.FormalParamContext formalParam : ctx.formalParams().formalParam()) {
String paramName = formalParam.ID().getText();
Type paramType = visit(formalParam.type());
if (localScope.hasSymbol(paramName)) {
Type.typeError(ctx, "function parameters must have unique names");
}
info(ctx, String.format("add parameter to the function-local scope %s : %s", paramName, paramType));
}
}
info(ctx, String.format("check for duplicate definitions of %s", name));
Type.FunctionType funType = new Type.FunctionType(paramTypes, returnType);
if (scope.hasSymbol(name)) {
Type declType = scope.getSymbol(name);
if (declType.equals(funType)) {
info(ctx, String.format("the function is declared as the same type in the current scope %s : %s", name, funType));
} else {
Type.typeError(ctx, String.format("%s's declaration doesn't match definition %s != %s", name, funType, declType));
}
} else {
info(ctx, String.format("add the function to the current scope %s : %s", name, funType));
}
info(ctx, "enter the function's local scope");
scope = localScope;
for (GrammarParser.DeclContext dctx : ctx.decl()) visit(dctx);
for (GrammarParser.StmtContext sctx : ctx.stmt()) visit(sctx);
scope = scope.getParent();
return Type.VOID;
}


This code creates a local scope and updates the current scope. Once the body of the function has been visited, it restores the scope to the parent scope.

### Checking symbol table and type errors

• scope.hasSymbol(name)
• Type.typeError(ctx, String.format("%s already declared", name));

• The string message you give is irrelevant to grading
@Override
public Type visitDecl(GrammarParser.DeclContext ctx) {
String name = ctx.ID().getText();
Type type = visit(ctx.type());
if (! scope.hasSymbol(name)) {
info(ctx, String.format("add the declaration to the current scope %s : %s", name, type));
} else {
}
return Type.VOID;
}


## Type checker pseudocode

### visitProgram

• initialize global scope
• add built-ins to symbol table
• visit the top level nodes

### visitDecl

• visit the type
• add the name to the symbol table unless it's already declared

### visitAssignment

• compare the type of the symbol to the discovered type of the right-hand-side

### visitIfThenElse

• Check that the condition is a boolean
• Check the bodies of the if and else

### visitIfThen

• Check that the condition is a boolean
• Check the body of the if

### visitReturn

• lookup the type of the return (returnTypeSymbol)
• check it against the expression's type

### visitExprStmt

• check the expression

• do nothing

### visitCompound

• check the contents of the compound statement (iterate over the statements)

### visitIntType

• return the int type

### visitBoolType

• return the bool type

### visitStringType

• return the string type

### visitFunType

• construct a function from the list of parameter types and the return type

### visitCall

• make sure the number of parameters matches the actual parameters
• get the types of the expressions
• iterate over the expressions and visit them to get the type
• for (GrammarParser.ExprContext expr : ctx.actualParams().expr())
• use the function's return type as the resulting type
• type error otherwise

### visitNegate

• check that the expression is an int
• return an int
• type error otherwise

### visitNot

• check that the expression is an bool
• return a bool
• type error otherwise

### visitMultDiv

• check that the expressions are int
• return an int
• type error otherwise

• check that the expressions are int
• return an int
• type error otherwise

### visitRelational

• check that the expressions are int
• return a bool
• type error otherwise

### visitEqNeq

• check that the expressions are int
• return a bool
• type error otherwise

### visitAndOr

• check that the expressions are bool
• return a bool
• type error otherwise

### visitVar

• passthrough the expression's type

### visitNum

• return an int type

### visitStringLiteral

• return a string type

### visitParens

• passthrough the expression's type

### visitSimple

• lookup the symbol, if it exists
• use scope.getTypeAnyScope to search all parent scopes as well
• type error otherwise

## Project

(2 weeks)

• References to keep on hand
• Write test programs as you go
• The type checker reports nothing when the input program is correctly-typed
• But an unfinished type checker will also report nothing even when the program is incorrectly-typed
• Need to create incorrectly-typed programs to trigger type errors
• Should be writing and adding tests cases to the tests/ folder
• Will check for these when grading
• Start from leaves of the grammar and simpler constructs in the grammar
• Build up to complex ones, once the child nodes are well-tested and working
• Assume the child nodes' are correct, and implement parent visitor on its own
• Submission instructions
• Run make clean` and/or reclone your repo in another directory to build from scratch