c_types.pl
C's types as terms: specifiers and declarators become type terms, and from those come sizes and layouts, the integer conversions and the values of constant expressions (milestone 4a).
Types
| Type | Means |
|---|---|
void bool | as in C |
char schar uchar | char, signed char, unsigned char |
short int long llong | the signed integer types |
ushort uint ulong ullong | their unsigned counterparts |
float double ldouble | the floating types |
ptrT | a pointer to T |
arrayT, N | N elements of T, or arrayT, none of unknown length |
funcRet, Params, Arity | Params a list of types, or unspecified; Arity fixed or variadic |
structId unionId | the struct or union Id of the environment's Defs |
badReason | a type the frontend cannot handle yet |
Parameter types are already adjusted: arrays and functions become pointers. Qualifiers are dropped, and an enum type is its underlying integer type. A badReason is an error only when a declaration uses it, so headers that mention such types still parse.
The environment
The semantic environment eOrds, Tags, Defs, Next has four parts:
| Part | From | To |
|---|---|---|
| Ords | an ordinary identifier | typedefT, enumconstV, T, or what lowering stores for a variable or function |
| Tags | a tag | tagKind, Id, Kind struct or union, or enumT |
| Defs | a struct or union Id | its definition, below |
| Next | the next Id |
A definition is one of:
| Definition | Means |
|---|---|
incompleteTag | declared, not yet defined; Tag is someName, or none when anonymous |
defKind, Members, Size, Align | complete: Members in order, and the layout |
partialReason, Members | only the members whose offsets are known: no usable layout |
badReason | a definition the frontend cannot handle yet |
and a member one of:
| Member | Means |
|---|---|
msomeField, Type, Offset | Field, of Type, Offset bytes in |
mnone, Type, Offset | an anonymous struct or union member |
msomeField, Type, Offset, bitsPos, Width | a bit-field: Width bits from bit Pos of the Type at Offset |
Lowering keeps entries of its own in Defs too. A block scope saves Ords and Tags and restores them when it ends; Defs and Next only grow. A predicate that changes the environment takes Env0 and gives Env as its last two arguments, so it threads through foldl/4,6.
Documentation is a relation of the module, doc(Head, Det, Props) (docs/dterm.md); tests/unit.pl runs every example.
Environment
Env has no user identifiers or tags, and the builtin va_list record.
Value is what the ordinary identifier Name stands for in Env.
Env is Env0 with the ordinary identifier Name standing for Value, replacing any binding it had.
Scope is the scoped part of Env (its ordinary identifiers and tags), for env_restore/3. The third argument is unused.
Env is Env0 with its scoped part replaced by Scope: the identifiers and tags of an inner scope are gone, its definitions stay.
Throw nanofil_error with the message Fmt/Args, prefixed with Loc. Never succeeds.
Declarations
Decls are d(Name, Type, Storage, Asm, Init) for the declarators Inits under the specifiers Specs, Storage one of none, static or extern. A typedef declares no Decls; Env is Env0 with its names bound instead. Env also has any struct, union or enum Specs defines. Throws not_constant when an enumerator's value is not constant.
Decl is the d/5 term Init declares, or none for a typedef, which binds the name.
Keep qualification for type reflection without changing the unqualified code-generation types. These entries share ordinary identifier scope, including shadowing and typedefs. Lvalue conversion removes only the outer qualification, never the qualification of a pointer's target.
Specifiers
Type is the type the declaration specifiers Specs name, bad(Reason) if the frontend cannot handle it. Env is Env0 with the struct, union and enum definitions in Specs entered.
Type from the type specifiers alone: either keywords or one named type, never both.
the type a list of type keywords names.
implicit int, as in old headers' K&R style
the msorted keywords name Type.
Structs and unions
Members is none for a reference to the tag, which declares it if it is not yet visible, and otherwise the list of member declarations that define it. Tag is some(Name), or none for an anonymous struct or union; a reference always has a tag.
Type is the struct or union Members define, laid out in Env.
A later unsupported member (notably glibc FILE's _flags2 bitfield) does not change preceding offsets. Never infer offsets beyond it, or a size/alignment for the whole object; those uses must still reject.
Tag names the Kind with Id, which is declared but not yet defined. An anonymous one never is.
Fields are the f(Name, Type) or bit-fields b(Name, Type, Width) the member declaration Decl declares ([] for a static assertion), or [bad(Why)]; Name is some(Field), or none for an anonymous member or an unnamed bit-field. Reflected are the q(Name, QType) of the named members, QType keeping the qualifiers that Type drops.
a bit-field Name of Type may have the width Expr, of value W: an integer constant no wider than Type, and 0 only without a name (6.7.2.1p4).
su_layout(+Kind, +Fields, +Env, -Def), with the SysV rules: each member at the next multiple of its alignment; size rounded up to the alignment. A trailing flexible array member has size 0. A bit-field follows the bits before it unless it would cross a boundary of its type's size, where it starts instead; a zero-width one only moves to that boundary. Unnamed bit-fields do not align the struct.
Layout is placed(Members, End, Align) for Fields laid out in Env, or incomplete when a field has no size. Throws bad(Why) as type_size_align/4.
Members, the member Field declares or none for an unnamed bit-field, are placed after members ending at bit End0 with alignment Align0; End and Align include them. Throws bad(Why) as type_size_align/4. Any type but a flexible array (array(_, none)) has its own size, so the if-then-else keeps that default case.
a member of Size and Align after members ending at bit End0 is at byte Off and ends the struct or union so far at bit End.
a bit-field of W bits, in units of UnitBits, after bits ending at End0 occupies bits Start to End.
Enums
Type is the enum's underlying type. Enumerators is none for a reference to Tag, and otherwise the list that defines it; a reference to an unknown enum tag has type uint. Tag is some(Name) or none, as in su_type/7.
Type is the underlying type of the enum Enumerators define; Env binds its constants and its tag.
V is the constant's value, Next0 when it has no expression, and Env binds it. Each constant has type int when it fits, as C requires of constants whose values fit; the enum's own type follows clang.
Type is the type clang gives an enum whose constants have Values: the first of uint, int, ulong and long that holds them all (uint when there are none).
Declarators
Type is the declarator type SyntaxType with the atom base replaced by Base, array sizes evaluated and parameter types resolved. The parser builds declarator types outermost first around base. An array size that is not a non-negative constant gives bad(Reason). Env is Env0 with the definitions of any struct or union a parameter list declares; their names are scoped to the list.
Types are the adjusted parameter types. Parameter declarations are scoped to the parameter list.
Type is the type the type name TypeName (as in a cast or sizeof) denotes; Env is Env0 with any tag it defines.
Type is variably modified (6.7.6p3), which nanofil does not support. The cut ending the func clause succeeds once however many parts are variably modified.
| T | in | a type an expression or declaration names |
| Format | out | the diagnostic's format |
| Args | out | its arguments |
nanofil does not support T: it is variably modified, since its size expressions would have to be evaluated, side effects and all, or it has no values nanofil can hold.
| ✓ | unsupported_typeldouble, '~w is not supported', 'long double' | ||
| ✓ | unsupported_typeptrldouble, _, _ | ⟹ | fails |
nanofil has no values of Type, for the reason Why.
Sizes
Size and Align are the size and alignment in bytes of Type in Env. Throws bad(Why) for a bad or incomplete type.
the struct or union Id has Size and Align. Fails when it is incomplete; throws bad(Why) when it is bad.
Type is complete in Env: it has a size.
The struct or union type SU has a member Name of Type at Offset, looking into anonymous members. Fails if it has none, if Name is a bit-field, or if SU is not defined.
| SU | in | an unqualified struct or union type |
| Name | in | a member name |
| Env | in | the scope that defines SU |
| Type | out | the member's type |
| Off | out | the byte offset of the member, or of the Type that holds a bit-field |
| Bits | out | none, or bits(Pos, Width) for a bit-field of Width bits from bit Pos |
Looks into anonymous members. A bit-field follows the bits before it unless it would cross a boundary of its type's size; a member that is not a bit-field starts at the next byte its alignment allows.
| ✓ | specs_typesustruct, none, fieldtspecchar, dclsomec, base, none, none, fieldtspecint, dclsomea, base, none, num3, none−dec, fieldtspecint, dclsomeb, base, none, num30, none−dec, [], S, E0, E member_layoutS, a, E, T, Off, Bits | ⟹ | T = int, Off = 0, Bits = bits8, 3 |
| ✓ | specs_typesustruct, none, fieldtspecchar, dclsomec, base, none, none, fieldtspecint, dclsomea, base, none, num3, none−dec, fieldtspecint, dclsomeb, base, none, num30, none−dec, [], S, E0, E member_layoutS, b, E, T, Off, Bits | ⟹ | T = int, Off = 4, Bits = bits0, 30 |
| ✓ | specs_typesustruct, none, fieldtspecint, dclnone, base, none, num8, none−dec, fieldtspecchar, dclsomec, base, none, none, [], S, E0, E member_layoutS, c, E, T, Off, Bits type_size_alignS, E, Size, Align | ⟹ | T = char, Off = 1, Bits = none, Size = 2, Align = 1 |
| ✓ | specs_typesustruct, none, fieldtspecint, dclsomea, base, none, num3, none−dec, [], S, E0, E member_type_offsetS, a, E, _, _ | ⟹ | fails |
Integers
| Type | in | a type term |
Type is an integer type: bool, the character types, and the standard signed and unsigned integer types.
| Type | in/out | an integer type |
| Size | in/out | its width in bytes |
| Signedness | in/out | signed or unsigned |
| Rank | in/out | its integer conversion rank, from 0 for bool to 5 for long long |
C's integer types on x86-64 Linux. Plain char is signed, long is as wide as long long, and the three character types share a rank.
| ✓ | integer_typelong, S, G, R | ⟹ | S = 8, G = signed, R = 4 |
| ✓ | findallT, integer_typeT, 8, unsigned, _, Ts | ⟹ | Ts = ulong, ullong |
| 818 | bool | 1 | unsigned | 0 |
| 819 | char | 1 | signed | 1 |
| 820 | schar | 1 | signed | 1 |
| 821 | uchar | 1 | unsigned | 1 |
| 822 | short | 2 | signed | 2 |
| 823 | ushort | 2 | unsigned | 2 |
| 824 | int | 4 | signed | 3 |
| 825 | uint | 4 | unsigned | 3 |
| 826 | long | 8 | signed | 4 |
| 827 | ulong | 8 | unsigned | 4 |
| 828 | llong | 8 | signed | 5 |
| 829 | ullong | 8 | unsigned | 5 |
| Type | in/out | an integer type |
| Size | in/out | its width in bytes |
| Signedness | in/out | signed or unsigned |
integer_type/4 without the rank.
| ✓ | int_infouchar, S, G | ⟹ | S = 1, G = unsigned |
| Type | in/out | an integer type |
| Rank | in/out | its integer conversion rank |
| ✓ | rankschar, R | ⟹ | R = 1 |
| Signed | in | a signed integer type |
| Unsigned | out | the unsigned type of the same rank |
| ✓ | unsigned_ofllong, U | ⟹ | U = ullong |
| ✓ | unsigned_ofchar, U | ⟹ | U = uchar |
| ✓ | unsigned_ofuint, _ | ⟹ | fails |
| Type | in | a type term |
Type is an integer, floating-point or pointer type: one that has an IR class.
| ✓ | scalar_typeptrchar | ||
| ✓ | scalar_typestruct1 | ⟹ | fails |
| Type | in/out | a real floating type |
The floating types nanofil supports; long double is refused.
| ✓ | float_typeldouble | ⟹ | fails |
| Type | in | a type term |
Type is an integer or floating type.
| ✓ | arithmetic_typebool | ||
| ✓ | arithmetic_typeptrint | ⟹ | fails |
| Type | in | the type of an argument with no parameter type |
| Promoted | out | the type it is passed as |
The default argument promotions: float becomes double, and the integer promotions apply to the rest.
| ✓ | default_promotionfloat, P | ⟹ | P = double |
| ✓ | default_promotionchar, P | ⟹ | P = int |
| ✓ | default_promotionptrint, P | ⟹ | P = ptrint |
| Type | in | the type of an operand |
| Promoted | out | its type after the integer promotions |
int for an integer type of rank below int, which int can always represent here, otherwise Type itself, also for a type that is not an integer.
| ✓ | integer_promotionushort, P | ⟹ | P = int |
| ✓ | integer_promotionbool, P | ⟹ | P = int |
| ✓ | integer_promotionuint, P | ⟹ | P = uint |
| ✓ | integer_promotiondouble, P | ⟹ | P = double |
| TypeA | in | the type of one arithmetic operand |
| TypeB | in | the type of the other |
| Type | out | their common real type |
The usual arithmetic conversions. A floating operand wins; otherwise both are promoted, and when the signedness differs the unsigned type wins unless the signed one is wider. A signed type exactly as wide as a higher-ranked unsigned one gives way to its own unsigned type.
| ✓ | usual_arith_conversionint, double, T | ⟹ | T = double |
| ✓ | usual_arith_conversionfloat, long, T | ⟹ | T = float |
| ✓ | usual_arith_conversionchar, short, T | ⟹ | T = int |
| ✓ | usual_arith_conversionint, uint, T | ⟹ | T = uint |
| ✓ | usual_arith_conversionuint, long, T | ⟹ | T = long |
| ✓ | usual_arith_conversionlong, ullong, T | ⟹ | T = ullong |
| ✓ | usual_arith_conversionllong, ulong, T | ⟹ | T = ullong |
| ✓ | usual_arith_conversionptrint, int, _ | ⟹ | fails |
| V | in | an integer |
| Type | in | an integer type |
| W | out | V converted to Type |
Conversion wraps modulo the width, as the standard requires for unsigned types and GCC and clang define for signed ones; bool is 0 or 1. Fails when Type is not an integer type. The cut commits to bool, which int_info/3 also describes as a byte.
| ✓ | convert_int300, uchar, W | ⟹ | W = 44 |
| ✓ | convert_int256, bool, W | ⟹ | W = 1 |
| ✓ | convert_int-1, uint, W | ⟹ | W = 4294967295 |
| ✓ | convert_int128, schar, W | ⟹ | W = -128 |
| ✓ | convert_int1, double, _ | ⟹ | fails |
| Signedness | in | signed or unsigned |
| U | in | a word, 0 =< U < M |
| M | in | the modulus of the word |
| W | out | the value of U read with Signedness |
| ✓ | signed_wordsigned, 255, 256, W | ⟹ | W = -1 |
| ✓ | signed_wordunsigned, 255, 256, W | ⟹ | W = 255 |
| Type | in/out | a scalar type |
| Class | in/out | its IR class |
w for an integer type of at most 4 bytes, l for an 8-byte one, p for a pointer, s and d for float and double. Deterministic when Type is bound.
| ✓ | type_classushort, K | ⟹ | K = w |
| ✓ | type_classullong, K | ⟹ | K = l |
| ✓ | type_classptrvoid, K | ⟹ | K = p |
| ✓ | type_classfloat, K | ⟹ | K = s |
| ✓ | type_classstruct0, _ | ⟹ | fails |
Arithmetic constant expressions
A constant expression has a type and a value: an integer, IEEE bits for float/double, or ub where evaluating it is undefined (integer division by zero). Operators propagate ub, except from operands that && || and ?: do not evaluate (C11 6.6p3): 1 || 1/0 is 1, and 1/0 alone is not constant. Every operand must still be a constant expression.
The environment is a c_types environment, or pp for a #if expression, where every integer type acts as intmax_t or uintmax_t (6.10.1p4): a constant is a long or ulong, and so is the result of ! == && and the like. The preprocessor has replaced identifiers by 0.
Expr is an integer constant expression with Value of Type in Env. Throws not_constant when Expr is not one.
Float constants use their unsigned IEEE bits, never a host float.
glibc's HUGE_VAL and INFINITY are builtin constant expressions, not libm calls. Keep the value bits exact, including in static storage.
| 1048 | '__builtin_huge_val' | double | 0x7ff0000000000000 |
| 1049 | '__builtin_inf' | double | 0x7ff0000000000000 |
| 1050 | '__builtin_huge_valf' | float | 0x7f800000 |
| 1051 | '__builtin_inff' | float | 0x7f800000 |
C arithmetic conversion of a constant. An undefined float-to-int conversion propagates ub, just like integer division by zero.
Value is the value of the #if expression Expr; fails when Expr is not constant.
folded(+XV, +Env, -V): the value tree XV is a constant expression of value V. Its types are the tree's: the conversions are those the elaborator chose, so this is arithmetic only. A node that is not a constant (a load, a call, an assignment, an address) does not fold.
6.4.4.1, 6.4.4.4
6.4.4.2
offsetof's member path.
a constant or operator result of type T0 has type T in Env: T0 itself, or in a #if expression intmax_t or uintmax_t (6.10.1p4). A #if expression has no floating constants (6.10.1p1), so a float type has none there.
V is ub if one of Values is, and otherwise as Goal gives it.
B is 1 if Goal holds, else 0.
the binary operator Op converts its operands as Kind (arith, shift, compare or logical) names.
V, of type T, is the value of Op, of Kind, on operands A and B, each Value-Type. Arithmetic converts both to T, a comparison both to their common type (binary_operands/6), and a shift only the left one to T.
Division by zero is undefined: R is ub.
Type is the type of an integer constant of value V: the first type in the C11 list for its suffix and radix that can represent it, or ullong when none can.
| 1279 | dec | none | int, long, llong |
| 1280 | dec | u | uint, ulong, ullong |
| 1281 | dec | l | long, llong |
| 1282 | dec | ul | ulong, ullong |
| 1283 | dec | ll | llong |
| 1284 | dec | ull | ullong |
| 1285 | other | none | int, uint, long, ulong, llong, ullong |
| 1286 | other | u | uint, ulong, ullong |
| 1287 | other | l | long, ulong, llong, ullong |
| 1288 | other | ul | ulong, ullong |
| 1289 | other | ll | llong, ullong |
| 1290 | other | ull | ullong |
Compatible and composite types
| T | in | a type |
| U | in | another type |
T and U are compatible: the same type, or alike in their parts where one leaves a part open, an array's size or a function's parameters. Integer types of the same width are still distinct, and a qualified type is compatible only with the same qualified type.
| ✓ | compatible_typearrayint, none, arrayint, 3 | ||
| ✓ | compatible_typelong, llong | ⟹ | fails |
| ✓ | compatible_typeptrint, ptrqualifiedconst, int | ⟹ | fails |
| T | in | a type |
| U | in | a type compatible with T |
| C | out | their composite type |
The type that says what both say: an array's size where either knows it, a function's parameters where either has a prototype. Fails when T and U are not compatible.
| ✓ | composite_typeptrarrayint, none, ptrarrayint, 3, C | ⟹ | C = ptrarrayint, 3 |
| ✓ | composite_typefuncint, unspecified, fixed, funcint, ptrchar, fixed, C | ⟹ | C = funcint, ptrchar, fixed |
| ✓ | composite_typearrayint, 2, arrayint, 3, _ | ⟹ | fails |
| ✓ | composite_typefuncint, [], fixed, funcint, [], variadic, _ | ⟹ | fails |
parameter lists Ps and Qs, each unspecified without a prototype, agree on Cs.
| T0 | in | a reflected type |
| T | out | T0 without qualifiers, at any depth |
The type code generation uses: qualifiers say what a program may do with an object, not how it is stored.
| ✓ | unqualifiedptrqualifiedconst, char, T | ⟹ | T = ptrchar |
| ✓ | unqualifiedfuncqualified(volatile), int, unspecified, fixed, T | ⟹ | T = funcint, unspecified, fixed |
The typed tree
An expression elaborated is a tree whose every node carries its (reflected, qualified) type, with its category the node's principal functor:
X ::= obj(T, F) an lvalue designating an object of type T
| fun(T, F) a function designator of function type T
| val(T, F) a value of type T, not an lvalueThe conversions C applies implicitly are nodes of their own: where an operand is used for its value, value/2 wraps it in load(X) or decay(X). _Generic is resolved here: its tree is the selected association's. Every operand is elaborated but the arguments of a builtin, a statement expression's block and a compound literal's initializer, which stay as the source has them.
| E | in | a C expression, as c_parse reads it |
| Env | in | the scope it is read in |
| X | out | its typed tree |
X is E as C reads it: each node typed, and lvalues, function designators and values told apart. Fails when E cannot be typed.
| ✓ | ⟹ | X = valptrint, addrobjint, idx | |
| ✓ | ⟹ | X = vallong, int3000000000 | |
| ✓ | ⟹ | X = valuint, int3000000000 | |
| ✓ | ⟹ | X = objint, derefvalptrint, binadd, valptrint, decayobjarrayint, 4, ida, valint, int1 | |
| ✓ | ⟹ | X = valshort, commavalshort, assign=, objshort, idx, valint, int1, valshort, loadobjshort, idx | |
| ✓ | ⟹ | X = objarraychar, 3, str104, 105 | |
| ✓ | ⟹ | fails | |
| ✓ | bind_ordinaryf, fnfuncint, int, variadic, f, extern, E0, E elaboratecallidf, chr1, chr2, E, X | ⟹ | X = valint, call_, valint, int1, valint, int2 |
| ✓ | bind_ordinaryf, fnfuncint, int, fixed, f, extern, E0, E elaboratecallidf, chr1, chr2, E, _ | ⟹ | fails |
| ✓ | ⟹ | fails | |
| ✓ | ⟹ | fails | |
| ✓ | bind_ordinaryc, varnone, int, E0, E1 bind_ordinaryqtypec, qualifiedconst, int, E1, E elaborateassign=, idc, chr1, E, _ | ⟹ | fails |
an assignment to X has type T, X's own but for a bit-field, whose value has its promoted type (6.5.16p3).
T is the type of value XV without qualifiers, the type lowering gives it.
assignment Op stores B, a value of type TB, into an object of unqualified type TA: B as it is (6.5.16.1), or the result of the operator applied to the two (6.5.16.2).
each argument, elaborated in XArgs, may be assigned to the unqualified type of its parameter in Ps.
| Binding | in | what the identifier Name stands for in Env |
| Name | in | an ordinary identifier |
| Env | in | the scope, which records Name's reflected type |
| T | out | the type it has as an expression |
An enumeration constant has its enumeration's type. An object or function has the type its declaration reflects, qualifiers included, with array sizes that initializers have since completed.
| ✓ | ⟹ | T = qualifiedconst, int | |
| ✓ | bind_ordinaryqtypes, arraychar, none, E0, E identifier_typegvararraychar, 6, s, static, none, s, E, T | ⟹ | T = arraychar, 6 |
S is reflected type RT with the array dimensions of declared type T, which an initializer may have completed since RT was recorded.
| SU | in | a struct or union type, perhaps qualified |
| F | in | a member name |
| Env | in | the scope that defines SU |
| T | out | the member's reflected type |
Looks into anonymous members. A qualified SU qualifies the member.
| ✓ | member_typestruct'builtin.va_list', fp_offset, E, T | ⟹ | T = uint |
| ✓ | member_typequalifiedconst, struct'builtin.va_list', reg_save_area, E, T | ⟹ | T = qualifiedconst, ptrvoid |
| ✓ | member_typestruct'builtin.va_list', gp, E, _ | ⟹ | fails |
one of Members, or a member of an anonymous one, is F of type T.
a call of builtin Name, when Name is not declared, has Type.
builtin Name checks arithmetic Op on operands of type Fixed, or of any integer types (generic).
T is the value type of a GNU statement expression with block Items: its last item's, once the items before it have declared their names.
Lvalues, values and designators
| X | in | a typed tree |
| Cat | out | lvalue, function or value, as its principal functor says |
| ✓ | tree_categoryobjint, idx, C | ⟹ | C = lvalue |
| ✓ | tree_categoryvalint, int1, C | ⟹ | C = value |
| 1710 | obj_, _ | lvalue |
| 1711 | fun_, _ | function |
| 1712 | val_, _ | value |
| X | in | a typed tree |
| V | out | X used for its value |
An array becomes a pointer to its first element, a function a pointer to it, and another lvalue the value it holds, unqualified.
| ✓ | valueobjqualifiedconst, int, idc, V | ⟹ | V = valint, loadobjqualifiedconst, int, idc |
| ✓ | valueobjarrayint, 4, ida, V | ⟹ | V = valptrint, decayobjarrayint, 4, ida |
| ✓ | valuefunfuncint, [], fixed, idf, V | ⟹ | V = valptrfuncint, [], fixed, decayfunfuncint, [], fixed, idf |
| ✓ | valuevalint, int7, V | ⟹ | V = valint, int7 |
| T | in | the unqualified integer type of a bit-field |
| W | in | its width in bits |
| P | out | the type of its value |
A bit-field's value is an int if an int can represent all its values, otherwise an unsigned int if that can, otherwise of its own type. C11 says so for _Bool, int and unsigned int; clang and GCC treat bit-fields of the other integer types alike.
| ✓ | bitfield_promotionuint, 31, P | ⟹ | P = int |
| ✓ | bitfield_promotionuint, 32, P | ⟹ | P = uint |
| ✓ | bitfield_promotionint, 32, P | ⟹ | P = int |
| ✓ | bitfield_promotionulong, 32, P | ⟹ | P = uint |
| ✓ | bitfield_promotionulong, 33, P | ⟹ | P = ulong |
| ✓ | bitfield_promotionlong, 33, P | ⟹ | P = long |
| ✓ | bitfield_promotionbool, 1, P | ⟹ | P = int |
| T | in | the type of an expression |
| V | out | the type of its value |
value/2 on types alone.
| ✓ | value_typequalified(volatile), long, V | ⟹ | V = long |
| ✓ | value_typearraychar, 3, V | ⟹ | V = ptrchar |
| ✓ | value_typefuncvoid, unspecified, fixed, V | ⟹ | V = ptrfuncvoid, unspecified, fixed |
| E | in | a C expression |
| Env | in | the scope it is read in |
| T | out | its reflected type, unevaluated |
Arrays and functions keep their types until a caller asks for value conversion.
| ✓ | ⟹ | T = arrayint, 4 |
| E | in | a C expression |
| Env | in | the scope it is read in |
| Cat | out | lvalue, function or value |
| ✓ | ⟹ | C = lvalue | |
| ✓ | ⟹ | C = function | |
| ✓ | ⟹ | C = value |
| E | in | a C expression |
| Env | in | the scope it is read in |
E is an lvalue whose type is not const, an array or incomplete, nor a struct or union with a const member, however deeply contained.
| ✓ | |||
| ✓ | bind_ordinaryc, varnone, int, E0, E1 bind_ordinaryqtypec, qualifiedconst, int, E1, E modifiable_lvalueidc, E | ⟹ | fails |
| ✓ | ⟹ | fails | |
| ✓ | modifiable_lvaluechr0, E | ⟹ | fails |
typed tree X designates a modifiable object.
T is const, or an aggregate or union with a const member or element, at any depth.
Operators
| Op | in | plus, neg, bnot or not |
| T0 | in | the value type of the operand |
| T | out | the type of the result |
+ and - need an arithmetic operand and ~ an integer, which they promote; ! needs a scalar and gives an int.
| ✓ | unary_operandneg, char, T | ⟹ | T = int |
| ✓ | unary_operandplus, ullong, T | ⟹ | T = ullong |
| ✓ | unary_operandnot, ptrint, T | ⟹ | T = int |
| ✓ | unary_operandbnot, double, _ | ⟹ | fails |
| ✓ | unary_operandneg, ptrint, _ | ⟹ | fails |
binary Op on operands of value types TA and TB has type T; && and || give an int (6.5.13-14).
| Op | in | a binary operator, not && or || |
| TA | in | the value type of the left operand |
| TB | in | the value type of the right operand |
| CA | out | the type the left operand converts to |
| CB | out | the type the right operand converts to |
| T | out | the type of the result |
A shift promotes each operand on its own and has the left one's type. Other arithmetic operands take their usual arithmetic type, the result's type unless Op compares; %, &, ^ and | need integers. A pointer and an integer add, or the pointer subtracts the integer, as the pointer and a long count of elements; two pointers subtract to a long count. A comparison with a pointer compares both operands as addresses, ulong. Fails if Op does not apply.
| ✓ | binary_operandsshl, char, ullong, CA, CB, T | ⟹ | CA = int, CB = ullong, T = int |
| ✓ | binary_operandsadd, int, uint, CA, CB, T | ⟹ | CA = uint, CB = uint, T = uint |
| ✓ | binary_operandslt, int, double, CA, CB, T | ⟹ | CA = double, CB = double, T = int |
| ✓ | binary_operandsadd, char, ptrint, CA, CB, T | ⟹ | CA = long, CB = ptrint, T = ptrint |
| ✓ | binary_operandssub, ptrint, ptrint, _, _, T | ⟹ | T = long |
| ✓ | binary_operandseq, ptrint, int, CA, CB, T | ⟹ | CA = ulong, CB = ulong, T = int |
| ✓ | binary_operandsmod, double, int, _, _, _ | ⟹ | fails |
| ✓ | binary_operandssub, int, ptrint, _, _, _ | ⟹ | fails |
binary Op applies to two operands of Domain types, arithmetic or integer.
| From | in | the unqualified value type of the operand |
| To | in | the type named in the cast |
Anything casts to void, and a scalar to a scalar, but a floating value not to or from a pointer. As in clang and GCC, a struct or union may also be cast to its own type.
| From | in | the value type of the right operand |
| Null | in | null if the right operand is a null pointer constant, else value |
| To | in | the unqualified type of the object assigned |
A value of type From may be assigned to an object of type To, and so passed for a parameter of type To, returned from a function returning To, or initialize a To. Integers and pointers do not mix, but for a null pointer constant or a pointer tested as a bool, and function pointers must be compatible. Clang only warns about the other pointer pairs (incompatible object types, lost qualifiers, object and function pointers), and they are accepted.
| ✓ | assignabledouble, value, char | ||
| ✓ | assignableptrint, value, bool | ||
| ✓ | assignableint, null, ptrint | ||
| ✓ | assignableptrint, value, ptrchar | ||
| ✓ | assignableint, value, ptrint | ⟹ | fails |
| ✓ | assignableptrfuncint, [], fixed, value, ptrfuncvoid, [], fixed | ⟹ | fails |
| ✓ | assignablestruct1, value, struct2 | ⟹ | fails |
| Arms | in | A-B, the second and third operands of ?: |
| Env | in | the scope they are read in |
| T | out | the type of the result |
From the arms' value types; fails if the arms are incompatible or cannot be typed.
| ✓ | ⟹ | T = ptrchar | |
| ✓ | ⟹ | fails |
| E | in | an expression that can be typed |
| Env | in | the scope it is read in |
| To | in | the type of the object its value initializes |
| Loc | in | where E is, for the diagnostic |
E's value may be assigned to a To, as a return value or an initializer is; else the error names the two types. Arguments and assignment sources are checked where their calls and assignments elaborate.
value tree XV is a null pointer constant (null) or not (value).
| XV | in | a typed tree, used for its value |
| Env | in | the scope it was elaborated in |
XV is an integer constant expression of value 0, or one cast to void * (unqualified void).
| ✓ | |||
| ✓ | |||
| ✓ | operandcasttypequalconst, tspecvoid, ptrbase, [], chr0, E, XV, _ | ⟹ | fails |
| ✓ | ⟹ | fails | |
| ✓ | bind_ordinaryx, varnone, int, E0, E operandbinmul, idx, chr0, E, XV, _ | ⟹ | fails |
| TA | in | the value type of the second operand of ?: |
| NA | in | null if it is a null pointer constant, else value |
| TB | in | the value type of the third operand |
| NB | in | null if it is a null pointer constant, else value |
| T | out | the type of the result |
Arithmetic arms take their usual arithmetic type, like structs or unions their own. A null pointer constant takes the other arm's pointer type. Pointer arms point to the composite of their targets, qualified with both arms' qualifiers, or to void if either does. As in clang and GCC, a void arm makes the result void whatever the other arm, and incompatible pointer arms give void *; a pointer and a nonnull integer are rejected.
| ✓ | arms_typechar, value, short, value, T | ⟹ | T = int |
| ✓ | arms_typevoid, value, int, value, T | ⟹ | T = void |
| ✓ | arms_typeptrint, value, int, null, T | ⟹ | T = ptrint |
| ✓ | arms_typeint, null, ptrchar, value, T | ⟹ | T = ptrchar |
| ✓ | arms_typeptrqualifiedconst, int, value, ptrvoid, value, T | ⟹ | T = ptrqualifiedconst, void |
| ✓ | arms_typeptrarrayint, none, value, ptrarrayint, 3, value, T | ⟹ | T = ptrarrayint, 3 |
| ✓ | arms_typeptrint, value, ptrlong, value, T | ⟹ | T = ptrvoid |
| ✓ | arms_typeptrint, value, int, value, _ | ⟹ | fails |
| ✓ | arms_typestruct1, value, struct2, value, _ | ⟹ | fails |
Generic selection
The controlling expression undergoes lvalue/array/function conversion, not integer promotion. Only the selected expression is evaluated, and its value category is retained. Association types must be unique even when they do not match the controlling type.
Diagnoses
When an expression cannot be typed, fault/4 says why: it finds the first operand, in the order lowering evaluates them, that cannot be typed, and the rule it breaks. The messages are lowering's, which the reject tests hold to. Where lowering would first refuse something the elaborator accepts (a type nanofil does not support, a struct or union passed by value, a va_arg or a statement expression), fault/4 declines rather than misplace the blame, and lowering reports.
| E | in | a full expression |
| Env | in | the scope it is read in |
| Loc | in | where it is, for a diagnostic |
| X | out | its typed tree |
elaborate/3, raising the diagnosis at Loc when E cannot be typed. Fails, raising nothing, when fault/4 declines to say why.
| E | in | an expression that cannot be typed |
| Env | in | the scope it is read in |
| Format | out | the diagnostic's format |
| Args | out | its arguments, with types unqualified as lowering has them |
The first fault in E, in evaluation order, or first a type or a long double constant in E that nanofil does not support. Fails when it is not one the elaborator diagnoses.
| ✓ | ⟹ | F = 'undeclared identifier ~w', A = g | |
| ✓ | bind_ordinaryx, varnone, double, E0, E1 bind_ordinaryqtypex, qualifiedconst, double, E1, E faultbinmod, idx, chr2, E, F, A | ⟹ | F = 'invalid operands to ~w: ~w and ~w', A = mod, double, int |
| ✓ | bind_ordinaryp, varnone, ptrint, E0, E1 bind_ordinaryd, varnone, double, E1, E faultcondidg, idp, idd, E, F, A | ⟹ | F = 'incompatible ?: operands ~w and ~w', A = ptrint, double |
| ✓ | ⟹ | F = '-> on a non-pointer' | |
| ✓ | bind_ordinaryc, varnone, int, E0, E1 bind_ordinaryqtypec, qualifiedconst, int, E1, E faultassign=, idc, idg, E, F, A | ⟹ | F = '~w a read-only lvalue', A = 'assignment to' |
| ✓ | bind_ordinaryf, fnfuncvoid, ptrchar, int, fixed, f, extern, E0, E faultcallidf, chr0, chr1, idg, E, F, A | ⟹ | F = 'too many arguments to ~w', A = f |
| ✓ | bind_ordinaryf, fnfuncvoid, ptrchar, int, fixed, f, extern, E0, E faultcallidf, chr1, idg, E, F, A | ⟹ | F = 'cannot assign ~w to ~w', A = int, ptrchar |
| ✓ | ⟹ | F = 'a ~w value is not a scalar', A = struct'builtin.va_list' | |
| ✓ | ⟹ | F = 'long double is not supported', A = [] |
the first fault of ?: arms A and B, lowered first, then whether they go together (6.5.15p3).
E, tested for truth, cannot be typed or is not a scalar.
why assignment Op cannot store a value of type TB into an object of type TA.
lowering names a call's callee by its symbol when Fn names a function, else as a function pointer.
Args are too few or too many for parameters Ps of callee Sym.
the first argument, in order, that cannot be typed or passed for its parameter. Lowering may refuse a struct or union argument, or an unpromotable variadic one, before it reaches a later one, so those end the search.
lowering looks for members in XS, an lvalue or a struct or union value; it refuses other values.
T is E's value type as lowering has it, unqualified.
the first type name in E that names a type nanofil does not support, or long double constant, and why.