Introduction: Consider expressions like x i, where x is of type float and i is of type integer. Since the representation of integers and floating-point numbers is different within a computer and different machine instructions are used for operations on integers and floats, the compiler may need to convert one of the operands of to ensure that both operands are of the same type when the addition occurs.
Suppose that integers are converted to floats when necessary, using a unary operator (float). For example, the integer 2 is converted to a float in the code for the expression 2*3.14:
ti = (float) 2
t2 = ti * 3.14
We can extend such examples to consider integer and float versions of the operators; for example, int * for integer operands and float * for floats.
Type synthesis will be illustrated by extending the scheme in Section 6.4.2 for translating expressions. We introduce another attribute E.type, whose value is either integer or float. The rule associated with E E\ E2 builds on the pseudocode
if ( Ei.type = integer and E2.type = integer ) E.type — integer,
else if ( Ei.type = float and E2.type — integer ) . . .
As the number of types subject to conversion increases, the number of cases increases rapidly. Therefore with large numbers of types, careful organization of the semantic actions becomes important.
Type conversion rules vary from language to language. The rules for Java in Fig. 6.25 distinguish between widening conversions, which are intended to preserve information, and narrowing conversions, which can lose information. The widening rules are given by the hierarchy in Fig. 6.25(a): any type lower in the hierarchy can be widened to a higher type. Thus, a char can be widened to an int or to a float, but a char cannot be widened to a short. The narrowing rules are illustrated by the graph in Fig. 6.25(b): a type s can be narrowed to a type t if there is a path from s to t. Note that char, short, and byte are pair wise convertible to each other.
Conversion from one type to another is said to be implicit if it is done automatically by the compiler. Implicit type conversions, also called coercions, are limited in many languages to widening conversions. Conversion is said to be explicit if the programmer must write something to cause the conversion. Explicit conversions are also called casts.
The semantic action for checking E -» E\ E2 uses two functions:
1. max(t\,t2) takes two types ti and t2 and returns the maximum (or least upper bound) of the two types in the widening hierarchy. It declares an error if either t\ or t2 is not in the hierarchy; e.g., if either type is an array or a pointer type.
2. widen(a, t, w) generates type conversions if needed to widen an address a of type t into a value of type w. It returns a itself if t and w are the same type. Otherwise, it generates an instruction to do the conversion and place the result in a temporary t, which is returned as the result.
The semantic action for E -» E\ E2in Fig. 6.27 illustrates how type conversions can be added to the scheme in Fig. 6.20 for translating expressions. In the semantic action, temporary variable a\ is either Ei.addr, if the type of Ei does not need to be converted to the type of E, or a new temporary variable returned by widen if this conversion is necessary. Similarly, a2 is either E2.addr or a new temporary holding the type-converted value of E2. Neither conversion is needed if both types are integer or both are float. In general, however, we could find that the only way to add values of two different types is to convert them both to a third type.