Files
Viper_Wiki/02-type-system.md
2026-10-13 15:36:42 +08:00

34 KiB
Raw Permalink Blame History

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(C 基础类型裸名)、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 层:

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__(...)

类型描述符写在类型前面,顺序自由、可混写:

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

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**)。

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 代码:

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*)判空一律用身份比较:

if p is None: ...        # ✅ → icmp eq <ptr>, null
if p is not None: ...    # ✅ → icmp ne <ptr>, null
if p == 0: ...           # ❌ 不用:虽同样编译为 icmp eq <ptr>, null,但语义要绕"指针 vs 整型"特例分支

以整型承载地址/句柄的字段判空:整型上的 None 隐式表示 0,两侧编译器都按此判定:

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 <ptr>, null 有效(命名 is_cmp / cmpresult)
x is not None / x != None / x != 0 整型 icmp ne <int> %x, 0 有效(两侧同效)
x is None / x == None / x == 0 整型 icmp eq <int> %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)。 因此"对象有效性校验"必须能读到首字:

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 = <rawptr 值>                    # ❌ 目标 LHS 是结构体类型的解引用时**可能一条指令都不发射**(静默丢失)

(IR 实证:ViperC/output/b5883dc9c114a604.ll:10382-10384 对比 :10611-10615。)

类型限定符组合

规划:本节 t.CConst | T/t.CConst | T | t.CPtr 链式写法将随上述演进被 const rawptr[int]/const int* 取代(当前仍可用)。

x: const int        # const int
p: const char*      # const char*(旧写法:t.CConst | t.CChar | t.CPtr)

存储类组合

存储类修饰可用前置软关键字(推荐)或链式标记:

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 等)在联合类型提取类名时会被跳过,它们不是真实类型。

函数返回类型组合

新写法用软关键字;旧写法链式标记仍可用:

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),由链接器在外部库中解析符号。

extern def CreateFileA(lpFileName: LPCSTR, dwDesiredAccess: ULONG,
                dwShareMode: ULONG,
                lpSecurityAttributes: SECURITY_ATTRIBUTES*,
                dwCreationDisposition: ULONG,
                dwFlagsAndAttributes: ULONG,
                hTemplateFile: HANDLE) -> HANDLE:
    pass

等价 C 声明:

HANDLE CreateFileA(LPCSTR, ULONG, ULONG, SECURITY_ATTRIBUTES*, ULONG, ULONG, HANDLE);

日常书写推荐用 extern def 软关键字(内部即改写为 t.State 标记),无需手写标记即可声明外部函数。详见 08-c-operations.md 的 FFI 章节 和 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 定义的堆上 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 段)。两者语义与内存布局都不同,不可互换。
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 代码:

char buf[64];
int ids[10];
float matrix[16];
struct idt_entry entries[256];

数组指针与函数指针

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 章节):

class Point:
    x: int
    y: int

等价 C 代码:

struct point {
    int x;
    int y;
};

带指针成员的结构体

class Node:
    value: int
    next: Node*            # struct Node* next

字节序标记

WARNING: 警告: 此关键字是实验性的。未经充分测试,不当使用可能导致未定义行为。

使用 t.BigEndian 或 t.LittleEndian 标记成员的字节序(仍需 |):

class NetworkPacket:
    length: uint16 | t.BigEndian     # 大端序存储
    type: uint8                      # 默认小端序

联合体类型

用软关键字 union(等价 class X(t.CUnion):)定义联合体:

union Value:
    ival: int
    fval: float
    pval: void*

等价 C 代码:

union value {
    int ival;
    float fval;
    void* pval;
};

枚举类型

用软关键字 enum(等价 class X(t.CEnum):)定义枚举:

enum Color:
    RED, GREEN, BLUE          # 裸名,自动编号 0,1,2

等价 C 代码:

enum Color {
    RED = 0,
    GREEN = 1,
    BLUE = 2
};

枚举成员可直接使用名称:

c: int = Color.RED

REnum(标签联合体)

t.CEnum 是普通枚举(整数常量集合),用软关键字 renum 定义的是标签联合体(tagged union / Rust enum 风格)。每个变体可携带不同的 payload,通过 match 模式匹配分派。适用于少变体、无通用字段、match 分派的场景(如类型系统、IR 表示)。

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 章节。

Typedef

用软关键字 typedef(等价 Name: t.CTypedef = ...)创建类型别名:

typedef IrqHandler = callable[[], int]     # typedef int (*IrqHandler)();
typedef MyStruct = OriginalStruct

等价旧写法:

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] 特化。

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 的泛型类章节。

隐式类型转换

右值赋给左值、传参、返回、二元运算对齐时,编译器按以下规则做隐式转换(统一入口 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, ...):

x: char* = "Hello"
p: void* = t.CVoid(x, t.CPtr)     # (void*)x(旧链式写法,仍可用)

新写法:指针类型直接写 T*,因此多数强转可以省略;确需强转时用 T(x):

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 路径):

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 深度解析。

define MAX_SIZE = 1024           # 推荐:等价 MAX_SIZE: t.CDefine = 1024
define PAGE_SIZE: uint32 = 4096  # 带显式底层类型
FA_READ: t.CDefine = 0x01        # 旧写法等价

语义上等价于 C 的 #define:

#define MAX_SIZE 1024
#define PAGE_SIZE 4096
#define FA_READ 0x01

宏定义支持表达式:

define CPU_FEATURE_FPU = (1 << 0)
define CPU_FEATURE_APIC = (1 << 6)

宏定义也可与类型组合使用(新写法用 define X: T = v):

define CODE_SEG: uint16 = 0x08   # 等价 CODE_SEG: t.CDefine | t.CUInt16T = 0x08

位域

使用 t.Bit(n) 定义位域成员(仍需 |):

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 级别的类型简写:

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__ 属性:

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 函数属性:

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

在函数返回类型注解中使用:

def foo() -> int | t.attr.llvm.nobuiltin | t.attr.llvm.nounwind:
    return 0