Semantic Analysis Type Checking Maryam Siahbani CMPT 379 * Slides are modified version of Schwarz s compiler course at Stanford 4/8/2016 1
Type Checking Type errors arise when operations are performed on values that do not support that operation Type checking is the process of verifying that each operation executed in a program respects the type system of the language Most common semantic conditions involve type checking 2
Types of Type-Checking Static type checking Analyze the program during compilation-time to prove the absence of type errors Never let bad things happen at run-time Dynamic type checking Check operations at runtime before performing them More precise than static type checking, but usually less efficient 3
Type Systems A collection of rules governing permissible operations on types form a type system Strong type system: does not allow any type error Java, Python, LISP, Weak type system: may allow type errors at runtime C, C++ 4
Type Debates Endless debate about what the right system is! Dynamic type systems make it easier to prototype, static type systems are more efficient Strong type systems are more robust, weak type systems are often faster 5
Static Type Checking Two processes for static type checking: Inferring the type of each expression from the types of its components Confirming that the types of expressions in certain contexts matches what is expected Can be done in one step 6
Example while (numbitsset(x + 5) <= 10 ) { if (1.0 + 4.0) { /* */ } while (5 == null) { /* */ } } 7
Example while (numbitsset(x + 5) <= 10 ) { if (1.0 + 4.0) { /* */ } } while (5 == null) { /* */ } Well-typed expression, wrong type for this context 8
Example while (numbitsset(x + 5) <= 10 ) { if (1.0 + 4.0) { /* */ } } while (5 == null) { /* */ } Expression with type error 9
Example while (numbitsset(x + 5) <= 10 ) { if (1.0 + 4.0) { /* */ } while (5 == null) { /* */ } } 10
Inferring Expression Types How do we determine the type of an expression? Think of process as logic inference int + int int constant int int constant 137 42 11
Inferring Expression Types How do we determine the type of an expression? Think of process as logic inference == bool bool identifier == bool X bool identifier bool bool constant Y true 12
Type Checking as Proofs We can think of type checking as proving claims about the types of expressions We begin with a set of axioms, then apply our inference rules to determine the types of expressions Many type systems can be thought of as proof systems 13
Samples of Inference Rules If X is an identifier that refers to an object of type T, the expression X has type T If E is an integer constant, E has type int If the operands E1 and E2 of E1 + E2 are known to have types int and int, then E1 + E2 has type int 14
Notation Axioms and inference rules are encode using this format: preconditions postconditions if preconditions are true, we can infer postconditions e T we can infer e has type T 15
Axioms true: bool false: bool 16
Simple Inference Rules i is an integer constant i: int s is a string constant s: string d is a double constant d: double 17
More Complex Rules e1: int e2: int e1 + e2: int e1: double e2: double e1 + e2: double If we can show that e1 and e2 have type int then we can show that e1+e2 has type int as well 18
More Complex Rules e1: T e2: T T is primitive type e1 == e2: bool e1: T e2: T T is primitive type e1! = e2: bool 19
Problem x is an identifier x?? We do not know anything about x, until we find out what x refers to 20
Incorrect Solution x is an identifier x is in scope with type T x T int func(string x) { { double x; } if (x == 1.5) { /* */ } Facts } 21
Incorrect Solution x is an identifier x is in scope with type T x T int func(string x) { { double x; } if (x == 1.5) { /* */ } Facts x: double } 22
Incorrect Solution x is an identifier x is in scope with type T x T int func(string x) { { double x; } if (x == 1.5) { /* */ } Facts x: double x: string } 23
Incorrect Solution x is an identifier x is in scope with type T x T d is a double constant d: double int func(string x) { { double x; } if (x == 1.5) { /* */ } Facts x: double x: string 1.5: double } 24
Incorrect Solution x is an identifier x is in scope with type T x T int func(string x) { { } double x; if (x == 1.5) { } /* */ e1: T e2: T T is primitive type e1 == e2: bool Facts x: double x: string 1.5: double x==1.5: bool } 25
Adding Scope We need to strength our inference rules to remember under what circumstances the results are valid We write: S e T If in scope S, expression e has type T Types are now proven relative to the scope they are in. 26
Revisited Rules S true: bool i is an integer constant S i: int S false: bool s is a string constant S s: string d is a double constant S d: double S e1: T S e2: T T is primitive type S e1 == e2: bool S e1: T S e2: T T is primitive type S e1! = e2: bool 27
Correct Rule for Identifiers x is an identifier x is variable in scope S with type T S x T 28
Rule for Function Calls f is an identifier f is a nonmember function in scope S f has type (T1,, Tn) U S ei Ti for 1 i n S f(e1,, en):?? U 29
Rule for Arrays S e1 T[] S e2 int S e1[e2]: T 30
Rule for Assignment S e1 T S e2 T S e1 = e2 T 5 = X; Why is not this rule a problem for this statement? 31
Rule for Assignment S e1 T S e2 T S e1 = e2 T If Derived extends Base, will this rule work for this code? Base mybase; Derived myderived; mybase = myderived; 32
Typing for Classes How do we factor inheritance into inference rules? We need to consider the shape of class hierarchies. Base Derived1 Derived2 Derived11 Derived21 33
Properties of Inheritance Structures 1. Any type is convertible to itself (reflexivity) 2. If A is convertible to B and B is convertible to C, then A is convertible to C (transitivity) 3. If A is convertible to B, and B is convertible to A, then A and B are the same type (antisymmetry) This defines a partial order over types 34
Types and Partial Orders Notation: A B: A is convertible to B 1. A A 2. A B and B C implies A C 3. A B and B A implies A = B 35
Updated Rule for Assignment S e1 T1 S e2 T2 T2 T1 S e1 = e2: T1 36
Updated Rule for Comparisons S e1: T S e2: T T is primitive type S e1 == e2: bool S e1: T1 S e2: T2 T1 and T2 are of class type T1 T2 or T2 T1 S e1 == e2: bool Try to unify the rules 37
Extend Convertibility If A is a primitive or array type, A is only convertible to itself More formally, if A and B are types and A is a primitive or array type: A B implies A = B B A implies A = B 38
Updated Rule for Comparisons S e1: T1 S e2: T2 T1 T2 or T2 T1 S e1 == e2: bool 39
Updated Rule for Function Calls f is an identifier f is a nonmember function in scope S f has type (T1,., Tn) U S ei Ri for 1 i n Ri Ti for 1 i n S f(e1,, en): U 40
Null! S null:?? 41
Null Define a new type corresponding to the type of the literal null; call it null type Define null type A for any class type A Null type is not convertible to primitive types or array type The null type is typically used internally Many programming languages have types like this 42
Object Oriented Considerations S is in scope of class T S this: T T is a class type S new T: T S e: int d is an identifier S T d e : T[] 43
Using our Type Proofs We can now prove the types of various expressions How do we check if statements have well-formed conditional expressions? return statements actually return the right type of value? Need another proof system! 44
Proof of Structural Soundness Idea: extend our proof system to statements to confirm that they are well-formed We say S WF(stmt) the statement stmt is well-formed in scope S. The type system is satisfied if for every function f with body B in scope S, we can show S WF(B) 45
A Simple Well-formedness Rule S expr T S WF(expr) If we can assign a valid type to an expression in scope S then it is a valid statement in scope S 46
Sequence of Statements S WF(stmt1) S WF(stmt2) S WF(stmt1 stmt2) 47
break Statement S is in a for or while loop S WF(break;) 48
Rule for Loops S expr bool S is the scope inside the loop S WF(stmt) S WF(while expr stmt) 49
Rule for Block Statements S is the scope formed by adding decls to S S WF(stmt) S WF({ decls stmt}) 50
Rules for return Statement S is in a function retruning T S expr T T T S WF(return expr; ) S is in a function returning void S WF(return; ) 51
Checking Well-Formedness Recursively walk the AST For each statement: Type check any sub expressions it contains Report errors if no type can be assigned Report errors if the wrong type is assigned Type check child statements Check the overall correctness 52