aboutsummaryrefslogtreecommitdiff
path: root/AGENTS/CONVENTIONS/TYPES.md
blob: d13773b587979dec1fea6e1f9b73d2af72cd19f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# Conventions: type system

Part of [Load-bearing conventions](../CONVENTIONS.md).

**Type names travel as `std::string`** (uppercase builtins such as `"INTEGER"` and
`"REAL"`; user-defined names retain their spelling) — never an enum. `TypeSystem`
resolves the string to `TypeInfo{LLVMType, Kind, per-kind payload}`. The parser's
`ParseTypeName` accepts *any* identifier as a type name except `VOID` (rejected
outside a RETURNS clause via its `AllowVoid` flag); other unknown types only fail
(softly) in CodeGen.

**Fixed IR representation per builtin type.** The table lives in
[ARCH/OVERVIEW.md](../ARCH/OVERVIEW.md). Some arithmetic code still infers types
backwards from LLVM widths; preserve the builtin scalar ABI.

**C aliases are structural within the C domain.** `CIdentity` retains the canonical
C base type and pointer levels independently of the alias `Name`. Use `sameType`
for storage compatibility (including BYREF and array arguments); never equate C
types just because their LLVM types or widths match. CScalar/CPointer values are
handled by CTypeGen, not the legacy representation coercions. Cross-domain
assignments, arguments and operators require explicit casts, including numeric literals,
except for marked IMPLICIT class constructors and the documented text bridges.
Plain C char uses native signedness; pseudocode CHAR stays unsigned. Numeric
conversions use source signedness, and native floating formats use numeric_limits.
Allocation sizes still use LLVM sizeof; native C type widths use build-compiler
sizeof. C alias declarations precede casts using their names; the parser tracks
declared type names to distinguish `(Type)operand` from `(expression)`.

**Builtin scalars are strictly typed.** `CodeGen::emitBinaryOp` (ExpressionGen.cc)
rejects arithmetic on BOOLEAN/CHAR, AND/OR on non-BOOLEAN, and comparisons between
different kinds (INTEGER with REAL is the only mixed pair); `emitCoercedExpr` allows
only INTEGER->REAL between the four builtin scalars. REAL->INTEGER therefore errors (with an `INT(...)` hint); `INT` is a compiler builtin (`Builtins.h`, `CodeGen::emitIntBuiltin`) truncating toward zero with a range check. BOOLEAN (i1) and CHAR (i8)
compare unsigned. C scalars keep C semantics.
AND/OR short-circuit (`CodeGen::emitShortCircuit`) unless a user overload applies.

**User-kind typing is nominal, and checked upstream only.** Enum/Record/Pointer
identity is case-insensitive name equality (`P1 = ^INTEGER` and `P2 = ^INTEGER` are
incompatible; same-layout records too). Their LLVM representations are
indistinguishable from builtins, so values of these kinds must NEVER reach
`ArithmeticHandler` or `coerceValueToType` without a prior name-level check —
`CodeGen::emitCoercedExpr` (assignments, BYVAL args, RETURN, IF/WHILE/REPEAT/FOR
operands, file operands, builtin args) and the typed `CodeGen::emitBinaryOp`
dispatcher in `ExpressionGen.cc` guard these paths. The dispatcher resolves overloads
and handles C/user kinds before builtin arithmetic. `coerceValueToType` hard-errors
("Internal: ...") if a user-kind target reaches it.

**Enum value names are a flat, globally reserved namespace** owned by `TypeSystem`
(not `Symbols`, so they survive FunctionGen's save/clear/restore). Expression
resolution order is `Symbols` first, then enum constants; collisions with
DECLARE/array/parameter/function names are compile errors at declaration time, so
the order can never shadow. TYPE declarations are top-level only and registered in
two passes; see [SUBSYSTEMS/USER_TYPES.md](../SUBSYSTEMS/USER_TYPES.md#registration).