diff --git a/packages/vipeye-internal/package-lock.json b/packages/vipeye-internal/package-lock.json index 3ab9960b..531fe146 100644 --- a/packages/vipeye-internal/package-lock.json +++ b/packages/vipeye-internal/package-lock.json @@ -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", diff --git a/packages/vipeye-internal/package.json b/packages/vipeye-internal/package.json index 155c332a..c84c4585 100644 --- a/packages/vipeye-internal/package.json +++ b/packages/vipeye-internal/package.json @@ -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": [ diff --git a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts index 30ac5d44..52c4f04a 100644 --- a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts +++ b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts @@ -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)) diff --git a/packages/vipeye-internal/src/analyzer/types.ts b/packages/vipeye-internal/src/analyzer/types.ts index 44b4b157..6dc17fbf 100644 --- a/packages/vipeye-internal/src/analyzer/types.ts +++ b/packages/vipeye-internal/src/analyzer/types.ts @@ -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); diff --git a/packages/vipeye-internal/src/commands/flowChartCommand.ts b/packages/vipeye-internal/src/commands/flowChartCommand.ts index c95e94a7..9ee5ee23 100644 --- a/packages/vipeye-internal/src/commands/flowChartCommand.ts +++ b/packages/vipeye-internal/src/commands/flowChartCommand.ts @@ -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); } } diff --git a/packages/vipeye-internal/src/parser/parser.ts b/packages/vipeye-internal/src/parser/parser.ts index 61797846..d04d0e32 100644 --- a/packages/vipeye-internal/src/parser/parser.ts +++ b/packages/vipeye-internal/src/parser/parser.ts @@ -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(); diff --git a/packages/vipeye-internal/src/tests/viperBuiltinsVpTypedef.test.ts b/packages/vipeye-internal/src/tests/viperBuiltinsVpTypedef.test.ts index f77b909d..d2497256 100644 --- a/packages/vipeye-internal/src/tests/viperBuiltinsVpTypedef.test.ts +++ b/packages/vipeye-internal/src/tests/viperBuiltinsVpTypedef.test.ts @@ -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(); diff --git a/packages/vipeye-internal/src/tests/viperTyNarrowDump.test.ts b/packages/vipeye-internal/src/tests/viperTyNarrowDump.test.ts index e3f6e106..63a63933 100644 --- a/packages/vipeye-internal/src/tests/viperTyNarrowDump.test.ts +++ b/packages/vipeye-internal/src/tests/viperTyNarrowDump.test.ts @@ -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}`); }); } diff --git a/packages/vipeye/package-lock.json b/packages/vipeye/package-lock.json index d9e97baf..01f2a9cf 100644 --- a/packages/vipeye/package-lock.json +++ b/packages/vipeye/package-lock.json @@ -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", diff --git a/packages/vipeye/package.json b/packages/vipeye/package.json index cfaae5fc..4df4d51e 100644 --- a/packages/vipeye/package.json +++ b/packages/vipeye/package.json @@ -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" diff --git a/packages/vscode-vipeye/CHANGELOG.md b/packages/vscode-vipeye/CHANGELOG.md index ce5dd82b..ed37c293 100644 --- a/packages/vscode-vipeye/CHANGELOG.md +++ b/packages/vscode-vipeye/CHANGELOG.md @@ -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 diff --git a/packages/vscode-vipeye/package-lock.json b/packages/vscode-vipeye/package-lock.json index 9c0aa666..4f970957 100644 --- a/packages/vscode-vipeye/package-lock.json +++ b/packages/vscode-vipeye/package-lock.json @@ -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", diff --git a/packages/vscode-vipeye/package.json b/packages/vscode-vipeye/package.json index ed0c6a88..049f1951 100644 --- a/packages/vscode-vipeye/package.json +++ b/packages/vscode-vipeye/package.json @@ -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": {