Typechecking Cool and PA3
Agenda
• About PA3 & Starter Code
• Typechecking the AST (simplified)
• Typechecking the AST (with rules in detail)
PA#3
• It’s big. More code than PA1 and PA2.
• Some tricky concepts.
• Many corner cases.
• If you haven’t started, start ASAP.
PA#3 Observations
• You have a lot of flexibility
Choices of data structures
• Error messages
• Code organization
• Algorithmic choices (how many passes, etc.)
• ……
•
• The starter code is minimal
•
•
You’ll need to write some boilerplate
You should (must?) change starter code
We provide
‘classes’ object
PA#3 starter code
Classes_class
is-list-of
Class__class
“Phylum” for Cool classes
is-a
class__class
“Constructor” for Cool classes
PA#3 starter code
Phyla
Classes_class
Feature_class
is-list-of
is-a
is-a
Class__class
attr_class
has-a
method_class
has-a
is-a
class__class
has-list-of
Expression_class
assign_class
let_class
plus_class
…
PA#3 starter code
Classes_class
Feature_class
is-list-of
is-a
is-a
Class__class
attr_class
has-a
method_class
• Error Handler
• Basic classes (Object, Int, etc.)
in AST form
• Symbol Table
• “ClassTable”
has-a
is-a
class__class
has-list-of
Expression_class
assign_class
let_class
plus_class
constructor: has members you need to write accessors for
…
PA#3 starter code
Classes_class
Feature_class
is-list-of
is-a
is-a
Class__class
attr_class
has-a
method_class
has-a
is-a
class__class
Expression_class
has-list-of
assign_class
id
let_class
plus_class
…
PA#3 Tips
• Don’t write phases independently (to combine later)
•
•
The first phase(s) are much simpler than the typechecking phase.
Typechecking has many dependencies on first phase.
• Understand the type checking rules!
PA#3 Tips
• Before submitting, read chap 1-13 of the cool manual again.
• Look for corner cases mentioned in manual.
• Testing:
•
•
(common case) make sure your implementation does what you think it does!
(corner case) think of examples that will break your code
Example
class Foo {
bar(id1 : Type1, id2 : Type2) : Type3 { …… }
};
Other than typechecking, what must we verify about bar?
Questions on PA3 or the starter code?
Typechecking Examples
Running Example
class Main {
y : String <- "abba";
z : Int <- 2;
main() : Object {
let x : Int <- y.length() in x+z
};
};
Running Example
class Main {
Main < Object
y : String
z : Int
“abba”
2
y : String <- "abba";
main() : Object
z : Int <- 2;
main() : Object {
let x : Int
let x : Int <- y.length() in
x+z
dyn dispatch:
method “length”
+
};
};
y
no args
x
z
Before Typechecking
• Compute “Method Environment” (M)
• Any data structure you like!
• Need to be able to lookup methods by type and name
Main.main() : Object
Object.cool_abort() : Object
Object.type_name() : String
Object.copy() : SELF_TYPE
String.concat(String) : String
String.length() : Int
String.substr(Int, Int) : String
(and all other functions…)
Before Typechecking
• Compute initial “Object Environment” for each class C. (OC)
• Good idea to use symbol table (but not required!)
• Lists the attributes of the class
• Example for our Main class
OMAIN
Symbol
y
Type
String
z
Int
Typechecking Example
Id Type
Main < Object
y : String
“abba”
String
z : Int
main() : Object
2
let x : Int
Int
Int
y
String
z
Int
self SELF_TYPEMain
Id Type
x
dyn dispatch:
method “length”
y
String
no args
+ Int
Int
x
Int
z
Int
Int
but what about all those rules?
Rule for Typechecking an Attribute
OC (x) = T0
OC [SELF_TYPEC/self], M, C Ͱ e1 : T1
T1 ≤ T 0
OC, M, C Ͱ x : T0 e1
• What are O, M and C?
Rule for Attributes
Id Type
• O is the object environment.
y
String
z
Int
self SELF_TYPEMain
• M is the method environment.
Main.main() : Object
Object.cool_abort() : Object
Object.type_name() : String
Object.copy() : SELF_TYPE
String.concat(String) : String
String.length() : Int
String.substr(Int, Int) : String
(and all other functions…)
• C is the class we’re typechecking (Main)
O, M, C Ͱ e1 : T1
T1 ≤ T0
O[T0/x], M, C Ͱ e2 : T2
O, M, C Ͱ let x : T0 e1 in e2 : T2
To typecheck an expression, you need to know O, M and C.
• The method environment (M) is constant.
• The object environment (O) starts with OC when typechecking C.
•
As you traverse top-to-bottom scopes are added.
• C is just the class that contains the expression.
Rule for Typechecking an Attribute
OC (x) = T0
OC [SELF_TYPEC/self], M, C Ͱ e1 : T1
T1 ≤ T 0
OC, M, C Ͱ x : T0 e1
• What are x, e1, T0, T1?
Typechecking an Attribute
• E.g. how do we typecheck y : String <- "abba" in class Main?
• Trick: read the rule backward!
OC (x) = T0
OC [SELF_TYPEC/self], M, C Ͱ e1 : T1
T1 ≤ T0
OC, M, C Ͱ x : T0 e1
• We want x -> y, T0 -> String, e1 -> “abba”. Don’t know T1 yet.
y : String <- “abba” (in class Main)
• We instantiate the rule with
C=Main, x = y, T0 = String, e1 = “abba”
• There are three things we need to
check!
OC (x) = T0
OC [SELF_TYPEC/self], M, C Ͱ e1 : T1
T1 ≤ T0
OC, M, C Ͱ x : T0 e1
OMain (y) = String
OMain [SELF_TYPEMain/self], M, Main Ͱ “abba” : T1
T1 ≤ String
OC, M, Main Ͱ y : String “abba”
y : String <- “abba” (in class Main)
OMain (y) = String
OMain [SELF_TYPEMain/self], M, Main Ͱ “abba” : T1
T1 ≤ String
OC, M, Main Ͱ y : String “abba”
• OMain(y) = String Look at the table we made earlier!
Symbol Type
y
String
z
Int
y : String <- “abba” (in class Main)
OMain (y) = String
OMain [SELF_TYPEMain/self], M, Main Ͱ “abba” : T1
T1 ≤ String
OMain, M, Main Ͱ y : String “abba”
• OMain [SELF_TYPEMain/self], M, Main Ͱ “abba” : T1
•
•
•
We need to prove that “abba” has type T1 in extended environment
We need not know what T1 is (yet).
This is where the typechecker makes a recursive call.
Typechecking “abba”
• We need to prove “abba” has type T0
• with this augmented object environment,
• Only one rule applies to a string constant:
Symbol
y
z
Type
String
Int
self
SELF_TYPEMAIN
x is string constant
O, M, C Ͱ x : String
• We can return “String” – no further checks required.
Now,
replace T1
by String
y : String <- “abba” (in class Main)
OMain (y) = String
OMain [SELF_TYPEMain/self], M, Main Ͱ “abba” : T1
T1 ≤ String
OMain, M, Main Ͱ y : String “abba”
T1 replaced
by String
y : String <- “abba” (in class Main)
OMain (y) = String
OMain [SELF_TYPEMain/self], M, Main Ͱ “abba” : String
String ≤ String
OMain, M, Main Ͱ y : String “abba”
• OMain [SELF_TYPEMain/self], M, Main Ͱ “abba” : T1
•
We just proved this with the recursive call.
• String ≤ String is true since ≤ is reflexive.
A complete proof
“abba” is a string constant
OMain [SELF_TYPEMain/self], M, Main Ͱ “abba” : String
OMain (y) = String
OMain [SELF_TYPEMain/self], M, Main Ͱ “abba” : String
String ≤ String
OMain, M, Main Ͱ y : String “abba”
Last example: typechecking main()
Last example
• Type check main()
class Main {
y : String <- "abba";
• Going to simplify the rules slightly
z : Int <- 2;
main() : Object {
(will only consider some of the
SELF_TYPE cases)
let x : Int <- y.length() in
x+z
};
};
Id Type
y
String
z
Int
Typechecking Example
[Method]
main() : Object
M(C,f) = (T1, T2, …, Tn, T0)
OC[SELF_TYPEC/self][T1/x1]…[Tn/xn], M, C Ͱ e : T1
let x : Int
T1 ≤ T0
OC, M, C Ͱ f(x1 : T1, …, xn : Tn) : T0 { e }
dyn dispatch:
method “length”
+
x
y
no args
z
C -> Main
f -> main
n -> 0
e -> let x : Int ….
Id Type
y
String
z
Int
Typechecking Example
[method]
main() : Object
M(Main,main) = (T0)
OMain[SELF_TYPEC/self], M, Main Ͱ { let x … } : T1
let x : Int
T1 ≤ Object
OMain, M, Main Ͱ main() : T0 { let x … }
dyn dispatch:
method “length”
+
x
y
no args
z
Id Type
y
String
z
Int
Typechecking Example
[Method]
main() : Object
M(Main,main) = (T0)
OMain[SELF_TYPEC/self], M, Main Ͱ { let x … } : T1
let x : Int
T1 ≤ Object
OMain, M, Main Ͱ main() : T0 { let x … }
dyn dispatch:
method “length”
+
x
y
no args
z
Id Type
y
String
z
Int
Typechecking Example
[Method]
main() : Object
M(Main,main) = (Object)
OMain[SELF_TYPEC/self], M, Main Ͱ { let x … } : T1
let x : Int
T1 ≤ Object
OMain, M, Main Ͱ main() : Object { let x … }
dyn dispatch:
method “length”
+
x
y
no args
z
Id Type
y
String
z
Int
Typechecking Example
[Method]
main() : Object
M(Main,main) = (Object)
OMain[SELF_TYPEC/self], M, Main Ͱ { let x … } : T1
let x : Int
T1 ≤ Object
OMain, M, Main Ͱ main() : Object { let x … }
dyn dispatch:
method “length”
+
x
y
no args
z
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Let-Init]
main() : Object
O, M, C Ͱ e1 : T1
T1 ≤ T0
let x : Int
O[T0/x], M, C Ͱ e2 : T2
O, M, C Ͱ let x : T0 e1 in e2 : T2
dyn dispatch:
method “length”
+
x
y
no args
z
e1 -> y.length()
e2 -> x + z
T0 -> Int
Id Type
y
String
z
Int
Typechecking Example
[Let-Init]
self SELF_TYPEMAIN
main() : Object
O, M, C Ͱ y.length() : T1
T1 ≤ Int
let x : Int
O[Int/x], M, C Ͱ x+z : T2
O, M, C Ͱ let x : Int y.length() in x+z : T2
dyn dispatch:
method “length”
+
x
y
no args
z
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Dispatch]
main() : Object
O, M, C Ͱ e0 : T0 … O, M, C Ͱ en : Tn
T0’ = { C if T0 = SELF_TYPEC; T0 otherwise }
let x : Int
M(T0’, f) = (T1’, …, Tn+1’)
Ti ≤ Ti’ for 1 ≤ i ≤ n
dyn dispatch:
method “length”
+
x
y
no args
Tn+1 = { T0 if T’n+1 = SELF_TYPE; T’n+1 otherwise }
O, M, C Ͱ e0.f(e1, …, en) : Tn+1
z
n -> 0 (there are no arguments)
f -> “length”
e0 -> y
C -> Main
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Dispatch]
main() : Object
O, M, Main Ͱ e0 : T0
T0’ = { Main if T0 = SELF_TYPEMain; T0 otherwise }
let x : Int
M(T0’, “length”) = (T1’)
T1 ≤ T1’
dyn dispatch:
method “length”
+
x
y
no args
T1 = { T0 if T’ 1 = SELF_TYPE; T’ 1 otherwise }
O, M, Main Ͱ y.length() : T1
z
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Var]
main() : Object
O(Id) = T
O, M, C Ͱ Id : T
let x : Int
dyn dispatch:
method “length”
+
x
y
no args
z
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Var]
main() : Object
O(y) = String
O, M, C Ͱ y : String
let x : Int
dyn dispatch:
method “length”
+
x
y
no args
z
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Var]
main() : Object
O(y) = String
O, M, C Ͱ y : String
let x : Int
dyn dispatch:
method “length”
+
x
y
String
no args
z
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Dispatch]
main() : Object
O, M, C Ͱ y : T0
T0’ = { Main if T0 = SELF_TYPEMain; T0 otherwise }
let x : Int
M(T0’, “length”) = (T1’)
T1 ≤ T1’
dyn dispatch:
method “length”
+
x
y
String
no args
T1 = { T0 if T’ 1 = SELF_TYPE; T’ 1 otherwise }
O, M, C Ͱ y.length() : T1
z
T0 -> String
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Dispatch]
main() : Object
O, M, C Ͱ y : String
T0’ = { Main if Str = SELF_TYPEMain; Str otherwise }
let x : Int
M(T0’, “length”) = (T1’)
T1 ≤ T1’
dyn dispatch:
method “length”
+
x
y
String
no args
T1 = { Str if T’ 1 = SELF_TYPE; T’ 1 otherwise }
O, M, C Ͱ y.length() : T1
z
Now T0’ = String
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Dispatch]
main() : Object
O, M, C Ͱ y : String
T0’ = String
let x : Int
M(String, “length”) = (T1’)
T1 ≤ T1’
dyn dispatch:
method “length”
+
x
y
String
no args
T1 = { Str if T’ 1 = SELF_TYPE; T’ 1 otherwise }
O, M, C Ͱ y.length() : T1
z
Now, look up String.length in the
method environment. Does it
really have no parameters?
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Dispatch]
main() : Object
O, M, C Ͱ y : String
T0’ = String
let x : Int
M(String, “length”) = (T1’) = (Int)
T1 ≤ T1’
dyn dispatch:
method “length”
+
x
y
String
no args
T1 = { Str if T’ 1 = SELF_TYPE; T’ 1 otherwise }
O, M, C Ͱ y.length() : T1
z
Yes! It returns an Int.
Now T1 = T1’ = Int.
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Dispatch]
main() : Object
O, M, C Ͱ y : String
T0’ = String
let x : Int
M(String, “length”) = (T1’) = (Int)
T1 ≤ T1’ Int ≤ Int
dyn dispatch:
method “length”
+
x
y
String
no args
T1 = Int
O, M, C Ͱ y.length() : Int
z
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Dispatch]
main() : Object
O, M, C Ͱ y : String
T0’ = String
let x : Int
M(String, “length”) = (T1’) = (Int)
T1 ≤ T1’ Int ≤ Int
dyn dispatch:
method “length”
Int
y
String
no args
+
x
T1 = Int
O, M, C Ͱ y.length() : Int
z
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Let-Init]
main() : Object
O, M, C Ͱ y.length() : T1
T1 ≤ Int
let x : Int
O[Int/x], M, C Ͱ x+z : T2
O, M, C Ͱ let x : Int y.length() in x+z : T2
dyn dispatch:
method “length”
Int
y
String
no args
+
x
z
Id Type
y
String
z
Int
Typechecking Example
[Let-Init]
self SELF_TYPEMAIN
main() : Object
O, M, C Ͱ y.length() : Int
Int ≤ Int
let x : Int
O[Int/x], M, C Ͱ x+z : T2
O, M, C Ͱ let x : Int y.length() in x+z : T2
dyn dispatch:
method “length”
Int
y
String
no args
+
x
z
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Let-Init]
main() : Object
O, M, C Ͱ y.length() : Int
Int ≤ Int
let x : Int
O[Int/x], M, C Ͱ x+z : T2
O, M, C Ͱ let x : Int y.length() in x+z : T2
dyn dispatch:
method “length”
Int
y
String
no args
+
x
z
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
Id Type
x
Int
[Arith]
main() : Object
O, M, C Ͱ e1 : Int
O, M, C Ͱ e2 : Int
let x : Int
op ∈ { *, +, -, / }
O, M, C Ͱ e1 op e2 : Int
dyn dispatch:
method “length”
Int
y
String
no args
+
x
z
e1 -> x
e2 -> z
op -> +
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
Id Type
x
Int
[Arith]
main() : Object
O, M, C Ͱ x : Int
O, M, C Ͱ z : Int
let x : Int
+ ∈ { *, +, -, / }
O, M, C Ͱ x + z : Int
dyn dispatch:
method “length”
Int
y
String
no args
+
x
z
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
Id Type
x
Int
[Var]
main() : Object
O, M, C Ͱ y : Int
let x : Int
dyn dispatch:
method “length”
Int
y
String
O(x) = Int
no args
+
z
x
Int
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
Id Type
x
Int
[Var]
main() : Object
O, M, C Ͱ z : Int
let x : Int
dyn dispatch:
method “length”
Int
y
String
O(z) = Int
no args
+
z
x
Int
Int
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
Id Type
x
Int
[Arith]
main() : Object
O, M, C Ͱ x : Int
O, M, C Ͱ z : Int
let x : Int
+ ∈ { *, +, -, / }
O, M, C Ͱ x + z : Int
dyn dispatch:
method “length”
Int
y
String
no args
+ Int
z
x
Int
Int
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Let-Init]
main() : Object
O, M, C Ͱ y.length() : Int
Int ≤ Int
let x : Int
O[Int/x], M, C Ͱ x+z : T2
O, M, C Ͱ let x : Int y.length() in x+z : T2
dyn dispatch:
method “length”
Int
y
String
no args
+ Int
z
x
Int
Int
T2 -> Int
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Let-Init]
main() : Object
O, M, C Ͱ y.length() : Int
Int ≤ Int
let x : Int
O[Int/x], M, C Ͱ x+z : Int
O, M, C Ͱ let x : Int y.length() in x+z : Int
dyn dispatch:
method “length”
Int
y
String
no args
+ Int
z
x
Int
Int
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Let-Init]
main() : Object
O, M, C Ͱ y.length() : Int
Int ≤ Int
let x : Int
Int
O[Int/x], M, C Ͱ x+z : Int
O, M, C Ͱ let x : Int y.length() in x+z : Int
dyn dispatch:
method “length”
Int
y
String
no args
+ Int
z
x
Int
Int
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Method]
main() : Object
M(Main,main) = (Object)
OMain[SELF_TYPEC/self], M, Main Ͱ { let x … } : T1
let x : Int
Int
T1 ≤ Object
OMain, M, Main Ͱ main() : Object { let x … }
dyn dispatch:
method “length”
Int
y
String
no args
+ Int
T1 -> Int
z
x
Int
Int
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Method]
main() : Object
M(Main,main) = (Object)
OMain[SELF_TYPEC/self], M, Main Ͱ { let x … } : Int
let x : Int
Int
Int ≤ Object
OMain, M, Main Ͱ main() : Object { let x … }
dyn dispatch:
method “length”
Int
y
String
no args
+ Int
z
x
Int
Int
Id Type
y
String
z
Int
Typechecking Example
self SELF_TYPEMAIN
[Method]
main() : Object
M(Main,main) = (Object)
OMain[SELF_TYPEC/self], M, Main Ͱ { let x … } : Int
let x : Int
Int
Int ≤ Object
OMain, M, Main Ͱ main() : Object { let x … }
dyn dispatch:
method “length”
Int
y
String
no args
+ Int
z
x
Int
Int
© Copyright 2026 Paperzz