Yasir Explains/Compiler Design/Syntax-Directed Translation/Type Conversions
Syntax-Directed Translation

Type Conversions

On this page

Why Conversions Are NeededCoercion vs. CastWidening vs. NarrowingAn SDD That Inserts CoercionsWorked Example: f = i + f * 2PitfallsKey Points
Syntax-Directed Translation

Type Conversions

Why compilers convert between types, the difference between implicit coercion and explicit casting, the widening hierarchy, and how an SDD inserts coercions automatically during type checking.

Why Conversions Are Needed

Definition. A type conversion changes a value from one type to another so that an operator can work on it.

The reason: an operator expects all its operands to be the same type. The hardware has separate instructions for each type.

  • IADD adds two integers.
  • FADD adds two IEEE-754 floats.

There is no instruction that adds an int to a float. So in 2 + 3.14, the compiler must convert 2 to a float first, then use FADD.

Example
2 + 3.14
→ convert 2 (int) to 2.0 (float)
→ FADD(2.0, 3.14) = 5.14

Deciding which operand to convert, and recording it, is the job of the type checker during semantic analysis.

Coercion vs. Cast

There are two ways a conversion appears.

Coercion (implicit) — a conversion inserted automatically by the compiler. The programmer writes nothing.

Example
int i = 5;
float f = i + 2.5; // compiler converts i to 5.0 by itself

Cast (explicit) — a conversion written by the programmer using (type) expr.

Example
float f = 3.99;
int i = (int) f; // programmer wrote the cast; i = 3
CoercionCast
Written byCompilerProgrammer
SyntaxNone (invisible)(type) expr
Used forSafe conversionsRisky conversions

Rule: the compiler coerces only when it is safe. Anything risky must be an explicit cast.

Widening vs. Narrowing

Widening = converting to a type that can hold more values (a "wider" type). It is safe — no value is lost — so the compiler does it implicitly.

Narrowing = converting to a "smaller" type that may not fit the value. It can lose data, so it needs an explicit cast.

The Widening Hierarchy

Example
char → short → int → long → float → double
(narrow) (wide)

Move right = widening (implicit, safe). Move left = narrowing (explicit cast, may lose data).

Examples

Example
short s = 300;
int i = s; // widening: 300 fits → safe
Example
double d = 3.99;
int i = (int) d; // narrowing: i = 3 (decimal truncated, NOT rounded)
Example
int big = 300;
char c = (char) big; // narrowing: 300 doesn't fit in a byte → wraps/garbage

One caveat: int → float is widening but very large ints (> 2^24) can lose their last digits, since a float has only ~7 significant digits.

An SDD That Inserts Coercions

The type checker uses an SDD to compute each expression's type and insert coercions. Two helpers do the work.

  • max(t1, t2) — returns the wider of two types.
  • widen(a, t, w) — emits code to convert address a from type t to wider type w. If t == w it does nothing and returns a.

Attributes

Each E carries:

  • E.type — the expression's type.
  • E.addr — the temporary holding its value.

Rule for E → E1 + E2

Example
E.type = max(E1.type, E2.type)
a1 = widen(E1.addr, E1.type, E.type) // coerce left if needed
a2 = widen(E2.addr, E2.type, E.type) // coerce right if needed
E.addr = newtemp()
emit( E.addr '=' a1 '+' a2 )

The same pattern applies to -, \*, and /.

widen(a, t, w)

Example
widen(a, t, w):
if t == w: return a // same type — no code
temp = newtemp()
if w == float and t == int: emit( temp '=' 'inttofloat' a )
elif w == double and t == int: emit( temp '=' 'inttodouble' a )
elif w == double and t == float:emit( temp '=' 'floattodouble' a )
else: error("illegal widen from " + t + " to " + w)
return temp

Real compilers use a bigger table, but the idea is the same: look up (t, w) and emit the matching coercion instruction.

Worked Example: f = i + f * 2

Trace the conversion-insertion on this statement, given int i; and float f;.

The right side parses as i + (f \* 2) (\* binds tighter):

+
i · int
*
f · float
2 · int

Step 1 — f \* 2

Example
max(float, int) = float
widen(f, float, float) → f // no-op
widen(2, int, float) → t1: t1 = inttofloat 2
emit: t2 = f \* t1 // result type float

Step 2 — i + (f \* 2)

Example
max(int, float) = float
widen(i, int, float) → t3: t3 = inttofloat i
widen(t2, float, float) → t2 // no-op
emit: t4 = t3 + t2 // result type float

Step 3 — assignment f = ...

Example
both sides are float → no coercion
emit: f = t4

Final Three-Address Code

Example
t1 = inttofloat 2 // coerce literal 2
t2 = f \* t1
t3 = inttofloat i // coerce variable i
t4 = t3 + t2
f = t4

Both int operands (i and 2) are widened to float because float is the widest type in the expression. No narrowing needed.

Pitfalls

Narrowing truncates, it does not round.

Example
double d = 3.9999;
int i = (int) d; // i = 3, not 4

Widening to float can still lose precision for very large integers (past 2^24), because a float keeps only ~7 significant digits.

Signed/unsigned mixing surprises. In C, mixing signed and unsigned promotes to unsigned:

Example
int a = -1; unsigned b = 1;
if (a < b) ... // a becomes 4294967295 → condition is FALSE (a bug)

Implicit narrowing is dangerous. Java, C#, and Rust forbid it. C/C++ allow it with a warning — compile with -Wconversion.

Core rule: only widen implicitly; never narrow implicitly. Insert a coercion only in the widening direction; any narrowing must be an explicit cast.

Key Points

  • Why: hardware has separate instructions per type, so operands of a mixed expression must be converted to one type.
  • Coercion = implicit, compiler-inserted. Cast = explicit, programmer-written.
  • Widening (char → short → int → long → float → double) is safe and implicit. Narrowing may lose data and needs a cast.
  • SDD: max(t1, t2) gives the result type; widen(a, t, w) emits a coercion (like inttofloat) only when t != w.
  • Coercions show up as ordinary three-address instructions in the IR.

Quick Reference — E → E1 op E2

Example
E.type = max(E1.type, E2.type)
a1 = widen(E1.addr, E1.type, E.type)
a2 = widen(E2.addr, E2.type, E.type)
E.addr = newtemp()
emit( E.addr '=' a1 op a2 )