# 02 - 类型系统 Viper 的类型系统通过 `t` 模块提供,所有类型注解继承 `t.CType` 。大都以 `t.C{TypeName:PascalCase}`为类型,类型注解是 Viper 的核心机制,决定了变量的内存布局、大小和对齐方式。 > **书写规范(重要)**:`t.C*` / `c.*` 正在被**逐步淘汰**。新代码推荐使用短名别名(`int`/`uchar`/`char`/`uint64`/`bool` …)、`T*` 指针、`&`/`*` 指针操作、前缀描述符 `const`/`volatile`/`__attribute__(...)`、前缀数组 `[N]T`,以及 `enum`/`union`/`renum`/`define`/`typedef`/`export`/`extern`/`virtual` 等软关键字。旧写法在过渡期仍可用,但不再推荐。规范原文见仓库根目录 `新规范`。 > **三端承载差异(同一套注解,三种实现)**: > - **TPC**(CPython 实现的种子编译器):类型是**继承 `t.CType` 的类**,命名形如 `t.C{TypeName:PascalCase}`(如 `t.CInt`、`t.CUInt64T`),类型注解即这些类的实例/注解组合。 > - **VPC**(Viper 自举实现的现行编译器):这些类型是**编译器内建**,直接映射到 LLVM 类型,不依赖 `t.CType` 继承链。 > - **Vipeye**(基于 pyright 的 VSCode 分析插件):通过 `t.vpi` 与 `c.vpi` 两个存根**统一解析**类型,从而对源码给出与编译期一致的类型信息。 > > 因此源码层面短名别名与 `t.C*` 写法等价,具体承载方式由各端自行决定。 ## 类型书写规范(新语法,推荐) ### 类型别名(裸名,等价 `t.C*`) | 新写法(推荐) | 等价旧写法 | LLVM 类型 | |---------------|-----------|-----------| | `int` | `t.CInt` | `i32` | | `uint` | `t.CUnsignedInt` | `i32` | | `short` / `ushort` | `t.CShort` / `t.CUnsignedShort` | `i16` | | `long` / `ulong` | `t.CLong` / `t.CUnsignedLong` | `i32`(Windows LLP64)/ `i64`(LP64) | | `longlong` / `ulonglong` | `t.CLongLong` / `t.CUnsignedLongLong` | `i64` | | `char` | `t.CChar` | `i8` | | `uchar` | `t.CUnsignedChar` | `i8` | | `bool` | `t.CBool` | `i1` | | `float` / `double` | `t.CFloat` / `t.CDouble` | `f32` / `f64` | | `str` / `bytes` | (等价 `t.CPtr`) | `i8*`(`str*` = `i8**`) | | `void` | `t.CVoid` | `void` | | `sizet` / `size_t` | `t.CSizeT` | `i64` | | `int8` / `int16` / `int32` / `int64` | `t.CInt8T` … `t.CInt64T` | `i8` / `i16` / `i32` / `i64` | | `uint8` / `uint16` / `uint32` / `uint64` | `t.CUInt8T` … `t.CUInt64T` | `i8` / `i16` / `i32` / `i64` | | `i8`…`i64` / `u8`…`u64` | 同上(`stdint.vp` 声明) | 同上 | | `int8_t`…`uint64_t`、`ssize_t`、`intptr_t`、`uintptr_t`、`ptrdiff_t` | 同上 | 对应宽度(多为 `i64`) | | `rawptr[T]` ≡ `T*` | `T \| t.CPtr` | `T*` | | `callable` / `Callable` | `t.Callable` | `i8*` | 别名来源:[includes/builtins.vp](../includes/builtins.vp)(C 基础类型裸名)、[includes/stdint.vp](../includes/stdint.vp)(定长与 `*_t`);`int`/`char`/`bool`/`float`/`double`/`str`/`bytes`/`void` 为编译器内置直通。 > 尚未实现 / 不可用:`schar`;`wchar_t`、注解位的 `f32`/`f64`、`char8_t`/`char16_t`/`char32_t`、`intfast*`/`intleast*`(声明存在但 VPC 类型映射缺失)。 ### 指针:统一写 `T*` `T | t.CPtr` 一律改写为 `T*`(层级 = 星号个数),四种写法在类型域恒等价且恒 +1 层: ```python p: int* # int* ≡ t.CInt | t.CPtr pp: int** # int** ≡ t.CInt | t.CPtr | t.CPtr s: char* # char* ≡ t.CChar | t.CPtr v: void* # void* = i8*(通配指针) ``` > `&a` 的结果类型明确是 `T*`,**不需要**再写 `t.CPtr` 强转。 ### 指针操作:`&` / `*`(取代 `c.*`) | 旧写法 | 新写法 | |--------|--------| | `c.Addr(x)` | `&x` | | `c.Deref(p)` | `*p` | | `c.DerefAs(p, v)` | `*p = v` | | `c.Load(p, q)` | `*p = *q` | ### 描述符:`const` / `volatile` / `__attribute__(...)` 类型描述符写在类型**前面**,顺序自由、可混写: ```python a: const int # ≡ t.CConst | int(只读) b: const int* # const int* c: volatile uint32 # 访问落 LLVM volatile @__attribute__(packed) # 类 / 结构体 d: __attribute__(aligned(16), section(".bss")) int ``` > `__attribute__(...)` 比 GCC **少一层括号**:写 `__attribute__(x)` 而非 `__attribute__((x))`;实参脱糖为 `t.attr.x`。可作为注解前缀,也可作为 `@__attribute__(...)` 装饰器。 ### 数组:前缀 `[N]T` ```python buf: [64]char # char buf[64] ≡ t.CArray[t.CChar, 64] grid: [8][4]float # float grid[8][4] s: []char = "hi" # 长度推导 ≡ t.CArray[char, None] ``` 三种写法(前缀 `[N]T`、软关键字 `array[E,N]`、`t.CArray[E,N]`)最终都生成 LLVM `[N x E]`。 ### 关于 `|` 注解中的 `|`(类型组合 / 存储类标记)正在**逐步取消**: - `T | t.CPtr` → `T*`; - `t.CInt | t.CExport` → `export def f(...) -> t.CInt`; - `t.CStatic | ...` → 前置 `static ...`; - `t.CConst | T` / `t.CVolatile | T` → 前置描述符 `const T` / `volatile T`。 > 暂未对齐、仍需 `|` 的仅剩少数字面描述符,如 `t.Bit[n]`、大小端标记 `t.BigEndian` / `t.LittleEndian`。 ## 基本类型 ### 整数类型 | Viper 类型 | C 等价 | 大小(位) | 有符号 | |------------|--------|-----------|--------| | `t.CChar` | `char` | 8 | 是 | | `t.CShort` | `short` | 16 | 是 | | `t.CInt` | `int` | 32 | 是 | | `t.CLong` | `long` | 32 / 64 | 是 | | `t.CUnsignedChar` | `unsigned char` | 8 | 否 | | `t.CUnsignedShort` | `unsigned short` | 16 | 否 | | `t.CUnsigned` / `t.CUnsignedInt` | `unsigned int` | 32 | 否 | | `t.CUnsignedLong` | `unsigned long` | 32 / 64 | 否 | | `t.CSignedChar` | `signed char` | 8 | 是 | > **平台相关(`long` / `unsigned long`)**:宽度随目标平台——Windows(LLP64)下为 **32 位**(对齐 `ULONG`/`DWORD`),其余目标(LP64 / 裸机 ELF)为 **64 位**;`long long` / `unsigned long long` 恒为 64 位。 > TPC 由 `configure_platform(triple)` 设置 `_LONG_SIZE` / `_UNSIGNED_LONG_SIZE`(`TransPyC/lib/includes/t.py`),VPC 由 `TypeGenResolve._long_llvm_ty` 按同一口径给出(Windows triple ⇒ `i32`,否则 `i64`)。 值得注意的是,t.CUnsigned 一般不单独使用,而是配合其它有符号类型,替换表示无符号,比如 `t.CUnsigned | t.CInt`,由于为了便于使用一些常用类型,则将其组合为常用的 `t.CUnsignedInt` 等。 ### 固定宽度整数类型 | Viper 类型 | C 等价 | 大小(位) | 有符号 | |------------|--------|-----------|--------| | `t.CInt8T` | `int8_t` | 8 | 是 | | `t.CInt16T` | `int16_t` | 16 | 是 | | `t.CInt32T` | `int32_t` | 32 | 是 | | `t.CInt64T` | `int64_t` | 64 | 是 | | `t.CUInt8T` | `uint8_t` | 8 | 否 | | `t.CUInt16T` | `uint16_t` | 16 | 否 | | `t.CUInt32T` | `uint32_t` | 32 | 否 | | `t.CUInt64T` | `uint64_t` | 64 | 否 | ### 最小宽度整数类型 | Viper 类型 | C 等价 | 大小(位) | 有符号 | |------------|--------|-----------|--------| | `t.CIntLeast8T` | `int_least8_t` | ≥8 | 是 | | `t.CIntLeast16T` | `int_least16_t` | ≥16 | 是 | | `t.CIntLeast32T` | `int_least32_t` | ≥32 | 是 | | `t.CIntLeast64T` | `int_least64_t` | ≥64 | 是 | | `t.CUIntLeast8T` | `uint_least8_t` | ≥8 | 否 | | `t.CUIntLeast16T` | `uint_least16_t` | ≥16 | 否 | | `t.CUIntLeast32T` | `uint_least32_t` | ≥32 | 否 | | `t.CUIntLeast64T` | `uint_least64_t` | ≥64 | 否 | ### 最快最小宽度整数类型 | Viper 类型 | C 等价 | 大小(位) | 有符号 | |------------|--------|-----------|--------| | `t.CIntFast8T` | `int_fast8_t` | ≥8 | 是 | | `t.CIntFast16T` | `int_fast16_t` | ≥16 | 是 | | `t.CIntFast32T` | `int_fast32_t` | ≥32 | 是 | | `t.CIntFast64T` | `int_fast64_t` | ≥64 | 是 | | `t.CUIntFast8T` | `uint_fast8_t` | ≥8 | 否 | | `t.CUIntFast16T` | `uint_fast16_t` | ≥16 | 否 | | `t.CUIntFast32T` | `uint_fast32_t` | ≥32 | 否 | | `t.CUIntFast64T` | `uint_fast64_t` | ≥64 | 否 | ### 最大宽度整数类型 | Viper 类型 | C 等价 | 大小(位) | 有符号 | |------------|--------|-----------|--------| | `t.CIntMaxT` | `intmax_t` | ≥64 | 是 | | `t.CUIntMaxT` | `uintmax_t` | ≥64 | 否 | ### stdint 短名别名 通过 `import stdint` 引入,这些短名是上述类型的 `t.CTypedef` 别名,便于在系统编程场景中快速使用。允许直接使用 `from stdint import *` 来直接使用。 #### 基本类型短名 | 短名 | 等价展开 | 说明 | |------|---------|------| | `stdint.INT` | `t.CInt` | - | | `stdint.UINT` | `t.CUnsignedInt` | - | | `stdint.BOOL` | `t.CInt` | 1 为真,0 为假 | | `stdint.SHORT` | `t.CShort` | - | | `stdint.USHORT` | `t.CUnsignedShort` | - | | `stdint.LONG` | `t.CLong` | - | | `stdint.ULONG` | `t.CUnsignedLong` | - | | `stdint.LONGLONG` | `t.CLongLong` | - | | `stdint.ULONGLONG` | `t.CUnsignedLongLong` | - | | `stdint.FLOAT` | `t.CFloat` | - | | `stdint.DOUBLE` | `t.CDouble` | - | #### 固定宽度短名 | 短名 | 等价展开 | |------|---------| | `stdint.INT8` | `t.CInt8T` | | `stdint.INT16` | `t.CInt16T` | | `stdint.INT32` | `t.CInt32T` | | `stdint.INT64` | `t.CInt64T` | | `stdint.UINT8` | `t.CUInt8T` | | `stdint.UINT16` | `t.CUInt16T` | | `stdint.UINT32` | `t.CUInt32T` | | `stdint.UINT64` | `t.CUInt64T` | #### 字节与字短名 | 短名 | 等价展开 | 说明 | |------|---------|------| | `stdint.BYTE` | `t.CUnsignedChar` | 8 位无符号 | | `stdint.WORD` | `t.CUInt16T` | 16 位无符号 | | `stdint.DWORD` | `t.CUInt32T` | 32 位无符号 | | `stdint.QWORD` | `t.CUInt64T` | 64 位无符号 | #### 字符类型短名 | 短名 | 等价展开 | 说明 | |------|---------|------| | `stdint.TCHAR` | `t.CChar` | - | | `stdint.CHARLIST` | `str \| t.CPtr` | - | | `stdint.WCHAR` | `stdint.WORD` | UTF-16 代码单元 | | `stdint.CHAR8` | `t.CChar8T` | - | | `stdint.CHAR16` | `t.CChar16T` | - | | `stdint.CHAR32` | `t.CChar32T` | - | #### 指针短名 | 短名 | 等价展开 | |------|---------| | `stdint.INTPTR` | `t.CInt \| t.CPtr` | | `stdint.UINTPTR` | `t.CUnsignedInt \| t.CPtr` | | `stdint.BYTEPTR` | `stdint.BYTE \| t.CPtr` | | `stdint.SHORTPTR` | `t.CShort \| t.CPtr` | | `stdint.USHORTPTR` | `t.CUnsignedShort \| t.CPtr` | | `stdint.WCHARPTR` | `stdint.WORD \| t.CPtr` | | `stdint.VOIDPTR` | `t.CVoid \| t.CPtr` | | `stdint.INT8PTR` | `t.CInt8T \| t.CPtr` | | `stdint.INT16PTR` | `t.CInt16T \| t.CPtr` | | `stdint.INT32PTR` | `t.CInt32T \| t.CPtr` | | `stdint.INT64PTR` | `t.CInt64T \| t.CPtr` | | `stdint.UINT8PTR` | `t.CUInt8T \| t.CPtr` | | `stdint.UINT16PTR` | `t.CUInt16T \| t.CPtr` | | `stdint.UINT32PTR` | `t.CUInt32T \| t.CPtr` | | `stdint.UINT64PTR` | `t.CUInt64T \| t.CPtr` | | `stdint.CHAR8PTR` | `t.CChar8T \| t.CPtr` | | `stdint.CHAR16PTR` | `t.CChar16T \| t.CPtr` | | `stdint.CHAR32PTR` | `t.CChar32T \| t.CPtr` | #### 平台特定短名 | 短名 | 等价展开 | 说明 | |------|---------|------| | `stdint.FSIZE_t` | `stdint.DWORD` | 文件大小 | | `stdint.LBA_t` | `stdint.DWORD` | 逻辑块地址 | ### 平台相关类型 | Viper 类型 | C 等价 | 大小(位) | 有符号 | |------------|--------|-----------|--------| | `t.CSizeT` | `size_t` | 64 | 否 | | `t.CIntPtrT` | `intptr_t` | 64 | 是 | | `t.CUIntPtrT` | `uintptr_t` | 64 | 否 | | `t.CPtrDiffT` | `ptrdiff_t` | 64 | 是 | ### 浮点类型 | Viper 类型 | C 等价 | 大小(位) | |------------|--------|-----------| | `t.CFloat` | `float` | 32 | | `t.CDouble` | `double` | 64 | ### 其他基本类型 | Viper 类型 | C 等价 | 说明 | |------------|--------|------| | `t.CVoid` | `void` | 空类型 | | `t.CBool` | `bool` | 布尔类型(LLVM i1,1 位;C _Bool 存储 1 字节) | | `t.CWCharT` | `wchar_t` | 宽字符(32位) | | `t.CChar8T` | `char8_t` | UTF-8 字符(8位,无符号) | | `t.CChar16T` | `char16_t` | UTF-16 字符(16位,无符号) | | `t.CChar32T` | `char32_t` | UTF-32 字符(32位,无符号) | > **`void` 关键字别名**:裸名 `void` 是 `t.CVoid` 的别名(无需 `import t`),二者都解析为 LLVM `void`;`void*`(`void | t.CPtr`)归并为 `i8*`。`void` 仅允许出现在**返回类型**、`void*`(指针的 pointee)、以及 `typedef 别名 = void` 三处;出现在**形参、字段、变量声明**或**被当作值使用**时一律编译期 fail-fast(`TypeError`)。 ### Python 便捷映射类型 | Python 类型| Viper 类型 | Viper 值 | |--------|--------|------| |`int`|`t.CInt`|-| |`float`|`t.CFloat`|-| |`str`|`i8*`(裸 `str`;`str \| t.CPtr` 才是 `i8**`)|-| |`bytes`|`i8*`(与裸 `str` 同义)|-| |`bool`|`t.CBool`|-| |`True`|`t.CBool`|`1`| |`False`|`t.CBool`|`0`| |`None`|`NULL`|`i8* NULL 常量(语义 NULL/零值)`| > 注:`True`/`False` 裸字面量在 LLVM 层为 i32 的 1/0;赋给 `bool`/`t.CBool`(i1)注解变量时,经比较归一化为 i1。 对于 t.py 和 c.py 的引用,支持 from ... import ... 与 as 别名(编译器自身大量依赖)。 ## 类型组合 Viper 使用 Python 的 `|` 来组合类型修饰符(TypeUnion):`|` 在类型/注解上下文(参数、返回、字段、typedef、强转表达式)中表示组合,在值表达式上下文中为位或。这是 Viper 最重要的语法特性之一。 > **注意**:`|` 作为类型组合正在被**逐步取消**,新代码请改用短名别名、`T*`、前缀描述符与软关键字(见上方「类型书写规范」)。本节保留 `|` 写法作为**过渡期参考**。 > **规划演进(新语法方向)**: > - `t.CConst | t.CInt | t.CPtr` 这类链式组合过于冗长,方向是改为贴近 C 的写法(过渡期链式写法仍可用): > - `a: const rawptr[int] = xxx`(`rawptr` 是编译器内置保留字,裸指针;规范名即 **rawptr**) > - 或 `a: const int* = xxx`(C 风格指针 / const 前缀类型注解) > - **指针写法已统一(2026-09-30 定)**:`T*` ≡ `rawptr[T]` ≡ `t.CPtr[T]` ≡ `T | t.CPtr`,四种写法在类型域恒等价,且**恒追加恰好 1 层间接**。详见下文「指针类型」。 > - `T1 | T2` 的计划是**在基本类型间完全禁用**,`|` 转回其本意 **TypeUnion(类型联合)**。 > - 计划引入 `T1 & T2` = **TypeIntersection(类型交集)**,主要面向 `object`,尤其是带 vtable 的 `object`。 > - 基本类型联合(如 `a: int | str`)是否支持仍需考虑,目前只期望未来先针对 `object` 落实联合/交集。 ### 指针类型 指针有四种等价写法(2026-09-30 起统一口径),**在类型域恒等价,且恒追加恰好 1 层间接**: | 写法 | 形态 | 含义 | |------|------|------| | `T \| t.CPtr` | 链式组合(经典写法) | `T*` | | `T*` | C 风格后缀(`T*` 文本反解析为 `rawptr[...]`) | `T*` | | `rawptr[T]` | 内建裸指针下标形 | `T*` | | `t.CPtr[T]` | `t` 模块指针下标形 | `T*` | > **关键规则:四种写法一律只加 1 层。** `rawptr[T]` / `t.CPtr[T]` 里的 `T` 是**被指类型(pointee)本身**,不是「指向 T 的指针」。所以 `t.CPtr[t.CPtr]` = `i8**`、`t.CPtr[int]` = `i32*`;要更高层就显式嵌套(`t.CPtr[t.CPtr[int]]` = `i32**`)。 ```python x: int* # 指向 int 的指针 p: void* # 通用指针(归并为 i8*) s: char* # 字符串指针 pp: int** # 指向指针的指针 # 以下四行彼此等价(都是 int*) a: int* b: rawptr[int] c: t.CPtr[int] d: int | t.CPtr # 旧链式写法 ``` 等价 C 代码: ```c int* x; void* p; char* s; int** pp; ``` 补充约定: - 裸 `rawptr` / 裸 `t.CPtr`(无下标)本身 = `void*` = `i8*`。 - `void*`(`t.CVoid | t.CPtr` 或 `void | t.CPtr`)= `i8*`,是**通配指针**:与任意 `T*` 双向隐式可转(单级)。 - `rawptr` 是**编译器内置语法保留字**(TPC/VPC 双端),用户对它做 `typedef` 会被忽略;`t.CPtr` 是 `t` 模块提供的类型名,二者类型语义等价。 - 复用 Python 高亮插件时,`T | t.CPtr` 通常比 `t.CPtr[T]` 更易被识别为属性访问。 - 裸类名恒为**原型 struct**(值/布局语义);堆对象引用必须显式写指针(`T*` / `T | t.CPtr` / `rawptr[T]`)。 ### 指针判空写法(硬约定) **指针**(`T | t.CPtr` / `T*`)判空**一律**用身份比较: ```python if p is None: ... # ✅ → icmp eq , null if p is not None: ... # ✅ → icmp ne , null if p == 0: ... # ❌ 不用:虽同样编译为 icmp eq , null,但语义要绕"指针 vs 整型"特例分支 ``` **以整型承载地址/句柄的字段判空**:整型上的 `None` **隐式表示 0**,两侧编译器都按此判定: ```python x is not None # ≡ x != 0 x != None # ≡ x != 0 x is None # ≡ x == 0 x == None # ≡ x == 0 ``` 即:这类判空既可写 `!= 0` / `== 0`,也可写 `is not None` / `is None`(以及 `!= None` / `== None`),**两侧同效**。 - **TPC**:`TransPyC/lib/core/Handles/HandlesExprOps.py:400-420` —— 一侧是 `IntType`、另一侧是空指针常量(`None`)时, 把 `None` 侧换成该整型的常量 0 再发 `icmp eq`,`is not` / `!=` 取反。该分支刻意排在「指针 vs 整型」特例之前, 否则 `i8` 会命中 `Gen._load(null)`(从空指针 load)。 - **VPC**:`ViperC/App/lib/core/Gen/Expr/ExprGenOps.vp:649-694` —— 同样把 `None` 侧物化为整数 0 后走 `icmp eq/ne` (`None is x` 与 `x is None` 两个方向、`is` 与 `==` 都覆盖)。 > 历史:早期 TPC 把「整型 vs None」直接折成常量(`is not None` → `i1 1`、`is None` → `i1 0`), > 于是「整型守卫」恒真/恒假、判不出空;而 VPC 已按 0 比较 ⇒ 两侧不一致。现已统一为「整数上的 None = 0」。 > 修复前的现场与逐字 IR 证据见 `Debugger/rawptr-migration-and-fastfail.md` §36。 #### 双侧 IR 证据(判据写法 → 生成码) | 写法 | 静态类型 | 生成 IR | 结论 | |---|---|---|---| | `p is None` / `p == 0` | 指针 | `icmp eq , null` | 有效(命名 `is_cmp` / `cmpresult`) | | `x is not None` / `x != None` / `x != 0` | 整型 | `icmp ne %x, 0` | 有效(两侧同效) | | `x is None` / `x == None` / `x == 0` | 整型 | `icmp eq %x, 0` | 有效(两侧同效) | 逐字实证(**语义统一之前**的历史现场):`x = elem_ptr.Ty.Pointee`(`LLVMType*` 的 REnum 载荷槽,偏移 8、`load i64`)时, `ViperC/output/818a0677d1e7fe22.ll:17979-17991` 中指针侧有 `icmp`,整型侧却只有 `%"and.result.4" = phi i1 [0, %"endif.19"], [1, %"and.rhs0.4"]`(`and.rhs0.4` 块内 `load i64` 后直接 `br`) ——那是 TPC 把「整型 vs None」折成常量的结果;现已统一为整数 0 比较,该现场不再出现。 #### 读"对象首字"(vptr)的唯一可用写法 `virtual` 类对象的 **vptr 在偏移 0**(IR:`getelementptr %T, %T* %p, i32 0, i32 0` + `load i8*`), `kind()` 等是**虚调用**(vptr 为 0 ⇒ `load [0 + slot]` ⇒ ASan `READ at 0x0`)。 因此"对象有效性校验"必须能读到首字: ```python v: uint64 = *(uint64*)p # ✅ bitcast p → i64* + load i64(旧写法 c.Deref((uint64*)(p))) if v == 0: ... # 地址值比较,有效 v2: rawptr = rawptr(*(uint64*)p) # ❌ 被折成裸 0(inttoptr i32 0 to i8*),读不到 vptr *ptr = # ❌ 目标 LHS 是结构体类型的解引用时**可能一条指令都不发射**(静默丢失) ``` (IR 实证:`ViperC/output/b5883dc9c114a604.ll:10382-10384` 对比 `:10611-10615`。) ### 类型限定符组合 > 规划:本节 `t.CConst | T`/`t.CConst | T | t.CPtr` 链式写法将随上述演进被 `const rawptr[int]`/`const int*` 取代(当前仍可用)。 ```python x: const int # const int p: const char* # const char*(旧写法:t.CConst | t.CChar | t.CPtr) ``` ### 存储类组合 存储类修饰可用**前置软关键字**(推荐)或链式标记: ```python static x: t.CInt = 0 # 等价于 x: t.CStatic | t.CInt → static int x = 0; extern errno: t.CInt # 等价于 errno: t.CExtern | t.CInt → extern int errno; ``` - `static`:符号**仅限定义模块内可见**,跨模块取值 / 调用 / `from ... import` 一律编译期 `NameError` fail-fast(定义侧登记 + 引用侧三处拦截)。 - `extern`:模块级变量生成 `@裸名 = external global T`(external linkage、无初始值、无 storage),符号由外部(C 库 / 汇编 / 其它翻译单元)提供。 - 旧写法 `名字: static 类型`(把 `static` 写在注解位置)**已不再支持**,会明确报错,请改为前置形式 `static 名字: 类型`。 > 存储类标记(`t.CStatic` / `t.CExtern` / `t.CExport` / `t.State` / `t.CInline` 等)在联合类型提取类名时会被跳过,它们不是真实类型。 ### 函数返回类型组合 新写法用软关键字;旧写法链式标记仍可用: ```python export def foo() -> int: # 导出函数,返回 int return 0 extern def bar() -> void: # 外部函数声明 pass ``` ### `t.State` 声明性标记 `t.State` 是**存储类标记**(不是真实类型),承载 `extern` 软关键字的语义:`extern def f(...) -> T` 会被解析器改写为返回注解 `T | t.State`;它同样是 `extern NAME: T` 全局变量的 external 标记,以及 `t.CEnum` 成员的默认类型(`i32`)。函数体为 `pass` 时,编译器只生成函数原型(declare),不生成定义(define),由链接器在外部库中解析符号。 ```python extern def CreateFileA(lpFileName: LPCSTR, dwDesiredAccess: ULONG, dwShareMode: ULONG, lpSecurityAttributes: SECURITY_ATTRIBUTES*, dwCreationDisposition: ULONG, dwFlagsAndAttributes: ULONG, hTemplateFile: HANDLE) -> HANDLE: pass ``` 等价 C 声明: ```c HANDLE CreateFileA(LPCSTR, ULONG, ULONG, SECURITY_ATTRIBUTES*, ULONG, ULONG, HANDLE); ``` 日常书写推荐用 `extern def` 软关键字(内部即改写为 `t.State` 标记),无需手写标记即可声明外部函数。详见 [08-c-operations.md 的 FFI 章节](08-c-operations.md#ffi-外部函数声明) 和 [includes/w32](../includes/w32) 的 Win32 绑定。 ## 数组类型 固定大小数组**推荐用前缀写法 `[N]T`**(等价 `t.CArray[T, N]` = `rawarray`,软关键字形态 `array[T, N]`)(通常用于栈上和 BSS 段上)。`N` 可为模块级 `define` 常量或常量表达式(如 `2*MAX`);省略 `N`(`[]T`)表示静态推导长度数组(退化为 `T*`);也支持多维嵌套。 - 裸用 `array`(无下标)、缺参 / 多参、长度非正整数常量都会 fail-fast。 - **`list[T, N]` 与定长数组绝不相同**:`list` 是 [includes/_list.vp](../includes/_list.vp) 定义的**堆上 STL 容器**泛型类(`__new__` / `__init__` 经 memhub 池分配,`__capacity__` 自 8 起翻倍扩容),`list[T]` 与 `list[T, N]` 都是这一容器;而 `t.CArray[T, N]` / `[N]T` / `array[T, N]` 三者同义,是**栈上定长数组**(LLVM `[N x T]`,通常位于栈或 BSS 段)。两者语义与内存布局都不同,不可互换。 ```python buf: [64]char # char buf[64] ids: [10]int # int ids[10] matrix: [16]float # float matrix[16] entries: [256]idt_entry # struct idt_entry entries[256]; s: []char = [0x68, 0x69] # char s[] = {0x68, 0x69}; 由列表字面量推断长度 2 grid: [8][4]float # float grid[8][4]; 多维嵌套 seg: [BUF_SIZE]char # char seg[BUF_SIZE]; N 可为模块级 define 常量 ``` > 旧写法 `t.CArray[t.CChar, 64]` / `array[char, 64]` 与 `[64]char` 完全等价,手写可用但不推荐。 等价 C 代码: ```c char buf[64]; int ids[10]; float matrix[16]; struct idt_entry entries[256]; ``` ### 数组指针与函数指针 ```python lp: [12]int* # int (*lp)[12] — 数组指针 vp: callable[[int, int], int] # 函数指针(带签名) ``` **函数指针(callable)**:以下写法在类型域全部等价,统一生成 **`i8*`**(函数值按 VPC 惯例 bitcast 为 `i8*` 存储 / 传递): | 写法 | 说明 | |------|------| | `t.Callable` / `c.Callable` / `Callable` | 无签名(裸名) | | `callable` / `Callable`(裸名下标) | 无签名下标形:`callable[]` / `callable[[]]` | | `t.Callable[[A, B], R]` / `callable[[A, B], R]` | 带签名;仅额外登记返回类型 `R`,LLVM 类型仍是 `i8*` | 带签名形态会登记返回类型供间接调用使用;声明了函数指针变量却缺 `t.Callable` 注解时,间接调用会 fail-fast。 ## 结构体类型 使用 `class` 定义结构体,成员通过类型注解声明(**纯数据**推荐用软关键字 `struct`,见 [05-classes.md 的 struct 章节](05-classes.md#struct纯数据结构体)): ```python class Point: x: int y: int ``` 等价 C 代码: ```c struct point { int x; int y; }; ``` ### 带指针成员的结构体 ```python class Node: value: int next: Node* # struct Node* next ``` ### 字节序标记 > WARNING: **警告:** 此关键字是实验性的。未经充分测试,不当使用可能导致未定义行为。 使用 `t.BigEndian` 或 `t.LittleEndian` 标记成员的字节序(**仍需 `|`**): ```python class NetworkPacket: length: uint16 | t.BigEndian # 大端序存储 type: uint8 # 默认小端序 ``` ## 联合体类型 用软关键字 `union`(等价 `class X(t.CUnion):`)定义联合体: ```python union Value: ival: int fval: float pval: void* ``` 等价 C 代码: ```c union value { int ival; float fval; void* pval; }; ``` ## 枚举类型 用软关键字 `enum`(等价 `class X(t.CEnum):`)定义枚举: ```python enum Color: RED, GREEN, BLUE # 裸名,自动编号 0,1,2 ``` 等价 C 代码: ```c enum Color { RED = 0, GREEN = 1, BLUE = 2 }; ``` 枚举成员可直接使用名称: ```python c: int = Color.RED ``` ### REnum(标签联合体) `t.CEnum` 是普通枚举(整数常量集合),用软关键字 `renum` 定义的是**标签联合体**(tagged union / Rust enum 风格)。每个变体可携带不同的 payload,通过 `match` 模式匹配分派。适用于少变体、无通用字段、match 分派的场景(如类型系统、IR 表示)。 ```python renum LLVMType: class Int: Bits: int class Void: pass match ty: case LLVMType.Int: snprintf(buf, size, "i%d", ty.Bits) case LLVMType.Void: string.strcpy(buf, "void") ``` REnum 的详细用法、与 CEnum/胖节点 AST 的对比见 [05-classes.md 的 REnum 章节](05-classes.md#renum标签联合体)。 ## Typedef 用软关键字 `typedef`(等价 `Name: t.CTypedef = ...`)创建类型别名: ```python typedef IrqHandler = callable[[], int] # typedef int (*IrqHandler)(); typedef MyStruct = OriginalStruct ``` 等价旧写法: ```python irq_handler_t: t.CTypedef | t.Callable[[], int] MyStruct2: t.CTypedef = original_struct ``` > `typedef T1 = T2` 与 `T1: t.CTypedef = T2` 完全等价。 ## 泛型类型(PEP 695 语法) Viper 支持 PEP 695 泛型语法 `class Name[T]:`,定义类型参数化的类。类型参数 `T` 在类体内可作为占位类型使用,实例化时用 `Name[ConcreteType]` 特化。 ```python novirtual class GSList[T]: Head: T* Tail: T* Count: size_t lst: GSList[GNode]* = &GSList[GNode]() ``` 泛型类还支持**递归泛型继承**(如 `class GNode(GSListNode[GNode])`),实现强类型自引用结构,无需 `void*` + 转型。这是 C/C++/Rust **当然可以表达,但是无法这样简洁**的能力。 泛型类的完整用法、递归继承、实例化示例见 [05-classes.md 的泛型类章节](05-classes.md#泛型类pep-695-语法)。 ## 隐式类型转换 右值赋给左值、传参、返回、二元运算对齐时,编译器按以下规则做隐式转换(统一入口 `coerce_to_type`);不满足则编译期 fail-fast(`TypeError` / `CastError`)。 | 源 → 目标 | 规则 | |-----------|------| | `None` → 任意类型 | 全放行:指针目标 bitcast NULL;非指针目标物化零值 | | `void` → `void` | 放行(无值传递);`void` → 非 void 一律 fail-fast | | 函数值 → 指针 | bitcast(传函数指针) | | 整数 → 指针 | **禁止**;唯一豁免编译期常量 `0`(空指针) | | 指针 → 整数 | **禁止**(须显式 `uint64(p)` 等) | | 整数 ↔ 整数 | 同宽原样;拓宽 `zext`/`sext`;**目标 `i1` 真值归一化 `x != 0`**(不是截断取最低位);窄化 `trunc`(常量溢出 fail-fast) | | `float` ↔ `float` | `fpext` / `fptrunc` | | 整数 ↔ 浮点 | `si2fp` / `fp2si`(整数→浮点需浮点宽度 ≥ 整数宽度) | | 浮点 ↔ 指针 | **禁止** | | 聚合 ↔ 指针 | **禁止**;唯一豁免**构造物化**(右值来自该类型构造调用,如 `p: Point = Point()`) | | 指针 ↔ 指针 | 见下(级数 + pointee 兼容) | **指针 → 指针**: 1. 通用指针(`t.CPtr` / `i8*`)与任意 `T*` 双向互转都放行; 2. `T* → U*`(两侧都非通用指针)允许 ⇔ **级数相同**且 pointee `T → U` 无损;**非基本类型**(结构体)只允许**上转**(源类派生自目标类),不相关结构体、向下转型、结构体 ↔ 基本类型一律禁止; 3. 非基本类型值与其指针之间一律禁止(构造物化除外); 4. 完全相同的类型不算转换。 **数组退让**:源被指类型是定长数组 `[N]T`、目标被指类型恰为元素类型 `T` 时,允许单向隐式转换(数组指针 → 元素指针,`[N]T* → T*`,C/C++ 同口径)。 > 显式强转可以绕过上述限制(见下方「强制类型转换」)。 ## 强制类型转换 Viper 的强制类型转换基于 `t.CType` 的用法,符合 Python 本意。继承自 `t.CType` 的类型可以直接用 call 转换,写法为 `T(x, ...)`: ```python x: char* = "Hello" p: void* = t.CVoid(x, t.CPtr) # (void*)x(旧链式写法,仍可用) ``` 新写法:指针类型直接写 `T*`,因此多数强转可以省略;确需强转时用 `T(x)`: ```python typedef VoidPtr = void* y: VoidPtr = VoidPtr(x) q: void* = x # void* ↔ 任意 T* 双向隐式可转,无需强转 ``` 对于比较、四则运算,可以不使用强制类型转换而依靠隐式类型转换;但严谨数学计算推荐显式强转,避免判断出错。此外,若将一个不同类型的右值赋值到左值,则会隐式将右值转换为左值的类型。 ### `T*(x)` 指针强转 C 风格强转可直接写 `T*(x)`——等价 `(T*)(x)`、`(T*)x`、`(T | t.CPtr)(x)`(四者产出**同一 AST、同一 IR 路径**): ```python node: Node* = Node*(child) p: int* = int*(raw) data: uint8* = uint8*(s) ``` **歧义与约束**(`a * (b)` 是合法乘法,所以判定必须是纯语法的): 1. `T` 必须是**类型原子**(Name / Attribute / Subscript); 2. `T` 与 `*` 串、`*` 串与 `(` 之间**必须紧贴**(无空白 / 无换行)。 ⇒ `T*(x)` 中的 `T` 必须是**类型名**(class 原型、typedef、`uint8` 等类型原子);`T` 若解析不出类型,会在类型解析阶段 fail-fast。而 `a * (b)`、`n * (m - 1)`、`(i + 1) * (j)`(有空白,或前驱不是类型原子)仍按**乘法**解析。 生成的 IR 与其它强转同路径:指针↔指针 `bitcast`、整数→指针 `inttoptr`、指针→整数 `ptrtoint`、整数↔整数 `zext/sext/trunc`。 ## 宏定义常量 使用 `t.CDefine` 定义编译时常量(新写法用软关键字 `define X = v` / `define X: T = v`)。`t.CDefine` 不是数据类型,而是编译时元指令——标记为 `t.CDefine` 的常量在 LLVM IR 中不生成任何全局变量,而是在引用处直接内联展开。详见 [README.MD 中 t.CDefine 深度解析](README.MD#tcdefine-深度解析)。 ```python define MAX_SIZE = 1024 # 推荐:等价 MAX_SIZE: t.CDefine = 1024 define PAGE_SIZE: uint32 = 4096 # 带显式底层类型 FA_READ: t.CDefine = 0x01 # 旧写法等价 ``` 语义上等价于 C 的 `#define`: ```c #define MAX_SIZE 1024 #define PAGE_SIZE 4096 #define FA_READ 0x01 ``` 宏定义支持表达式: ```python define CPU_FEATURE_FPU = (1 << 0) define CPU_FEATURE_APIC = (1 << 6) ``` 宏定义也可与类型组合使用(新写法用 `define X: T = v`): ```python define CODE_SEG: uint16 = 0x08 # 等价 CODE_SEG: t.CDefine | t.CUInt16T = 0x08 ``` ## 位域 使用 `t.Bit(n)` 定义位域成员(**仍需 `|`**): ```python class Flags: a: uint32 | t.Bit[1] # 此处,如果存入 2,二进制是 10,会被截断为 0 b: uint32 | t.Bit[3] c: uint32 | t.Bit[4] ``` ## LLVM IR 类型简写 Viper 提供了 LLVM IR 级别的类型简写: ```python i1 # 1位整数 i8 # 8位有符号整数 i16 # 16位有符号整数 i32 # 32位有符号整数 i64 # 64位有符号整数 u1 # 1位无符号整数 u8 # 8位无符号整数 u16 # 16位无符号整数 u32 # 32位无符号整数 u64 # 64位无符号整数 ``` 仍然通过 `t.x` 的方式调用,比如 `t.i1` 等。 ## `__attribute__` 属性 通过 `t.attr` 命名空间访问 GCC `__attribute__` 属性: ```python t.attr.packed() # __attribute__((packed)) t.attr.aligned(16) # __attribute__((aligned(16))) t.attr.section(".text.startup") # __attribute__((section(".text.startup"))) t.attr.always_inline() # __attribute__((always_inline)) t.attr.noinline() # __attribute__((noinline)) t.attr.noreturn() # __attribute__((noreturn)) t.attr.weak() # __attribute__((weak)) t.attr.unused() # __attribute__((unused)) t.attr.constructor() # __attribute__((constructor)) t.attr.destructor() # __attribute__((destructor)) t.attr.deprecated("use X") # __attribute__((deprecated("use X"))) ``` ### LLVM 函数属性 通过 `t.attr.llvm` 命名空间指定 LLVM 函数属性: ```python t.attr.llvm.nobuiltin t.attr.llvm.nounwind t.attr.llvm.noredzone t.attr.llvm.willreturn t.attr.llvm.mustprogress t.attr.llvm.optnone t.attr.llvm.noinline t.attr.llvm.alwaysinline t.attr.llvm.readnone t.attr.llvm.readonly t.attr.llvm.writeonly t.attr.llvm.inaccessiblememonly t.attr.llvm.inaccessiblemem_or_argmemonly ``` 在函数返回类型注解中使用: ```python def foo() -> int | t.attr.llvm.nobuiltin | t.attr.llvm.nounwind: return 0 ```