100% found this document useful (1 vote)
992 views48 pages

Chapter Five: Type Checking

This document discusses type checking in programming languages. It defines type checking as the process by which a compiler verifies that a program follows the semantic conventions of the language. This includes ensuring operators and operands are compatible, variables match assigned values, and functions match parameters. The document contrasts static type checking, done at compile time, with dynamic checking done at runtime. It describes type systems and type expressions used to define rules for assigning types in a program.

Uploaded by

Vuggam Venkatesh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
992 views48 pages

Chapter Five: Type Checking

This document discusses type checking in programming languages. It defines type checking as the process by which a compiler verifies that a program follows the semantic conventions of the language. This includes ensuring operators and operands are compatible, variables match assigned values, and functions match parameters. The document contrasts static type checking, done at compile time, with dynamic checking done at runtime. It describes type systems and type expressions used to define rules for assigning types in a program.

Uploaded by

Vuggam Venkatesh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
You are on page 1/ 48

CHAPTER FIVE

Type checking

1
Outline
• Type checking
• Static versus Dynamic Checking
• Static checking
• Type systems
• Type Expression
• Specification of a simple type checker
• A Simple Language example
• Structural Equivalence of Type Expressions
• Names for Type Expressions
• Type Conversions or casting

2
Type checking
• The compiler must check that the source program follows both
the syntactic and semantic conventions of the source language.

• Semantic Checks
– Static – done during compilation
– Dynamic – done during run-time
• This checking is called static checking (to distinguish it from
dynamic checking executed during execution of the target
program).
• Static checking ensures that certain kind of errors will be
detected and reported.

3
Position of type checker

Syntax Parse
Token Parser tree Type tree IC gen
stream checker

IC code

Parser Type IC gen


checker

Parse tree ONE


PASS
4
Static versus Dynamic Checking
• Static checking: the compiler enforces programming
language’s static semantics
– Program properties that can be checked at compile time
• Dynamic semantics: checked at run time
– Compiler generates verification code to enforce
programming language’s dynamic semantics

• Type checking is one of these static checking operations.


– we may not do all type checking at compile-time.
– Some systems also use dynamic type checking too.

5
Why static checking?
• Parsing finds syntactic errors
– An input that can't be derived from the grammar

• Static checking finds semantic errors


– Calling a function with the wrong number/kind of arguments
– Applying operators to the wrong kinds of arguments
– Using undeclared variables
– Invalid conditions (not boolean) in conditionals
– inappropriate instruction
• return, break, continue used in wrong place
6
Other Static Checks
• A variety of other miscellaneous static checks can be
performed

– Check for return statements outside of a function


– Check for case statements outside of a switch statement
– Check for duplicate cases in a case statement
– Check for break or continue statements outside of any loop
– Check for goto statements that jump to undefined labels
– Check for goto statements that jump to labels not in scope

• Most such checks can be done using 1 or 2 traversals of


(part of) the parse tree
7
The Need for Type checking
• We want to generate machine code
• Memory layout
– Different data types have different sizes
• In C, char, short, int, long, float, double usually have
different sizes
• Need to allocate different amounts of memory for
different types
• Choice of instructions
– Machine instructions are different for different types
• add (for i386 ints)
• fadd (for i386 floats)
8
Type Checking
• One important kind of static checking is type checking

– Do operators match their operands?


– Do types of variables match the values assigned to them?
– Do function parameters match the function declarations?
– Have called function and variable names been declared?

• Not all languages can be completely type checked

• All compiled languages must be at least partially type


checked
9
Type Checking…
• Type checking can be done bottom up using the parse
tree
• For convenience, we may create one or more pseudo-
types for error handling purposes
– Error type can be generated when a type checking
error occurs
• e.g., adding a number and a string
– Unknown type can be generated when the type of an
expression is unknown
• e.g., an undeclared variable

10
Static checking
Typical examples of static checking are:
– Type checks
– Flow-of-control checks
– Uniqueness checks…
Type checks:
• a compiler should report an error if an operator is applied to an
incompatible operand.
Example: if an array variable and a function variable are added
together.
int a, c[10],d;
d = c + d;
11
Type checking
Flow of control check:
• Statements that cause flow of control to leave a construct must have some place to
which to transfer the flow of control.
• Example: a break statement in C causes control to leave the smallest enclosing
while, for, or switch statement.
• An error occurs if such an enclosing statement does not exist.

for(i=0;i<attempts;i++) {
cout<<“Please enter your password:”;
cin>>password;
if(verify(password))
break;//OK
cout<<“incorrect\n”;
}

12
Flow of control example…
foo()
{
foo()

{…
break;//ERROR
switch(a)
}
{ case 0:

foo() break;// OK
{… case 1:
while (n) …
{… }
if (i>10) }
break;// OK
}
}
13
Type checking…
Uniqueness check:
• Variables or objects must be defined exactly once.
• Example: in most PL, an identifier must be declared uniquely.

foo() foo(int a, int a)//ERROR


{ {
int i, j, i;//ERROR …
… }
}
struct myrec
{ int name;
};
struct myrec //ERROR
{int id;
}; 14
One-Pass versus Multi-Pass Static Checking
• One-pass compiler: static checking in C, Pascal,
Fortran, and many other languages is performed in one
pass while intermediate code is generated
– Influences design of a language: placement
constraints

• Multi-pass compiler: static checking in Ada, Java, and


C# is performed in a separate phase, sometimes by
traversing a syntax tree multiple times.
- A separate type-checking pass between parsing and
intermediate code generation.
15
Type checking…
• In this chapter, we focus on type checking.
• A type checker verifies that the type construct matches that
expected by its context.
• For example:
– The type checker should verify that the type value assigned to
a variable is compatible with the type of the variable.
– Built in operator mod requires integer operands.
– Indexing is done only to array.
– Dereference to pointer
– A user-defined function is applied to the correct number and
type of arguments…

16
Type systems
• A type system is a collection of rules for assigning type
expressions to the parts of a program.

• A type checker implements a type system.


• A sound type system eliminates run-time type checking for type
errors.
• A programming language is strongly-typed, if every program its
compiler accepts will execute without type errors.
– In practice, some of type checking operations are done at run-
time (so, most of the programming languages are not strongly-
typed).
– Ex: int x [100]; … x[i]  most of the compilers cannot
guarantee that i will be between 0 and 99

17
Type expressions
• The type of a language construct is denoted by a type
expression.
• A type expression is either:
– a basic type or
– formed by applying an operator called type constructor to the
type expression.
• The followings are type expressions:
– A basic type is a type expression:
Example: boolean, char, integer, and real
- A special basic type, type_error, will signal an error during type
checking.
- A basic type void denoting “the absence of a value” allows
statement to be checked.
18
Type expressions…
• The following are type constructors:
• Arrays: if I in an index set and T is a type expression, then
Array (I, T) is a TE:
In java, int[] A = new int[10];
In C++ int A[10];
In pascal var A: array [1…10] of integer;
associates the type expression array(1..10, integer) with A.

• Products: if T1 and T2 are type expressions, the Cartesian


product T1 x T2 is a TE. X is left associative.
• Example: foo(int, char), int x char  (1,’a’), (2,’b’)…

19
Type expressions…

• Pointers: if T is a TE then pointer (T) is a TE.


• Denotes the type “pointer to an object of type T.”

var p: ^integer  pointer(integer)


int *a;

20
Type expressions…
• Functions: the TE of a function has the form D  R where:
– D is the type expression of the parameters and
– R is the TE of the returned value.
• For example:
– mod function has domain type int x int, a pair of
integers and range type int, thus mod has
int x int  int
• The TE corresponding to the Pascal declaration:
function f (a, b : char): ^Integer:
char x char  pointer (integer)

21
Type expressions…
• A convenient way to represent a type expression is to use a graph (tree or DAG).
• For example, the type expression corresponding to the above function declaration
can be represented with the tree shown below:
TE: char x char  pointer (integer)

 

X pointer pointer
X
char char
integer char integer
Tree DAG 22
Example: tree and DGA
int *foo(char *, char *)

DAG
TREE
foo
foo

args args pointer


pointer

pointer pointer int pointer int

char char char

23
Type Expression (summary)
• The type of a language construct is denoted by a type expression.
• A type expression can be:
– A basic type
• a primitive data type such as integer, real, char, boolean, …
• type-error to signal a type error
• void : no type

– A type name
• a name can be used to denote a type expression.

– A type constructor applies to other type expressions.


• arrays: If T is a type expression, then array(I,T) is a type expression where I denotes index range. Ex:
array(0..99,int)
• products: If T1 and T2 are type expressions, then their cartesian product T1 x T2 is a type expression.
Ex: int x int
• pointers: If T is a type expression, then pointer(T) is a type expression. Ex: pointer(int)
• functions: We may treat functions in a programming language as mapping from a domain type D to a
range type R. So, the type of a function can be denoted by the type expression D→R where D are R
type expressions. Ex: int→int represents the type of a function which takes an int value as parameter,
and its return type is also int.
24
Specification of a simple type checker
• In this section, we specify a type checker for a simple
language.
• The type of each identifier must be declared before the
identifier is used.
• The type checker is a translation scheme:
• synthesizes the type of each expression from the types of its sub
expressions.
• The type checker can handle:
– arrays,
– pointers,
– statements, and
– functions.
25
A Simple Language example
• This grammar generates programs, represented by the non-terminal
P consisting of sequence of declarations D followed by a single
expression E or statement S.
PD;E|D;S
D  D ; D | id : T
T  boolean | char | integer | array { num } of T | ^T
E  true | false | literal | num | id | E mod E | E [ E ] | E ^
|E=E|E+E
S  id := E | if E then S | while E do S | S ; S

One program generated by the grammar is:


key : integer;
key mod 100
26
Types in the language
• The language has three basic types: boolean, char and integer.
• type_error used to signal error.
• Void used to check statements.
• All arrays start at 1. For example:
array [256] of char
leads to the TE array (1…256, char)
• Consisting of the constructor array applied to the sub range 1..256 and
the type char.
• The prefix operator ^ in declarations builds a pointer type, so
^ integer
leads to the TE pointer(integer), consisting of the constructor pointer
applied to the type integer.

27
Specification of a simple type checker
Translation schemes for Declarations
P D;E|D;S
DD;D
D  id : T {addtype (id.entry, T. Type)}
T  boolean { T.type:= boolean}
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 (1…num.val, T1.type)}

28
Specification of a simple type checker…

• The purpose of the above semantic actions is:


– to synthesize the type expression corresponding to
a declaration of a type and
– add the type expression in the symbol table entry
corresponding to the variable identifier.

29
Specification of a simple type checker…
Translation scheme for type checking of Expressions:

E  true {E.type:= boolean} Fetches the type saved


E  false {E.type:= boolean} in symbol table entry
E  literal {E.type := char} pointed to by id.entry
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}
30
Translation scheme for type checking of
Expressions…
E  E1 ^ {E.type := if E1.type = pointer (t) then t
else type_error}
The objects pointed to by its operand

E → E1 + E2 { if (E1.type=int and E2.type=int) then E.type=int


else if (E1.type=int and E2.type=real) then E.type=real
else if (E1.type=real and E2.type=int) then E.type=real
else if (E1.type=real and E2.type=real) then E.type=real
else E.type=type-error }

E  E1 = E2 { E.type := if E1.type = E2.type then boolean


else type_error}
31
Specification of a simple type checker…
Translation scheme for 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}
32
Type Checking Function Calls

• To type-check function calls we need to


– Check that the arguments to a function match the
function's declaration
• The return type of a function call is specified by its
declaration

33
Specification of a simple type checker…
• Functions:
T→ TT EE(E)

Function type declaration Function call


Example:
v : integer;
odd : integer -> boolean;
if odd(3) then
v := 1;
34
Specification of a simple type checker…
• Type checking of Functions declaration:
T→ TT { T.type := function (T1.type, T2.type)}

Type constructs

35
Specification of a simple type checker…

• Type checking of Functions declaration:


E  E1 (E2) {E.type := if E2.type = s and E1.type = function
(s,t) then t
else type_error}

Ex: int f(double x, char y) { ... }


f: double x char  int

argument types return type

36
Exercises
• For the translation scheme of a simple type checker
presented above, draw the decorated parse tree for:

1- A: array [1…10] of ^array [1…5] of char

2- A: array [1…10] of ^ array [1…5] of char;


B: char

37
P
E
;
addtype(A, array(1…10, pointer(array(1…5,char)))
D

T. type= array(1…10, pointer(array(1…5,char))


id.entry=A :

T. type= pointer(array(1…5,char))
array [ 1…10 ] of
A ^ T1. type= array(1…5,char)

T.type=char
array [ 1…10 ] of
char 38
P

addtype(id.entry, T.type) ; E
D

D
T. type=array(1…10,T.type)
id.entry=A : ; id.entry=B :T.type=
T. type=pointer(T1.type) char
array [ 1…10 ] of char
A ^ T1. type=array(1…5,T.type)

T.type=char
array [ 1…10 ] of
char 39
Exercises
• For the translation scheme of a simple type checker
presented above, draw the decorated parse tree for:
a: array [1…5] of integer;
b: integer;
c: char

b=a[1];
if b = a[10] then
b = b + 1;
c = a[5];

40
Equivalence of Types
• An “equal operator” is not defined except between basic
types.
• i.e doesn’t work for type name and constructed type
expressions.
• Hence an “equivalent” operator is appropriate.
• Natural notion of equivalence is Structural equivalence.
• But there are two types of equivalence:

1 - Structural Equivalence
2 - Name Equivalence

41
Structural Equivalence
• Two type expressions are structurally equivalent iff:
– They have the same basic types
– They can be formed by applying same constructor to
structural equivalence type
Example: char is equivalent only to char.
pointer(char) to pointer(char)

Note:
Two type expressions are name equivalent iff they are
identical.

42
Structural Equivalence of Type
Expressions
• How do we know that two type expressions are equal?
• As long as type expressions are built from basic types (no type names),
we may use structural equivalence between two type expressions

Structural Equivalence Algorithm (sequiv):


if (s and t are same basic types) then return true
else if (s=array(s1,s2) and t=array(t1,t2)) then return (sequiv(s1,t1) and sequiv(s2,t2))
else if (s = s1 x s2 and t = t1 x 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

43
Names for Type Expressions
• In some programming languages, we give a name to a type expression,
and we use that name as a type expression afterwards.

type link =  cell; next,last,r,s have same types?


var next,last : link;
var r,s :  cell
variable type expression
next link
last link
r pointer(cell)
s pointer(cell)

44
Name Equivalence example
Example:
type ptr = ^ integer
var A , B : ptr
C : ^ integer
D, E : ^ integer

• here ptr is type name - name for p(int)

A and C  structural equivalent but not name equivalent.


A and B  name equivalent
C and E  name equivalent

45
Type Conversions
x+y what is the type of this expression (int or real)?

• What kind of codes we have to produce, if the type of x is real


and the type of y is int?
• Of course, the machine cannot execute this operation as it
involves different types of values.

• However, most languages accept such expressions to be used;

• The compiler will be in charge of converting one of the operand


into the type of the other.

46
Type Conversions…
• The type checker can be used to insert these conversion
operations into the intermediate representation of the source
program.
• For example, an operator inttoreal may be inserted whenever
an operand need to implicitly converted.

inttoreal y,t1
real+ t1,x,t2

• inttoreal operator converts y from integer to real and then


perform a real addition on its operands.

47
Type conversion
• Conversion from one type to another is said to be implicit it if is
done automatically by the compiler, and is also called
coercion.

• Conversion is said to be explicit if the programmer must write


the conversion
– Called casts in C and Java languages

48

You might also like