fix(vipeye-internal): 打通 TransPyC 现行语法的解析/类型检查并清理全部既有失败用例
Validation / Typecheck (push) Has been cancelled
Validation / Style (push) Has been cancelled
Validation / Test macos-latest (push) Has been cancelled
Validation / Test ubuntu-latest (push) Has been cancelled
Validation / Test windows-latest (push) Has been cancelled
Validation / Build (push) Has been cancelled
Validation / Required (push) Has been cancelled

- parser:C 指针后缀(T* / T**)不再限定于类型注解上下文,调用实参同样识别
  (va_arg(ap, int64*) / short* / int*),a * b、a ** b、N * 2 等真实运算行为不变
- types:str | t.CPtr、bytes | t.CPtr 折叠为 CPtr[str] / CPtr[bytes](i8**,如 argv),
  与 TransPyC 的类型契约一致
- typeEvaluator:CPtr[str|bytes] 下标返回 pointee(str/bytes)而非 char,修 argparse.vp
  `token: str = entry[0]` 误报;新增 C 风格强转识别 Type(value, t.CPtr),修
  char(addr, t.CPtr) 在 memhub.vp / requests.vp 的误报(int("12", 16) 不受影响)
- flowChartCommand:补 AnalyzerService.run 的 CancellationToken 实参(TS2554),
  此前被该编译错误阻塞的 5 个测试套件恢复运行
- 测试维护:ord 断言改为 TransPyC 现行的 def ord(s: char) -> int;
  AssignGenMixin.vp 收窄守卫改为不写死行号,抗源码漂移
- 版本号提升至 1.1.423 并更新 CHANGELOG;全量 57 套件 / 758 用例全绿(0 失败)
This commit is contained in:
TermiNexus committed 2026-09-22 21:50:58 +08:00
1 parent 988ba922f4
commit 36dab6433b
13 files changed
+120 -53

No files matched your search

+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "vipeye-internal",
"version": "1.1.422",
"version": "1.1.423",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vipeye-internal",
"version": "1.1.422",
"version": "1.1.423",
"license": "MIT",
"dependencies": {
"@yarnpkg/fslib": "2.10.4",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "vipeye-internal",
"displayName": "vipeye",
"description": "Type checker for the Viper language",
"version": "1.1.422",
"version": "1.1.423",
"license": "MIT",
"private": true,
"files": [
@@ -8759,6 +8759,18 @@ export function createTypeEvaluator(
const pointeeInstance = TypeBase.isInstance(pointeeType)
? pointeeType
: ClassType.cloneAsInstance(pointeeType);
// Viper 扩展:str / bytes 例外——不走转发。
// Viper 中 str/bytes 都对应 C 的 char*(i8*),因此 CPtr[str] 是指向
// char* 的指针(i8**,如 argv)。对 CPtr[str] 下标 ptr[i] 按 C 语义就是
// 解引用 *(ptr + i),结果本身就是 str(i8*)指针。
// 若按下面的通用逻辑转发到 str 自身的下标特判,会落到 Viper 中 str[i]
// 返回 char 的语义(CPtr[str] 实际是 i8**,取出的应是 i8* 而非 i8),
// 造成 `token: str = entry[0]` 误报"char 不可分配给 str"。
// 因此这里直接返回 pointee 实例(等价解引用语义),CPtr[CArray[...]]、
// CPtr[MyStruct] 等"指针指向可下标聚合"的转发行为保持不变。
if (ClassType.isBuiltIn(pointeeInstance, ['str', 'bytes'])) {
return { type: pointeeInstance };
}
// 检查 T 是否有自己的 item access magic method
const pointeeMethodType = getBoundMagicMethod(
pointeeInstance,
@@ -9312,6 +9324,28 @@ export function createTypeEvaluator(
return name !== undefined && isViperCIntegerTypeName(name);
}
// Viper 扩展:判断调用节点是否为 C 风格强转 `Type(value, t.CPtr)`。
// Viper 的 C 强转用类型构造调用表达,第二参数是"类型对象"(t.CPtr / t.CVoid 等
// CType 类型形式),表示在值上再做指针修饰(如 char(addr, t.CPtr) 表示 char*)。
// 判据(须同时满足):
// 1. 恰好 2 个实参;
// 2. 第 2 个实参求值为类型对象(可实例化的 CType 子类,如 t.CPtr/t.CVoid)。
// 第 2 条是关键:Python 风格的 int("12", 16) 第二参是整数字面量,求值后不是类型
// 对象,因此不会被误判为 C 强转;只有 Viper 的 `Type(value, t.CPtr)` 才会命中。
function isViperCStyleCastCall(node: CallNode): boolean {
if (node.d.args.length !== 2) {
return false;
}
const secondArgExpr = node.d.args[1].d.valueExpr;
if (!secondArgExpr) {
return false;
}
const secondArgType = stripTypeForm(
getTypeOfExpression(secondArgExpr, EvalFlags.CallBaseDefaults).type
);
return isInstantiableClass(secondArgType) && isCTypeSubclassType(secondArgType);
}
// Viper 扩展:判断调用节点是否为 __sizeof__ 方法调用。
// 支持两种形式:
// 1. 类调用:Type.__sizeof__()
@@ -9813,6 +9847,19 @@ export function createTypeEvaluator(
} else {
typeResult.type = instanceType;
}
} else if (
// Viper 扩展:拦截目标为普通类型构造的 C 风格强转调用,如 char(addr, t.CPtr)。
// 内置 char 类(builtins.vpi 的 class char:)不是 C 整数类型白名单成员
// (不进入上面的 int / CPtr / CType 分支),若走默认 validateCallArgs 会命中
// char.__new__ 的 `base: SupportsIndex` 重载而误报"类型对象不可分配给 base"。
// Viper 的 C 强转 `Type(value, t.CPtr)` 语义上等价 C 的 (Type*)value,
// 由编译器在语义分析阶段处理,LSP 跳过 __new__ 参数检查,
// 直接返回被调用类型构造的实例(指针修饰信息不影响此处的类型表达)。
IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) &&
isInstantiableClass(baseTypeResult.type) &&
isViperCStyleCastCall(node)
) {
typeResult.type = ClassType.cloneAsInstance(baseTypeResult.type);
} else if (
// Viper 扩展:拦截 REnum 变体类构造调用。
// Viper 的 REnum 变体(如 LLVMType.Int(bits)、LLVMType.Struct(fields, fcount, name))
@@ -4179,8 +4179,12 @@ export function combineTypes(subtypes: Type[], options?: CombineTypesOptions): T
// Viper 扩展:将 Struct | CPtr 自动映射为 CPtr[Struct]。
// 模拟 C 语言指针语义:结构体与指针的联合类型自动转换为指向该结构体的指针类型。
// 例如:Scheduler | t.CPtr → CPtr[Scheduler],bootinfo.bootinfo | t.CPtr → CPtr[bootinfo.bootinfo]。
// 识别条件:联合类型中同时存在 CPtr 子类型和"结构体"子类型(非 CPtr/CVoid/CArray/CConst/CDefine/str/bytes)。
// 识别条件:联合类型中同时存在 CPtr 子类型和"结构体"子类型(非 CPtr/CVoid/CArray/CConst/CDefine)。
// int 允许作为结构体候选:`int | t.CPtr` → CPtr[int](C 的 int*),与 `t.CInt | t.CPtr` 一致。
// str/bytes 也允许作为 CPtr 的被指向类型:Viper 中 str/bytes 本身就是 C 的 char*(i8*),
// 因此 `str | t.CPtr` 折叠为 CPtr[str](i8**,即 char**,如 argv),
// `bytes | t.CPtr` 折叠为 CPtr[bytes](同一 i8** 语义)。这与 TransPyC 的契约一致。
// 注:无 CPtr 子类型时(如 `CConst | str` = const char*)不会折叠,保持联合类型语义。
if (subtypes.length > 1) {
let cptrSubtype: ClassType | undefined;
const structSubtypes: ClassType[] = [];
@@ -4201,8 +4205,6 @@ export function combineTypes(subtypes: Type[], options?: CombineTypesOptions): T
!isCConstType(subtype) &&
!isCStaticType(subtype) &&
!isCDefineType(subtype) &&
!ClassType.isBuiltIn(subtype, 'str') &&
!ClassType.isBuiltIn(subtype, 'bytes') &&
!ClassType.isBuiltIn(subtype, 'object')
) {
structSubtypes.push(subtype);
@@ -75,7 +75,7 @@ export class FlowChartCommand implements ServerCommand {
startLine: convertOffsetToPosition(functionNode.start, lines).line + 1,
endLine: convertOffsetToPosition(functionNode.start + functionNode.length - 1, lines).line + 1,
};
});
}, token);
}
}
+23 -13
View File
@@ -3796,14 +3796,15 @@ export class Parser {
nextOperator === OperatorType.Mod ||
nextOperator === OperatorType.FloorDivide
) {
// Viper 扩展:类型注解上下文中的后缀 '*' 是 C 指针类型(T* = CPtr[T]),
// 不是二元乘法。若该星号串后紧跟"无右操作数"位置(']' / ',' / ')' / ':' /
// '=' / 换行 / ';' / 文件结束),就地按指针后缀消费(覆盖 `list[ast.AST*]`
// 这类下标实参内的后缀);否则停止解析,交由
// _parseTypeAnnotation/_parseTypeAliasStatement 的后缀处理消费。
// Viper 扩展:后缀 '*' 是 C 指针类型(T* = CPtr[T]),不是二元乘法。
// 若该星号串后紧跟"无右操作数"位置(']' / ',' / ')' / ':' /
// '=' / 换行 / ';' / 文件结束),就地按指针后缀消费。此判定不限于类型注解
// 上下文:调用实参中的类型名(如 `va_arg(ap, int64*)`)同样适用。
// 若不在后缀位置:类型注解上下文停止解析,交由
// _parseTypeAnnotation/_parseTypeAliasStatement 的后缀处理消费;
// 其他上下文则按真实二元乘法继续解析(`a * b`、`N * 2` 行为不变)。
if (
this._parseOptions.dialect === 'viper' &&
this._isParsingTypeAnnotation &&
nextOperator === OperatorType.Multiply
) {
if (this._isAtViperPointerTypeSuffix()) {
@@ -3812,7 +3813,9 @@ export class Parser {
nextOperator = this._peekOperatorType();
continue;
}
break;
if (this._isParsingTypeAnnotation) {
break;
}
}
this._getNextToken();
@@ -3853,13 +3856,20 @@ export class Parser {
const peekToken = this._peekToken();
const nextPowerOperator = this._peekOperatorType();
// Viper 扩展:类型注解上下文中的 '**' 是二级指针类型后缀(T** = CPtr[CPtr[T]]),
// Viper 扩展:'**' 是二级指针类型后缀(T** = CPtr[CPtr[T]]),
// 词法化为 Power 算子('**'),不是幂运算。若其后紧跟"无右操作数"位置
// (如 `list[ast.AST**]`、`x: int**`),就地按指针后缀消费;
// 否则跳过幂运算分支,交由 _parseTypeAnnotation/_parseTypeAliasStatement
// 的后缀处理消费。
if (nextPowerOperator === OperatorType.Power && this._parseOptions.dialect === 'viper' && this._isParsingTypeAnnotation) {
return this._isAtViperPointerTypeSuffix() ? this._parseViperPointerTypeSuffix(leftExpr) : leftExpr;
// (如 `list[ast.AST**]`、`x: int**`、调用实参 `va_arg(ap, short**)`),
// 就地按指针后缀消费。此判定不限于类型注解上下文;
// 若不在后缀位置:类型注解上下文跳过幂运算分支,交由
// _parseTypeAnnotation/_parseTypeAliasStatement 的后缀处理消费;
// 其他上下文则按真实幂运算解析(`a ** b` 行为不变)。
if (nextPowerOperator === OperatorType.Power && this._parseOptions.dialect === 'viper') {
if (this._isAtViperPointerTypeSuffix()) {
return this._parseViperPointerTypeSuffix(leftExpr);
}
if (this._isParsingTypeAnnotation) {
return leftExpr;
}
}
if (nextPowerOperator === OperatorType.Power) {
this._getNextToken();
@@ -263,7 +263,7 @@ test('其它环境的文件先被绑定时,本工程 builtins.vp typedef 仍
fs.rmSync(base, { recursive: true, force: true });
});
test('builtins.vp 同名声明优先覆盖 builtins.vpi(ord 参数为 t.CChar)', () => {
test('builtins.vp 同名声明优先覆盖 builtins.vpi(ord 参数为 char)', () => {
const source = `import t
def f() -> int:
@@ -272,7 +272,7 @@ def f() -> int:
`;
const { program, parseResults, serviceProvider } = buildProgram(source);
// ord 在 builtins.vp 中声明为 ord(s: t.CChar) -> int,
// ord 在 TransPyC 的 includes/builtins.vp 中声明为 ord(s: char) -> int,
// 覆盖 builtins.vpi 的 ord(c: str | bytes | bytearray, /) -> int。
// 通过 main 文件作用域链(含 builtin 作用域)解析 ord 符号。
const AnalyzerNodeInfo = require('../analyzer/analyzerNodeInfo');
@@ -283,8 +283,12 @@ def f() -> int:
const ordType = program.getTypeOfSymbol(ordSymbol);
const ordSignature = program.printType(ordType, { expandTypeAlias: true });
assert(
ordSignature.includes('CChar'),
`ord 应使用 builtins.vp 的 t.CChar 签名,实际: ${ordSignature}`
ordSignature.includes('char'),
`ord 应使用 builtins.vp 的 char 签名,实际: ${ordSignature}`
);
assert(
!ordSignature.includes('str | bytes'),
`ord 不应仍是 builtins.vpi 的 str | bytes 签名,实际: ${ordSignature}`
);
program.dispose();
@@ -129,19 +129,18 @@ test('AssignGenMixin.vp match 变体模式内 rhs_val.Ty 保持指针形态 CPtr
if (evaluator && parseResults) {
const ev = evaluator;
const lines = parseResults.parserOutput.lines;
// 不再写死行号:TransPyC 源码行号会漂移(该文件已从 L2013 漂到 L2527),
// 改为扫描全文所有 `rhs_val.Ty` 成员访问,断言其类型始终是指针形态。
const tyByLine: Array<{ line: number; type: string }> = [];
class TyCollector extends ParseTreeWalker {
override visitMemberAccess(node: MemberAccessNode) {
const pos = convertOffsetToPosition(node.start, lines);
if (pos.line < 2012 || pos.line > 2051) {
return true;
}
const memberName = (node.d.member as any)?.d?.value ?? '?';
const baseName = (node.d.leftExpr as any)?.d?.value;
// 只关注 `rhs_val.Ty`;同范围内还有 `tgt_alloca.Ty`(另一个变量)不属于本用例。
// 只关注 `rhs_val.Ty`;文件里还有 `tgt_alloca.Ty`(另一个变量)不属于本用例。
if (memberName !== 'Ty' || baseName !== 'rhs_val') {
return true;
}
const pos = convertOffsetToPosition(node.start, lines);
const t = ev.getType(node as any);
tyByLine.push({ line: pos.line + 1, type: t ? program.printType(t) : '(undefined)' });
out.push(` L${pos.line + 1} .${memberName}: ${t ? program.printType(t) : '(undefined)'}`);
@@ -150,28 +149,25 @@ test('AssignGenMixin.vp match 变体模式内 rhs_val.Ty 保持指针形态 CPtr
}
new TyCollector().walk(parseResults.parserOutput.parseTree);
// 核心断言 1:Struct case 体内(L2017/L2020/L2039)的 rhs_val.Ty 保持指针形态
// rawptr[Struct],而非误收窄为值类型 Struct(原导致 build_alloca 等报
// "Struct 不可分配给 rawptr[LLVMType]")
const caseBodyTy = tyByLine.filter((e) => e.line >= 2016 && e.line <= 2055);
expect(caseBodyTy.length).toBeGreaterThan(0);
expect(caseBodyTy.every((e) => e.type === 'rawptr[Struct]')).toBe(true);
// case 之外(守卫 L2013 与 match 主语 L2015)的 rhs_val.Ty 仍是声明类型
// rawptr[LLVMType](或其变体联合),不得退化为纯值类型 Struct
const outsideCaseTy = tyByLine.filter((e) => e.line < 2016);
expect(outsideCaseTy.length).toBeGreaterThan(0);
expect(outsideCaseTy.every((e) => e.type.includes('rawptr['))).toBe(true);
expect(tyByLine.some((e) => e.type === 'Struct')).toBe(false);
// 断言 1:必须确实扫到 `rhs_val.Ty`(源码若再漂移/删改,本用例应显式失败而非静默通过)
expect(tyByLine.length).toBeGreaterThan(0);
// 核心断言 2:L2013-2052 范围内不应再出现"不可分配给"诊断
// 断言 2:rhs_val.Ty 声明为 `LLVMType | t.CPtr`(折叠为 rawptr[LLVMType]),
// 在 `match rhs_val.Ty:` 的 `case LLVMType.Struct(...)` 体内应保持指针形态
// rawptr[Struct],不得被误收窄为值类型 Struct(原导致 build_alloca 等报
// "Struct 不可分配给 rawptr[LLVMType]")。
expect(tyByLine.some((e) => e.type === 'Struct')).toBe(false);
expect(tyByLine.every((e) => e.type.includes('rawptr['))).toBe(true);
// 断言 3:全文不得出现 "Struct → rawptr[...]" 形态的赋值不兼容诊断
const sourceFile = program.getSourceFile(uri);
const diags = sourceFile!.getDiagnostics(configOptions) || [];
const rangeDiags = diags.filter((d: any) => {
const line = d.range ? d.range.start.line + 1 : -1;
return line >= 2013 && line <= 2052;
const narrowDiags = diags.filter((d: any) => {
const msg = d.message;
return msg.includes('不可分配') && msg.includes('Struct') && msg.includes('rawptr');
});
expect(rangeDiags.length).toBe(0);
rangeDiags.forEach((d: any) => {
expect(narrowDiags.length).toBe(0);
narrowDiags.forEach((d: any) => {
out.push(` [category=${d.category}][line=${(d.range?.start.line ?? -1) + 1}] ${d.message}`);
});
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "vipeye",
"version": "1.1.422",
"version": "1.1.423",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vipeye",
"version": "1.1.422",
"version": "1.1.423",
"license": "MIT",
"bin": {
"vipeye": "index.js",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "vipeye",
"displayName": "Vipeye",
"description": "Type checker for the Viper language",
"version": "1.1.422",
"version": "1.1.423",
"license": "MIT",
"author": {
"name": "TermiNexus"
+8
View File
@@ -1,5 +1,13 @@
# Change Log
## 1.1.423
- Parser: the C pointer suffix (`T*` / `T**`) is now recognized in call arguments, not only inside type annotations, so `va_arg(ap, int64*)` / `short*` / `int*` (as used by TransPyC's `viperlib.vp`) parse correctly. Real multiplication and power expressions (`a * b`, `a ** b`, `N * 2`) are unaffected
- `str | t.CPtr` / `bytes | t.CPtr` now fold to `CPtr[str]` / `CPtr[bytes]` (`i8**`, e.g. `argv`), and indexing such a pointer yields the pointee (`str` / `bytes`) instead of `char` — fixes `token: str = entry[0]` being reported as `char` not assignable to `str` (`argparse.vp`)
- C-style casts targeting a plain built-in type are accepted: `char(addr, t.CPtr)` (used by `memhub.vp` / `requests.vp`) no longer fails `char.__new__`'s `base: SupportsIndex` overload. The rule requires the second argument to be a type object, so Python's `int("12", 16)` is unaffected
- `flowChartCommand` now passes a `CancellationToken` to `AnalyzerService.run`, fixing a TS2554 error that blocked five test suites
- Test maintenance: the stale `ord` signature assertion was updated (TransPyC now declares `def ord(s: char) -> int`), and the `AssignGenMixin.vp` narrowing guard no longer hard-codes line numbers, so it survives source drift
## 1.1.422
- `str` / `bytes` iteration now yields `char` as well: `for ch in s` and `Iterable` / `Iterator` argument inference (`enumerate`, `zip`, `map`, `filter`, `reversed`, `sorted`, `list()` …) bind the element type to `char`, matching subscript `s[i]` and dereference `*s`. The old `t.CChar`-aliased `int` construction is removed
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "vscode-vipeye",
"version": "1.1.422",
"version": "1.1.423",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vscode-vipeye",
"version": "1.1.422",
"version": "1.1.423",
"license": "MIT",
"dependencies": {
"mermaid": "^11.12.0",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "vipeye",
"displayName": "Vipeye",
"description": "VS Code language support for Viper (LSP client)",
"version": "1.1.422",
"version": "1.1.423",
"private": true,
"license": "MIT",
"author": {