0% found this document useful (0 votes)
0 views

lecture10

Uploaded by

menber988
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
0 views

lecture10

Uploaded by

menber988
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 38

Type Checking II

CS143
Lecture 10

Instructor: Fredrik Kjolstad


Slide design by Prof. Alex Aiken, with modifications
1
Lecture Outline

• Type systems and their expressiveness

• Type checking with SELF_TYPE in COOL

• Error recovery in semantic analysis

2
Expressiveness of Static Type Systems

• Static type systems detect common errors

• But some correct programs are disallowed


– Some argue for dynamic type checking instead
– Others argue for more expressive static type checking

• But more expressive type systems are more


complex

3
Dynamic and Static Types

• The dynamic type of an object is the class C that


is used in the “new C” expression that created it
– A run-time notion
– Even languages that are not statically typed have the
notion of dynamic type

• The static type of an expression captures all


dynamic types the expression could have
– A compile-time notion

4
Dynamic and Static Types. (Cont.)

• In early type systems the set of static types


correspond directly with the dynamic types

• Soundness theorem: for all expressions E


dynamic_type(E) = static_type(E)
(in all executions, E evaluates to values of the type
inferred by the compiler)

• This gets more complicated in advanced type


systems

5
Dynamic and Static Types in COOL

class A { … }
class B inherits A {…}
class Main { Here, x’s value has
x has static x:A ← new A; dynamic type A
type A … Here, x’s value has
x ← new B; dynamic type B

}

• A variable of static type A can hold values of static


type B, if B ≤ A
6
Dynamic and Static Types

Soundness theorem for the Cool type system:


∀ E. dynamic_type(E) ≤ static_type(E)

Why is this Ok?


– All operations that can be used on an object of type A
can also be used on an object of type B ≤ A
• Such as fetching the value of an attribute
• Or invoking a method on the object
– Subclasses only add attributes or methods
– Methods can be redefined but with same type!

7
An Example

class Count { • Class Count incorporates


i : int ← 0; a counter
inc () : Count {
{ • The inc method works for
i ← i + 1; any subclass
self;
}
}; • But there is problem
}; lurking in the type system

8
An Example (Cont.)

• Consider a subclass Stock of Count

class Stock inherits Count {


name : String; -- name of item
};
• And the following use of Stock:

class Main {
Stock a ← (new Stock).inc (); Type checking error !
… a.name …
};
9
What Went Wrong?

• (new Stock).inc() has dynamic type Stock

• So it is legitimate to write
Stock a ← (new Stock).inc ()

• But this is not well-typed


– (new Stock).inc() has static type Count
– inc () : Count {…}

• The type checker loses type information


– This makes inheriting inc useless
– So, we must redefine inc for each of the subclasses, with a
specialized return type

10
SELF_TYPE to the Rescue

• We will extend the type system

• Insight:
– inc returns “self”
– Therefore the return value has same type as “self”
– Which could be Count or any subtype of Count!

• Introduce the keyword SELF_TYPE to use for the return


value of such functions
– We will also need to modify the typing rules to handle
SELF_TYPE

11
SELF_TYPE to the Rescue (Cont.)

• SELF_TYPE allows the return type of inc to change when


inc is inherited

• Modify the declaration of inc to read


inc() : SELF_TYPE { … }

• The type checker can now prove:


C,M ⊢ (new Count).inc() : Count
C,M ⊢ (new Stock).inc() : Stock

• The program from before is now well typed

12
Notes About SELF_TYPE

• SELF_TYPE is not a dynamic type


– It is a static type
– It helps the type checker to keep better track of types
– It enables the type checker to accept more correct
programs

• In short, having SELF_TYPE increases the


expressive power of the type system

13
SELF_TYPE and Dynamic Types (Example)

• What can be the dynamic type of the object


returned by inc?
– Answer: whatever could be the type of “self”

class A inherits Count { } ;


class B inherits Count { } ;
class C inherits Count { } ;
(inc could be invoked through any of these classes)

– Answer: Count or any subtype of Count

14
SELF_TYPE and Dynamic Types (Example)

• In general, if SELF_TYPE appears textually in the class C


as the declared type of E then
dynamic_type(E) ≤ C

• Note: The meaning of SELF_TYPE depends on where it


appears
– We write SELF_TYPEC to refer to an occurrence of SELF_TYPE
in the body of C

• This suggests a typing rule:


SELF_TYPEC ≤ C (*)

15
Type Checking

• Rule (*) has an important consequence:


– In type checking it is always safe to replace
SELF_TYPEC by C

• This suggests one way to handle SELF_TYPE :


– Replace all occurrences of SELF_TYPEC by C

• This would be correct but it is like not having


SELF_TYPE at all

16
Operations on SELF_TYPE

• Recall the operations on types


– T1 ≤ T2 T1 is a subtype of T2
– lub(T1,T2) the least-upper bound of T1 and T2

• We must extend these operations to handle


SELF_TYPE

17
Extending ≤

Let T1 and T2 be any types but SELF_TYPE


There are four cases in the definition of ≤

1. SELF_TYPEC ≤ SELF_TYPEC
• In Cool we never need to compare SELF_TYPEs coming from
different classes

2. SELF_TYPEC ≤ T1 if C ≤ T1
• SELF_TYPEC can be any subtype of C
• This includes C itself
• Thus this is the most flexible rule we can allow

18
Extending ≤ (Cont.)

3. T1 ≤ SELF_TYPEC always false


Note: SELF_TYPEC can denote any subtype of C.

4. T1 ≤ T2 (according to the rules from before)

Based on these rules we can extend lub …

19
Extending lub(T,T’)

Let T1 and T2 be any types but SELF_TYPE


Again there are four cases:
1. lub(SELF_TYPEC, SELF_TYPEC) = SELF_TYPEC

2. lub(SELF_TYPEC, T1) = lub(C, T1)


This is the best we can do because SELF_TYPEC ≤ C

3. lub(T1, SELF_TYPEC) = lub(C, T1)

4. lub(T1, T2) defined as before

20
Where Can SELF_TYPE Appear in COOL?

• The parser checks that SELF_TYPE appears only


where a type is expected

• But SELF_TYPE is not allowed everywhere a type can


appear:

1. class T inherits T’ {…}


• T, T’ cannot be SELF_TYPE
2. x : T
• T can be SELF_TYPE
• An attribute whose type is ≤ SELF_TYPEC

21
Where Can SELF_TYPE Appear in COOL?

3. let x : T in E
• T can be SELF_TYPE
• x has a type ≤ SELF_TYPEC

4. new T
• T can be SELF_TYPE
• Creates an object of the same type as self

5. m@T(E1,…,En)
• T cannot be SELF_TYPE

22
Where Can SELF_TYPE Not Appear in COOL?

6. m(x : T) : T’ { … }
• Only T’ can be SELF_TYPE !

What could go wrong if T were SELF_TYPE?


class A { foo(x : SELF_TYPE) : Bool {…}; };
class B inherits A {
b : int;
foo(x : SELF_TYPE) : Bool { … x.b …}; };

let x : A ← new B in … x.foo(new A); …
… 23
Typing Rules for SELF_TYPE

• Since occurrences of SELF_TYPE depend on the


enclosing class we need to include that context
during type checking

• Recall the form of a typing judgment:


O,M,C ⊢ e : T
(An expression e occurring in the body of C has
static type T given a variable type environment O
and method signatures M)

24
Type Checking Rules

• The next step is to design type rules using SELF_TYPE for each
language construct

• Most of the rules remain the same except that ≤ and lub are the new
ones

• Example:

O(Id) = T0
O,M,C ⊢ e1 : T0
T1 ≤ T0
O,M,C ⊢ Id ← e1 : T1
25
What is Different?

• Recall the old rule for dispatch

O,M,C ⊢ e0 : T0

O,M,C ⊢ en : Tn
M(T0, f) = (T'1,…,T'n,T'n+1)
T'n+1 ≠ SELF_TYPE
Ti ≤ T'i 1≤i≤n
O,M,C ⊢ e0.f(e1,…,en) : T'n+1

26
What is Different?

• If the return type of the method is SELF_TYPE


then the type of the dispatch is the type of the
dispatch expression:

O,M,C ⊢ e0 : T0

O,M,C ⊢ en : Tn
M(T0, f) = (T'1,…,T'n, SELF_TYPE)
Ti ≤ T'i 1≤i≤n
O,M,C ⊢ e0.f(e1,…,en) : T0
27
What is Different?

• Note this rule handles the Stock example

• Formal parameters cannot be SELF_TYPE

• Actual arguments can be SELF_TYPE


– The extended ≤ relation handles this case

• The type T0 of the dispatch expression could be


SELF_TYPE
– Which class is used to find the declaration of f?
– Answer: it is safe to use the class where the dispatch appears

28
Static Dispatch

• Recall the original rule for static dispatch

O,M,C ⊢ e0 : T0

O,M,C ⊢ en : Tn
T0 ≤ T
M(T, f) = (T1’,…,Tn’,Tn+1’)
Tn+1’ ≠ SELF_TYPE
Ti ≤ Ti ’ 1≤i≤n
O,M,C ⊢ [email protected](e1,…,en) : Tn+1’
29
Static Dispatch

• If the return type of the method is SELF_TYPE


we have:

O,M,C ⊢ e0 : T0

O,M,C ⊢ en : Tn
T0 ≤ T
M(T, f) = (T1’,…,Tn’,SELF_TYPE)
Ti ≤ Ti ’ 1 ≤ i ≤ n
O,M,C ⊢ [email protected](e1,…,en) : T0
30
Static Dispatch

• Why is this rule correct?

• If we dispatch a method returning SELF_TYPE in


class T, don’t we get back a T?

• No. SELF_TYPE is the type of the self parameter,


which may be a subtype of the class in which the
method appears

31
New Rules

• There are two new rules using SELF_TYPE

O,M,C ⊢ self : SELF_TYPEC

O,M,C ⊢ new SELF_TYPE : SELF_TYPEC


• There are a number of other places where
SELF_TYPE is used

32
Summary of SELF_TYPE

• The extended ≤ and lub operations can do a lot of the


work.

• SELF_TYPE can be used only in a few places. Be sure it


isn’t used anywhere else.

• A use of SELF_TYPE always refers to any subtype of the


current class
– The exception is the type checking of dispatch. The method return
type of SELF_TYPE might have nothing to do with the current
class

33
Why Cover SELF_TYPE ?

• SELF_TYPE is a research idea


– It adds more expressiveness to the type system

• SELF_TYPE is itself not so important


– except for the project

• Rather, SELF_TYPE is meant to illustrate that type


checking can be quite subtle

• In practice, there should be a balance between the


complexity of the type system and its expressiveness

34
Error Recovery

• As with parsing, it is important to recover from type errors

• Detecting where errors occur is easier than in parsing


– There is no reason to skip over portions of code

• The Problem:
– What type is assigned to an expression with no legitimate type?
– This type will influence the typing of the enclosing expression

35
Error Recovery Attempt

• Assign type Object to ill-typed expressions

let y : Int ← x + 2 in y + 3
• Assume x is undeclared, then its type is Object
• But now we have Object + Int
• This will generate another typing error
• We then say that that Object + Int = Object
• Then the initializer’s type will not be Int
⇒ a workable solution but with cascading errors
36
Better Error Recovery

• We can introduce a new type called No_type for use with ill-typed
expressions

• Define No_type ≤ C for all types C

• Every operation is defined for No_type


– With a No_type result

• Only one typing error for:

let y : Int ← x + 2 in y + 3

37
Notes

• A “real” compiler would use something like


No_type

• However, there are some implementation issues


– The class hierarchy is not a tree anymore

• The Object solution is fine in the class project

38

You might also like