160 likes | 319 Views
Type Checking. Legality checks Operator determination Overload resolution. Type as a synthesized attribute. Expr ::= Expr + Term if Etype (Expr 2 ) = Etype (Term) then Etype (Expr 1 ) := Etype (Expr 2 ); else Etype (Expr1) = Any_Type;
E N D
Type Checking Legality checks Operator determination Overload resolution
Type as a synthesized attribute • Expr ::= Expr + Term if Etype (Expr2) = Etype (Term) then Etype (Expr1) := Etype (Expr2); else Etype (Expr1) = Any_Type; Error (“incompatible types for +”, Expr1); end if; • Sufficient for Pascal and C
Rules for operations on composite types • Indexed_Component ::= Prefix ( Expr ) A_Typ := Etype (Prefix); Etype (Indexed_Component) := Any_Type; - - by default if not Is_Array_Type (A_Typ) then Error (“expect array type in indexed component”, Prefix); elsif Etype (Expr) /= Index_Type (A_Typ) then Error (“wrong index type”, Expr); else Etype (Indexed_Component) := Component_Type (A_Typ); end if;
Type expressions • Built from: • Primitive types: boolean, float, integer • Type constructors: array, record, access, function • Type_Expr ::= Prim_Type • Type_Expr ::= Type_Name • Type_Expr ::= array (Type_Expr) Type_Expr • Pascal, Ada : index type is part of type expression • Early Pascal : index bounds are part of type expression (bad idea) • Type_Expr ::= record (…) • Type_Expr ::= access Type_Expr • Type checking is a set of rules to compute the type expressions of all expressions in the program
Product types and functions • Type_Expr := Type_Expr * Type_Expr • Intuitively, a pair of types • Type_Expr := Type_Expr1 -> Type_Expr2 • A function that takes an argument of type Type_Expr1 and returns a value of type Type_Expr2 • function Distance (C1, C2 : Character) return Integer; • Distance: character * character -> integer • real merge (int[] a, int[]b); • merge: array ( int ) * array ( int ) -> real
Type checking and type equivalence • Type correctness can be stated in terms of equivalence of type expressions: int [ ] arr1; // arr2 : array ( int ) int [ ] arr2; // arr2 : array ( int ) … Arr1 = arr2; // ok in Java: type expressions are equivalent Usually the rule in languages where a declaration can contain an arbitrary type expression
Type checking and name equivalence Arr1 : array (1..10) of integer; Arr2 : array (1..10) of integer; … Arr1 := Arr2; // illegal in Ada: different anonymous types. -- same as: type anon1 is array (1..10) of integer; Arr1 : anon1; -- Arr1 : anon1 type anon2 is array (1..10) of integer; Arr2 : anon2: -- Arr2 : anon2 -- Arr1 and Arr2 are not equivalent. Language does not allow arbitrary (anonymous) type expressions in a declaration
Type expressions with cycles type t1 = record c: integer; p: pointer (t1); end record; type t2 = record c: integer; p: pointer (t2); end record; • t1 and t2 are equivalent in Algol68 • C approach: name of type is part of type expression, types are not equivalent: struct cell { int c; struct cell *next; /* struct cell is in type expression */ };
Type checking and coercions • If language allows coercions, type depends on context, cannot be purely synthesized. • For C++ boolean operators (Stroustrup C.6.3) • If either operand is of type long double, the other is converted to long double • Otherwise, if either operand is double, the other is converted to double • Otherwise, if either is float, the other is converted to float • Otherwise, integral promotions are performed on both: • char, (un) signed_char, (unsigned) short int -> int • bit-field -> int • bool -> int • Further complication: user-defined conversions.
Overload resolution • If expression is overloaded, collect set of possible meanings sm. • If context is overloaded, collect set of possible context types sc. • Expression is legal in context if intersection of sc and sm is a singleton: | sc ∩ sm | = 1 • C++: if multiple interpretations, select the one with smallest number of coercions • Ada: if multiple interpretations, select predefined numeric operator over user-defined one
Overloaded context: procedure call procedure P (x : integer; y : float); procedure P (x : float; y : float); procedure P (x : boolean; z : integer); function F (x : integer) return integer; function F (x : float) return integer; function F (x : integer) return float; function F (x : float) return boolean; … P (f (3.14), f (1)); -- P1 (f2 (3.14), f3 (1));
Two-pass type resolution • Bottom-up (analyze) : synthesize candidate types • Top-down (resolve): propagate unique context type procedure analyze_indexed_component (N : Node_Id) is begin analyze prefix, collect set of interpretations Pre analyze index, collect set of interpretations Ind forall t in Pre loop if there is a compatible ix in Ind then add component_Type (t) to interpretations of N else remove t from Pre; end if; end loop; end;
Two-pass type resolution (2) procedure resolve_indexed_component (N : Node_Id; Typ : Entity_Id) is begin find unique interpretation of prefix whose component type is Typ resolve index using this interpretation end; General scheme: bottom-up: analyze descendants, synthesize local attribute top-down : disambiguate construct, propagate to descendants
Type variables and polymorphism • In a language with list primitives (LISP, ML) what is the type expression that describes CAR or hd? • Informally, given a list of components of any kind, CAR yields a value of that kind: • Car : list_of_whatever -> whatever • A type variable is universally quantified: the expression is valid for any instantiation of the variable. In ML notation: • Head (hd) α list -> α • Tail (tl) α list -> α list • Constructor (::) : α * α list -> α list
Polymorphic functions • fun len lis = if lis = nullthen 0 else 1 + len (tl lis) • Len : α list -> int • fun map (f lis) = if L = null then nullelse f (hd lis) :: map (f (tl lis)) Map: (α ->β) * α list -> β list Map applies a function to each element of a list. The result type of the function is not necessarily equal to the element type of the list.
The limits of type inference • Overloading must be handled specially • Self-application is not typable: • 1 + g (g) • g is a function: • g: α -> β • g applied to itself yields an integer: • β = int • α = α -> β : circular definition (not unifiable)