Lecture 15
Lecture 15
Type Checking
YoungMin Kwon
Type Expressions
• Type Expressions
– Basic type or constructed type by applying type constructors to
type expressions.
• Basic type:
– boolean, integer, real, type_error, void,…
• Type name is a type expression
• Type constructor
– Arrays: if T is a type expression, then array(I,T) is a type
expression (I is the size of the array)
• array[10] of integer: array(10, integer)
– Products: if T1 and T2 are type expressions, then T1 * T2 is a
type expression (Cartesian product)
• integer * integer
Type Expressions
– Records: similar to products, but the fields have names
• record( (row * integer) * (column * integer) )
– Pointers: if T is a type, then pointer(T) is a type
• pointer(integer)
– Functions: if D and R are types, D -> R is a type
• integer * integer -> integer
• Type expressions may contain variables whose values are
type expressions.
• Tree and dag representation of
(char * char) -> pointer(integer)
Type Systems
• Type system
– A collection of rules for assigning type expressions to the
various parts of a program
– A type checker implements a type system
• Type checking
– Static type checking: checking is done by a compiler
– Dynamic type checking: checking is done when the target
program runs
– Sound type system: if a type system assigns a type to a program
part, type error shouldn’t occur for the program part.
– A language is strongly typed: static type checking guarantees
that type errors cannot occur during the runtime.
• Difficult to achieve
int a[100];
a[ a_very_complex_function() ] = 1;
A Simple Type Checker
• A Simple Language
P -> D ; E
D -> D ; D | id : T
T -> char | integer
| array [ num ] of T
| ^ T
E -> literal | num | id
| E mod E
| E [ E ]
| E ^
Type of an Id
• A Part of Translation Scheme
P -> D ; E
D -> D ; D
D -> id : T { addtype(id.entry, T.type) }
T -> char { T.type := char }
T -> integer { T.type := integer }
T -> ^ T1 { T.type := pointer(T1.type) }
T -> array [ num ] of T1 {
T.type := array(num.val, T1.type) }
Type Checking of Expressions
E -> literal { E.type := char }
E -> num { E.type := integer }
E -> id { E.type := lookup(id.entry) }
E -> E1 mod E2 { E.Type :=
if E1.type = integer and E2.type = integer
then integer
else type_error }
E -> E1 [ E2 ] { E.type :=
if E2.type = integer and E1.type = array(s,t)
then t
else type_error }
E -> E1^ { E.type :=
if E1.type = pointer(t)
then t
else type_error }
Type Checking of Statements
S -> id := E { S.type :=
if id.type = E.type
then void else type_error }
S -> if E then S1 { S.type :=
if E.type = boolean
then S1.type else type_error }
S -> while E do S1 { S.type :=
if E.type = boolean
then S1.type else type_error }
S -> S1 ; S2 { S.type :=
if S1.type = void and S2.type = void
then void else type_error }
Type Checking of Functions
• Type expression for functions
T -> T1 ‘->’ T2 {
T.type := T1.type -> T2.type }
• Rules for checking a function application
E -> E1 ( E2 ) { E.type :=
if E2.type = s and E1.type = s->t
then t else type_error }
• For more than one arguments
– T1,… ,Tn can be views as a single argument type
T1*… *Tn
Equivalence of Type Expressions
• “Two type expressions are equal” what does
that mean?
• Structural Equivalence
– Two expressions are either the same basic type,
– They are formed by applying the same constructor
to structurally equivalent types.
Structural Equivalence
function sequiv(s, t): boolean
begin
if s and t are the same basic type then
return true
else if s = array(s1,s2) and t = array(t1,t2) then
return s1 = t1 and sequiv(s2,t2)
else if s = s1*s2 and t = t1*t2 then
return sequiv(s1,t1) and sequiv(s2,t2)
else if s = pointer(s1) and t = pointer(t1) then
return sequiv(s1,t1)
else if s = s1->s2 and t = t1->t2 then
return sequiv(s1,t1) and sequiv(s2,t2)
else
return false
end
Names for Type Expressions
• In some languages types can have names
type link = ^cell;
var next : link;
last : link;
p : ^cell;
q, r : ^cell;
– Allow type expressions to be named, and allow the names to appear in
type expressions
– p: pointer(cell) type, next: link type
• Name Equivalence
– Views each type name as a distinct type.
– last and p are not the same type
• Structural Equivalence
– Names are replaced by the type expressions they define.
– next, last, p, q, and r are the same type
Type Conversions
• Coercions: Implicit conversion from one type to another by
the compiler
E -> num { E.type := integer }
E -> num . num { E.type := real }
E -> id { E.type = lookup(id.entry) }
E -> E1 op E2 { E.type :=
if E1.type = integer and E2.type = integer then
integer
else if E1.type = real and E2.type = integer then
real
else if E1.type = integer and E2.type = real then
real
else if E1.type = real and E2.type = real then
real
else
type_error }
Overloading
• An overloaded symbol has different meanings
depending on its context
– E.g. +: integer addition, real addition, complex
addition, string concatenation, …
– Overloading is said to be resolved if a unique
meaning is determined.
Set of Possible Types
• It is not always possible to resolve overloading immediately.
– E.g.)
function “*” (i, j: integer) return integer;
function “*” (i, j: integer) return complex;
function “*” (i, j: complex) return complex;
• 3*5 : either integer or complex
• 2*(3*5) : 3*5 must be integer because * takes same arg. types
• z*(3*5) : 3*5 must be complex if z is a complex type.
– integer ? real
– integer -> real ? α -> α
– integer -> α ? α -> α (all occurrences
of α must be replaced)
Unification
• Two type expressions t1 and t2 unify if there
is a substitution S such that
S(t1) = S(t2)
• A substitution is the most general unifier if
– S(t1) = S(t2)
– for any other unifier S’, S’ is an instance of S (for
any t, S’(t) < S(t))
Checking Polymorphic Functions
E -> E1 ( E2 ) {
p := mkleaf(newtypevar)
unify(E1.type, mknode(->, E2.type, p))
E.type := p
}
E -> E1, E2 {
E.type := mknode(*, E1.type, E2.type)
}
E -> id {
E.type := fresh(id.type)
}