Conversions and Promotions
Conversion Rules For C3
C3 differs in some crucial respects when it comes to number conversions and promotions. These are the rules for C3:
float
toint
conversions require a cast.int
tofloat
conversions do not require a cast.bool
tofloat
converts to0.0
or1.0
- Widening
float
conversions are only conditionally allowed*. - Narrowing conversions require a cast*.
- Widening
int
conversions are only conditionally allowed*. - Signed <-> unsigned conversions of the same type do not require a cast.
- In conditionals
float
tobool
do not require a cast, any non zerofloat
value considered true. - Implicit conversion to
bool
only occurs in conditionals or when the value is enclosed in()
e.g.bool x = (1.0)
orif (1.0) { ... }
C3 uses two’s complement arithmetic for all integer math.
Target type
The left hand side of an assignment, or the parameter type in a call is known as the target type the target type is used for implicit widening and inferring struct initialization.
Common arithmetic promotion
Like C, C3 uses implicit arithmetic promotion of integer and floating point variables before arithmetic operations:
- For any floating point type with a bitwidth smaller than 32 bits, widen to
float
. E.g.f16 -> float
- For an integer type smaller than the minimum arithmetic width promote the value to a same signed integer of the minimum arithmetic width (this usually corresponds to a c int/uint). E.g.
ushort -> uint
Implicit narrowing
An expression with an integer type, may implicitly narrow to smaller integer type, and similarly a float type may implicitly narrow to less wide floating point type is determined from the following algorithm.
- Shifts and assign look at the lhs expression.
++
,--
,~
,-
,!!
,!
- check the inner type.+
,-
,*
,/
,%
,^
,|
,&
,??
,?:
- check both lhs and rhs.- Narrowing
int
/float
cast, assume the type is the narrowed type. - Widening
int
/float
cast, look at the inner expression, ignoring the cast. - In the case of any other cast, assume it is opaque and the type is that of the cast.
- In the case of an integer literal, instead of looking at the type, check that the integer would fit the type to narrow to.
- For
.len
access, allow narrowing to C int width. - For all other expressions, check against the size of the type.
As rough guide: if all the sub expressions originally are small enough it’s ok to implicitly convert the result.
Examples:
Implicit widening
Unlike C, implicit widening will only happen on “simple expressions”: if the expression is a primary expression, or a unary operation on a primary expression.
For assignment, special rules hold. For an assignment to a binary expression, if its two subexpressions are “simple expressions” and the binary expression is +
, -
, /
, *
, allow an implicit promotion of the two sub expressions.
As a rule of thumb, if there are more than one possible conversion an explicit cast is needed.
Example:
Maximum type
The maximum type is a concept used when unifying two or more types. The algorithm follows:
- First perform implicit promotion.
- If both types are the same, the maximum type is this type.
- If one type is a floating point type, and the other is an integer type,
the maximum type is the floating point type. E.g.
int + float -> float
. - If both types are floating point types, the maximum type is the widest floating point type. E.g.
float + double -> double
. - If both types are integer types with the same signedness, the
maximum type is the widest integer type of the two. E.g.
uint + ulong -> ulong
. - If both types are integer types with different signedness, the
maximum type is a signed integer with the same bit width as the maximum integer type.
ulong + int -> long
- If at least one side is a struct or a pointer to a struct with an
inline
directive on a member, check recursively check if the type of the inline member can be used to find a maximum type (see below under sub struct conversions) - All other cases are errors.
Substruct conversions
Substructs may be used in place of its parent structs in many cases. The rule is as follows:
- A substruct pointer may implicitly convert to a parent struct.
- A substruct value may be implicitly assigned to a variable with the parent struct type, This will truncate the value, copying only the parent part of the substruct. However, a substruct value cannot be assigned its parent struct.
- Substruct slices and arrays can not be cast (implicitly or explicitly) to an array of the parent struct type.
Pointer conversions
Pointer conversion between types usually need explicit casts.
The exception is void *
which any type may implicitly convert to or from.
Conversion rules from and to arrays are detailed under arrays
Vector conversions
Conversion between underlying vector types need explicit conversions. They work
as regular conversions with one notable exception: converting a true
boolean
vector value into an int will yield a value with all bits set. So bool[<2>] { true, false }
converted to for example char[<2>]
will yield { 255, 0 }
.
Vectors can also be cast to the corresponding array type, for example: char[<2>]
<=> char[2]
.
Binary conversions
1. Multiplication, division, remainder, subtraction / addition with both operands being numbers
These operations are only valid for integer and float types.
- Resolve the operands.
- Find the maximum type of the two operands.
- Promote both operands to the resulting type if both are simple expressions
- The resulting type of the expression is the maximum type.
2. Addition with left side being a pointer
- Resolve the operands.
- If the rhs is not an integer, this is an error.
- If the rhs has a bit width that exceeds isz, this is an error.
- The result of the expression is the lhs type.
3. Subtraction with lhs pointer and rhs integer
- Resolve the operands.
- If the right hand type has a bit width that exceeds isz, this is an error.
- The result of the expression is the left hand type.
4. Subtraction with both sides pointers
- Resolve the operands.
- If the either side is a
void *
, it is cast to the other type. - If the types of the sides are different, this is an error.
- The result of the expression is isz.
- If this result exceeds the target width, this is an error.
6. Bit operations ^
&
|
These operations are only valid for integers and booleans.
- Resolve the operands.
- Find the maximum type of the two operands.
- Promote both operands to the maximum type if they are simple expressions.
- The result of the expression is the maximum type.
6. Shift operations <<
>>
These operations are only valid for integers.
- Resolve the operands.
- In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side.
- The result of the expression is the lhs type.
7. Assignment operations +=
-=
*=
*=
/=
%=
^=
|=
&=
- Resolve the lhs.
- Resolve the right operand as an assignment rhs.
- The result of the expression is the lhs type.
8. Assignment shift >>=
<<=
- Resolve both operands
- In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side.
- The result of the expression is the lhs type.
9. &&
and ||
- Resolve both operands.
- Insert bool cast of both operands.
- The type is bool.
10. <=
==
>=
!=
- Resolve the operands, left to right.
- Find the maximum type of the two operands.
- Promote both operands to the maximum type.
- The type is bool.
Unary conversions
1. Bit negate
- Resolve the inner operand.
- If the inner type is not an integer this is an error.
- The type is the inner type.
2. Boolean not
- Resolve the inner operand.
- The type is bool.
3. Negation
- Resolve the inner operand.
- If the type inner type is not a number this is an error.
- If the inner type is an unsigned integer, cast it to the same signed type.
- The type is the type of the result from (3)
4. &
and &&
- Resolve the inner operand.
- The type is a pointer to the type of the inner operand.
5. *
- Resolve the inner operand.
- If the operand is not a pointer, or is a
void *
pointer, this is an error. - The type is the pointee of the inner operand’s type.
Dereferencing 0 is implementation defined.
6. ++
and --
- Resolve the inner operand.
- If the type is not a number, this is an error.
- The type is the same as the inner operand.
Base expressions
1. Typed identifiers
- The type is that of the declaration.
- If the width of the type is less than that of the target type, widen to the target type.
- If the width of the type is greater than that of the target type, it is an error.
2. Constants and literals
- If the constant is an integer, it is assumed to be the arithmetic promotion width and signed.
If the suffix
u
is added, it is assumed to be an unsigned number. If a suffixixx
oruxx
is given then it is considered a an integer of that type width and signedness. It cannot be implicitly narrowed. - If the constant is a floating point value, it is assumed to be a
double
unless suffixed withf
which is then assumed to be afloat
. If a bit width is given afterf
, it is instead a floating point type of that width.