== Type Checking In this section, we explain the rules used to assign types to expressions. FPP is a statically typed language. That means the following: * Type checking of expressions occurs during <>. * If the type checking phase detects a violation of any of these rules, then translation halts with an error message and does not produce any code. Each type represents a collection of <>. The type checking rules exist to ensure that whenever an expression of type stem:[T] is <>, the result is a value of type stem:[T]. === Integer Literals To type an <>, the semantic analyzer does the following: . Evaluate the expression to an unsigned integer value stem:[v]. . Assign the type <> to the expression. **Examples:** [source,fpp] ---- constant a = 0 # Type is Integer constant b = 1 # Type is Integer constant c = 256 # Type is Integer constant d = 65536 # Type is Integer constant e = 0x100000000 # Type is Integer ---- === Floating-Point Literals The type of a <> is `F64`. === Boolean Literals The type of a <> is `bool`. === String Literals The type of a <> is `string`. === Identifier Expressions To type an <> stem:[e], the semantic analyzer <> and uses the type given in the definition. **Example:** [source,fpp] ---- constant a = 42 # a is a constant with type Integer constant b = a # The expression a refers to the constant a and has type Integer ---- === Dot Expressions To type a <> stem:[e] `.x`, the semantic analyzer does the following: . If stem:[e]`.x` is a <> that represents the use of a definition according to the <>, and the use is a valid <>, then use the type given in the definition. . Otherwise do the following: .. Compute the type stem:[T] of stem:[e]. .. If stem:[T] is a <> or <> with a member `x`, then use the type associated with `x`. .. Otherwise throw an error. *Example:* [source,fpp] ---- module M { constant a = 0 # The constant M.a has type Integer constant b = { c = "string1" d = "string2" } } constant b = M.a # Expression M.a represents a use of the definition M.a. # Its type is Integer. constant c = M.b.c # Expression M.b.c uses M.b and selects member `c`. # Its type is string. ---- === Array Expressions To type an <> `[` stem:[e_1] `,` stem:[...] `,` stem:[e_n] `]`, the semantic analyzer does the following: . For each stem:[i in [1,n]], compute the type stem:[T_i] of stem:[e_i]. . Compute the <> stem:[T] of the list of types stem:[T_i]. . Assign the type _[_ stem:[n] _]_ stem:[T] to the expression. *Examples:* [source,fpp] ---- constant a = [ 1, 2, 3 ] # Type is [3] Integer constant b = [ 1, 2, 3.0 ] # Type is [3] F64 ---- === Array Subscript Expressions To type an <> stem:[e_1] `[` stem:[e_2] `]`, the semantic analyzer does the following: . Compute the type stem:[T_1] of stem:[e_1]. . Check that stem:[T_1] is an <> or <> with element type stem:[T] and size stem:[n]. . Compute the type stem:[T_2] of stem:[e_2]. . Check that stem:[T_2] <> _Integer_. . Use stem:[T] as the type of the expression. === Struct Expressions To type a <> `{` stem:[m_1] `=` stem:[e_1] `,` stem:[...] `,` stem:[m_n] `=` stem:[e_n] `}`, the semantic analyzer does the following: . Check that the member names stem:[m_i] are distinct. . For each stem:[i in [1,n]], compute the type stem:[T_i] of stem:[e_i]. . Assign the type _{_ stem:[m_1] _=_ stem:[T_1] _,_ stem:[...] _,_ stem:[m_n] _=_ stem:[T_n] _}_ to the expression. *Examples:* [source,fpp] ---- constant a = { x = 1, y = 2.0 } # Type is { x: Integer, y: F64 } constant b = { x = 1, x = 2 } # Error ---- === Unary Negation Expressions To type a <> `-` stem:[e], the semantic analyzer does the following: . Compute the type stem:[T] of stem:[e]. . If stem:[T] is a <>, then use stem:[T]. . Otherwise if stem:[T] <> _Integer_, then use _Integer_. . Otherwise throw an error. **Examples:** [source,fpp] ---- constant a = -1.0 # Type is F64 constant b = -1 # Type is Integer constant c = -c # Type is Integer constant d = -0xFFFFFFFF # Type is Integer enum E { X = 1 } constant e = -E.X # Type is Integer constant f = -true # Error ---- === Binary Arithmetic Expressions To type a <> stem:[e_1] _op_ stem:[e_2], the semantic analyzer does the following: . Compute the common type stem:[T] of stem:[e_1] and stem:[e_2]. . If stem:[T] is a <>, then use stem:[T]. . Otherwise if stem:[T] <> _Integer_, then use _Integer_. . Otherwise throw an error. **Examples:** [source,fpp] ---- constant a = 1 + 2 # Type is Integer constant b = 3 + 4 # Type is Integer constant c = -0xFFFFFFFF # Type is Integer enum E { X = 1, Y = 2 } constant d = X + Y # Type is Integer constant e = true + "abcd" # Error ---- === Parenthesis Expressions To type a <> `(` stem:[e] `)`, the semantic analyzer does the following: . Compute the type stem:[T] of stem:[e]. . Use stem:[T] as the type of the expression. **Examples:** [source,fpp] ---- constant a = (1.0 + 2) # Type is F64 constant b = (3 + 4) # Type is Integer constant c = (true) # Type is bool constant d = ("abcd") # Type is string constant e = ([ 1, 2, 3]) # Type is [3] Integer ---- === Identical Types We say that types stem:[T_1] and stem:[T_2] are *identical* if one of the following holds: . stem:[T_1] and stem:[T_2] are <> with the same name, e.g., `U32` or _Integer_. In each case of a numeric type, the name of the type uniquely identifies the type. . stem:[T_1] and stem:[T_2] are both <>. . stem:[T_1] and stem:[T_2] are both the same <>. . Each of stem:[T_1] and stem:[T_2] is an <>, <>, <>, <>, or <>, and both types refer to the same definition. === Type Conversion We say that a type stem:[T_1] *may be converted to* another type stem:[T_2] if every <> represented by type stem:[T_1] can be <> into a value of type stem:[T_2]. Here are the rules for type conversion: . stem:[T_1] may be converted to stem:[T_2] if stem:[T_1] and stem:[T_2] are <>. . If either stem:[T_1] or stem:[T_2] or both is an <>, then stem:[T_1] may be converted to stem:[T_2] if the <> of stem:[T_1] may be converted to the <> of stem:[T_2]. . Any <> may be converted to any other string type. . Any <> may be converted to any other numeric type. . An <> may be converted to a <>. . If stem:[T_1] or stem:[T_2] or both are <>, then stem:[T_1] may be converted to stem:[T_2] if the conversion is allowed after replacing the array type or types with structurally equivalent <>. . An anonymous array type stem:[T_1 =] _[_ stem:[n] _]_ stem:[T'_1] may be converted to the anonymous array type stem:[T_2 =] _[_ stem:[m] _]_ stem:[T'_2] if stem:[n = m] and stem:[T'_1] may be converted to stem:[T'_2]. . A <>, <>, <>, or <> stem:[T_1] may be converted to an anonymous array type stem:[T_2] if stem:[T_1] may be converted to the member type of stem:[T_2]. . If stem:[T_1] or stem:[T_2] or both are <>, then stem:[T_1] may be converted to stem:[T_2] if the conversion is allowed after replacing the struct type or types with structurally equivalent <>. For purposes of structural equivalence, the member sizes of struct types are ignored. For example, the struct type `S` defined by `struct S { x: [3] U32 }` is structurally equivalent to the anonymous struct type `{ x: U32 }`. . An anonymous struct type stem:[T =]_{_ stem:[m_1] _:_ stem:[T_1] _,_ stem:[...] _,_ stem:[m_1] _:_ stem:[T_n] _}_ may be converted to the anonymous struct type stem:[T'] if for each stem:[i in [1,n\]], .. stem:[m_i] _:_ stem:[T'_i] is a member of stem:[T']; and .. stem:[T_i] may be converted to stem:[T'_i]. . A <> type, <>, <>, or <> stem:[T] may be converted to an <> stem:[T'] if for each member stem:[m_i] `:` stem:[T_i] of stem:[T'], stem:[T] may be converted to stem:[T_i]. . Type convertibility is transitive: if stem:[T_1] may be converted to stem:[T_2] and stem:[T_2] may be converted to stem:[T_3], then stem:[T_1] may be converted to stem:[T_3]. === Computing a Common Type ==== Pairs of Types Here are the rules for resolving two types stem:[T_1] and stem:[T_2] (e.g., the types of two subexpressions) to a common type stem:[T] (e.g., the type of the whole expression): . If stem:[T_1] and stem:[T_2] are <>, then let stem:[T] be stem:[T_1]. . Otherwise if stem:[T_1] or stem:[T_2] or both are <>, then do the following: .. Let stem:[L_1] be the <> of stem:[T_1]. .. Let stem:[L_2] be the <> of stem:[T_2]. .. If there is no type in stem:[L_1] that is <> to a type in stem:[L_2], then replace each each alias type with its <> and reapply these rules. .. Otherwise let stem:[T] be the first type in stem:[L_2] that is identical to a type in stem:[L_1]. + Note that the algorithm is symmetric in stem:[L_1] and stem:[L_2], i.e., reversing stem:[L_1] and stem stem:[L_2] in the last two items would produce the same result. . Otherwise if stem:[T_1] and stem:[T_2] are both <>, then do the following: .. If stem:[T_1] and stem:[T_2] are both floating-point types, then use `F64`. .. Otherwise use <>. . Otherwise if stem:[T_1] and stem:[T_2] are both <>, then use `string`. . Otherwise if stem:[T_1] or stem:[T_2] or both are enum types, then replace the enum type or types with the representation type specified in the enum definitions and and reapply these rules. . Otherwise if stem:[T_1] or stem:[T_2] or both are array types, then replace the array type or types with structurally equivalent anonymous array types and reapply these rules. . Otherwise if stem:[T_1] and stem:[T_2] are anonymous array types with the same size stem:[n] and with member types stem:[T'_1] and stem:[T'_2], then apply these rules to resolve stem:[T'_1] and stem:[T'_2] to stem:[T'] and let stem:[T] be _[_ stem:[n] _]_ stem:[T']. . Otherwise if one of stem:[T_1] and stem:[T_2] is a type stem:[T'] that is convertible to an anonymous array type and the other one is an anonymous array type _[_ stem:[n] _]_ stem:[T''], then apply these rules to resolve stem:[T'] and stem:[T''] to a common type stem:[T''']. Let stem:[T] be the array type _[_ stem:[n] _]_ stem:[T''']. . Otherwise if stem:[T_1] or stem:[T_2] or both are struct types, then replace the struct type or types with structurally equivalent anonymous struct types and reapply these rules. . Otherwise if stem:[T_1] and stem:[T_2] are both anonymous struct types, then use the anonymous struct type stem:[T] with the following members: .. For each member stem:[m_1] _:_ stem:[T'_1] of stem:[T_1], if stem:[T_2] has a member stem:[m_1] _:_ stem:[T'_2], then apply these rules to convert stem:[T'_1] and stem:[T'_2] to a common type stem:[T'] and use stem:[m_1] _:_ stem:[T']. Otherwise use stem:[m_1] _:_ stem:[T'_1]. .. For each member stem:[m_2] _:_ stem:[T'_2] of stem:[T_2] such that stem:[T_1] has no member named stem:[m_2], use stem:[m_2] _:_ stem:[T'_2]. . Otherwise if one of stem:[T_1] and stem:[T_2] is a type stem:[T'] that is convertible to an anonymous struct type and the other one is an anonymous struct type stem:[S], then apply these rules to resolve stem:[T'] and each of the struct member types to a common type. Let stem:[T] be the struct type whose member names are the member names of stem:[S] and whose member types are the corresponding common types. . Otherwise the attempted resolution is invalid. Throw an error. ==== Lists of Types To compute a common type for a list of types stem:[T_1, ... , T_n], do the following: . Check that stem:[n > 0]. If not, then throw an error. . Let stem:[T'_1] be stem:[T_1]. . For each stem:[i in [2,n]], compute the <> stem:[T'_i] of stem:[T'_(i-1)] and stem:[T_i]. . Use stem:[T'_n] as the common type option of the list.