diff --git a/packages/vipeye-internal/src/analyzer/typePrinter.ts b/packages/vipeye-internal/src/analyzer/typePrinter.ts index c2fc9918..7c5859af 100644 --- a/packages/vipeye-internal/src/analyzer/typePrinter.ts +++ b/packages/vipeye-internal/src/analyzer/typePrinter.ts @@ -323,9 +323,19 @@ function printTypeInternal( } } + // Viper 扩展:别名自身没有类型参数(如 `rawptr: t.CTypedef = t.CPtr`), + // 但类型被下标特化(`rawptr[T]` → 底层 `CPtr[T]`)。这种别名不能只显示 + // 别名本身,否则会丢掉类型实参(`rawptr[T]` 会显示成裸 `rawptr`)。 + // 跳过别名显示,交给常规路径输出 `rawptr[T]`。 + const isSpecializedNonGenericViperAlias = + (printTypeFlags & PrintTypeFlags.ViperDialect) !== 0 && + type.category === TypeCategory.Class && + (type.priv.typeArgs?.length ?? 0) > 0 && + (!typeParams || typeParams.length === 0); + // If it's a TypeVar, don't use the alias name. Instead, use the full // name, which may have a scope associated with it. - if (type.category !== TypeCategory.TypeVar) { + if (!isSpecializedNonGenericViperAlias && type.category !== TypeCategory.TypeVar) { return aliasName; } } finally { @@ -1009,6 +1019,17 @@ function printObjectTypeForClassInternal( objName = 'None'; } + // Viper 方言:t.CPtr 等价编译器内置裸指针 rawptr,统一显示为 rawptr + // (如 `elts: CPtr[list[CPtr[AST]]]` → `rawptr[list[rawptr[AST]]]`)。 + // 类型实参照常展开,仅替换指针类型的显示名。 + if ( + (printTypeFlags & PrintTypeFlags.ViperDialect) !== 0 && + type.shared.name === 'CPtr' && + (type.shared.fullName === 't.CPtr' || type.shared.moduleName === 't') + ) { + objName = 'rawptr'; + } + // Use the fully-qualified name if the name isn't unique. if (!uniqueNameMap.isUnique(objName)) { objName = type.shared.fullName; diff --git a/packages/vipeye-internal/src/parser/parser.ts b/packages/vipeye-internal/src/parser/parser.ts index 38b86e01..6fbe1eb6 100644 --- a/packages/vipeye-internal/src/parser/parser.ts +++ b/packages/vipeye-internal/src/parser/parser.ts @@ -3726,13 +3726,21 @@ export class Parser { nextOperator === OperatorType.FloorDivide ) { // Viper 扩展:类型注解上下文中的后缀 '*' 是 C 指针类型(T* = CPtr[T]), - // 不是二元乘法。停止解析,交由 _parseTypeAnnotation/_parseTypeAliasStatement - // 的后缀处理消费,避免 `p: T*` 被解析成 `T * <缺失操作数>` 报错。 + // 不是二元乘法。若该星号串后紧跟"无右操作数"位置(']' / ',' / ')' / ':' / + // '=' / 换行 / ';' / 文件结束),就地按指针后缀消费(覆盖 `list[ast.AST*]` + // 这类下标实参内的后缀);否则停止解析,交由 + // _parseTypeAnnotation/_parseTypeAliasStatement 的后缀处理消费。 if ( this._parseOptions.dialect === 'viper' && this._isParsingTypeAnnotation && nextOperator === OperatorType.Multiply ) { + if (this._isAtViperPointerTypeSuffix()) { + leftExpr = this._parseViperPointerTypeSuffix(leftExpr); + peekToken = this._peekToken(); + nextOperator = this._peekOperatorType(); + continue; + } break; } @@ -3775,12 +3783,14 @@ export class Parser { const peekToken = this._peekToken(); const nextPowerOperator = this._peekOperatorType(); // Viper 扩展:类型注解上下文中的 '**' 是二级指针类型后缀(T** = CPtr[CPtr[T]]), - // 词法化为 Power 算子('**'),不是幂运算。跳过幂运算分支, - // 交由 _parseTypeAnnotation/_parseTypeAliasStatement 的后缀处理消费。 - if ( - nextPowerOperator === OperatorType.Power && - !(this._parseOptions.dialect === 'viper' && this._isParsingTypeAnnotation) - ) { + // 词法化为 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; + } + if (nextPowerOperator === OperatorType.Power) { this._getNextToken(); const rightExpr = this._parseArithmeticFactor(); return this._createBinaryOperationNode(leftExpr, rightExpr, peekToken, OperatorType.Power); @@ -4958,6 +4968,55 @@ export class Parser { return result; } + // Viper 扩展:判定当前 '*'/'**' 串是否为类型注解内的 C 指针后缀。 + // 判据:星号串('*' 一层 / '**' 两层,可含换行)之后紧跟"无右操作数"位置, + // 即 ']' / ',' / ')' / ':' / '=' / 换行 / ';' / 文件结束。 + // 因此 `t.CArray[X, N * 2]`、`a ** b` 这类'*'后有操作数的真实运算不会被误判。 + private _isAtViperPointerTypeSuffix(): boolean { + let offset = 0; + let starCount = 0; + while (true) { + const token = this._peekToken(offset); + if (token.type === TokenType.Operator) { + const operatorType = (token as OperatorToken).operatorType; + if (operatorType === OperatorType.Multiply) { + starCount += 1; + offset += 1; + continue; + } + if (operatorType === OperatorType.Power) { + starCount += 2; + offset += 1; + continue; + } + } + if (token.type === TokenType.NewLine) { + offset += 1; + continue; + } + break; + } + + if (starCount === 0) { + return false; + } + + const nextToken = this._peekToken(offset); + switch (nextToken.type) { + case TokenType.CloseBracket: + case TokenType.Comma: + case TokenType.CloseParenthesis: + case TokenType.Colon: + case TokenType.Semicolon: + case TokenType.NewLine: + case TokenType.EndOfStream: + return true; + case TokenType.Operator: + return (nextToken as OperatorToken).operatorType === OperatorType.Assign; + } + return false; + } + // Viper 扩展:解析 C 指针类型后缀 `T*` / `T**` / `T***`。 // 每个 '*' 对应一层指针;'**' 词法化为单个 Power 算子、等价两层指针。 // 结果用 UnaryOperationNode(operator=Multiply) 嵌套表示: diff --git a/packages/vipeye-internal/src/tests/__ty_narrow_dump.txt b/packages/vipeye-internal/src/tests/__ty_narrow_dump.txt index 5c64c3f1..d9f896b1 100644 --- a/packages/vipeye-internal/src/tests/__ty_narrow_dump.txt +++ b/packages/vipeye-internal/src/tests/__ty_narrow_dump.txt @@ -1,42 +1,5 @@ - L798 .coerce_to_type: (builder: CPtr[IRBuilder], val: CPtr[Value], target_ty: CPtr[LLVMType], node: CPtr[AST] = None, trans: CPtr[Translator] = None) -> CPtr[Value] - L798 .ExprGenBase: CPtr[ExprGenBase] - L798 .Gen: CPtr[Generator] - L801 .fatal_error: (node: CPtr[AST], msg: str, err_type: str = "SyntaxError", col_start: int = 0, col_end: int = 0) -> None - L801 .TypeGenErr: CPtr[TypeGenErr] - L801 .Gen: CPtr[Generator] - L802 .build_store: (builder: CPtr[IRBuilder], val: CPtr[Value], ptr: CPtr[Value]) -> None - L807 .Subscript: Literal[ASTKind.Subscript] - L807 .ASTKind: type[ASTKind] - L808 .MemBuddy: type[MemBuddy] - L808 .CPtr: type[CPtr[Unknown]] - L808 .Pool: CPtr[MemBuddy] - L809 .IRBuilder: type[IRBuilder] - L809 .CPtr: type[CPtr[Unknown]] - L809 ._cur_builder: CPtr[IRBuilder] - L810 .LLVMModule: type[LLVMModule] - L810 .CPtr: type[CPtr[Unknown]] - L810 .Module: CPtr[LLVMModule] - L813 .Value: type[Value] - L813 .CPtr: type[CPtr[Unknown]] - L813 .get_subscript_ptr: (builder: CPtr[IRBuilder], pool: CPtr[MemBuddy], mod: CPtr[LLVMModule], node: CPtr[AST], trans: CPtr[Translator] = None) -> CPtr[Value] - L813 .ExprGenName: CPtr[ExprGenName] - L813 .Gen: CPtr[Generator] - L817 .fatal_error: (node: CPtr[AST], msg: str, err_type: str = "SyntaxError", col_start: int = 0, col_end: int = 0) -> None - L817 .TypeGenErr: CPtr[TypeGenErr] - L817 .Gen: CPtr[Generator] - L820 .LLVMType: type[LLVMType] - L820 .CPtr: type[CPtr[Unknown]] - L821 .Ty: CPtr[LLVMType] - L821 .Pointee: CPtr[LLVMType] - L821 .Ty: CPtr[LLVMType] - L822 .Pointee: CPtr[LLVMType] - L822 .Ty: CPtr[LLVMType] - L825 .fatal_error: (node: CPtr[AST], msg: str, err_type: str = "SyntaxError", col_start: int = 0, col_end: int = 0) -> None - L825 .TypeGenErr: CPtr[TypeGenErr] - L825 .Gen: CPtr[Generator] - L826 .Value: type[Value] - L826 .CPtr: type[CPtr[Unknown]] - L826 .build_load: (builder: CPtr[IRBuilder], ty: CPtr[LLVMType], ptr: CPtr[Value]) -> CPtr[Value] - L830 .fatal_error: (node: CPtr[AST], msg: str, err_type: str = "SyntaxError", col_start: int = 0, col_end: int = 0) -> None - L830 .TypeGenErr: CPtr[TypeGenErr] - L830 .Gen: CPtr[Generator] + L2013 .Ty: rawptr[LLVMType] | rawptr[Ptr] | rawptr[Struct] + L2015 .Ty: rawptr[LLVMType] | rawptr[Ptr] | rawptr[Struct] + L2018 .Ty: rawptr[Struct] + L2021 .Ty: rawptr[Struct] + L2040 .Ty: rawptr[Struct] diff --git a/packages/vipeye-internal/src/tests/__unary_ptr__/unary_ptr.vp b/packages/vipeye-internal/src/tests/__unary_ptr__/unary_ptr.vp index bc3d0438..25496f95 100644 --- a/packages/vipeye-internal/src/tests/__unary_ptr__/unary_ptr.vp +++ b/packages/vipeye-internal/src/tests/__unary_ptr__/unary_ptr.vp @@ -1,10 +1,6 @@ import t, c +import ast -def main() -> int: - x: int = 42 - px: int | t.CPtr = &x - *px = 100 - y: int = *px - pz: int* = &x - *pz = 7 - return 0 +class Holder: + ch_r: list[ast.AST*]* + direct: rawptr[ast.AST] diff --git a/packages/vipeye-internal/src/tests/parser.test.ts b/packages/vipeye-internal/src/tests/parser.test.ts index bef68ba8..67669687 100644 --- a/packages/vipeye-internal/src/tests/parser.test.ts +++ b/packages/vipeye-internal/src/tests/parser.test.ts @@ -197,7 +197,8 @@ test('TrailingBackslashCRAtEOF', () => { TestUtils.parseText(code, diagSink); const errors = diagSink.getErrors(); assert.strictEqual(errors.length > 0, true); - assert.ok(errors.some((e) => e.message.includes('Unexpected EOF'))); + // 消息已本地化(默认 zh-cn),断言用当前 locale 的实际文案,避免写死英文。 + assert.ok(errors.some((e) => e.message.includes(LocMessage.unexpectedEof()))); }); test('LazyImport - Python 3.15', () => { diff --git a/packages/vipeye-internal/src/tests/viperBuiltinsVpTypedef.test.ts b/packages/vipeye-internal/src/tests/viperBuiltinsVpTypedef.test.ts index d7bc6e1e..6196b5b9 100644 --- a/packages/vipeye-internal/src/tests/viperBuiltinsVpTypedef.test.ts +++ b/packages/vipeye-internal/src/tests/viperBuiltinsVpTypedef.test.ts @@ -129,7 +129,7 @@ def h(a: int8) -> int: // 2. 参数注解类型解析:rawptr[int] → CPtr[int] const types = collectParamAnnotationTypes(program, parseResults!); const fTypes = types.get('f'); - assert(fTypes && fTypes.some((t) => t.includes('CPtr') && t.includes('int')), `f(x) 应为 CPtr[int],实际: ${JSON.stringify(fTypes)}`); + assert(fTypes && fTypes.some((t) => t.includes('rawptr') && t.includes('int')), `f(x) 应为 rawptr[int],实际: ${JSON.stringify(fTypes)}`); // 3. 返回注解 size_t → CSizeT const gReturn = (() => { diff --git a/packages/vipeye-internal/src/tests/viperTyNarrowDump.test.ts b/packages/vipeye-internal/src/tests/viperTyNarrowDump.test.ts index 24a93038..e3f6e106 100644 --- a/packages/vipeye-internal/src/tests/viperTyNarrowDump.test.ts +++ b/packages/vipeye-internal/src/tests/viperTyNarrowDump.test.ts @@ -74,7 +74,7 @@ test('AssignGenMixin.vp 守卫后 .Ty/.Pointee 不误收窄为 CPtr[None]', () = class TyCollector extends ParseTreeWalker { override visitMemberAccess(node: MemberAccessNode) { const pos = convertOffsetToPosition(node.start, lines); - if (pos.line < 795 || pos.line > 830) { + if (pos.line < 943 || pos.line > 967) { return true; } const memberName = (node.d.member as any)?.d?.value ?? '?'; @@ -94,18 +94,18 @@ test('AssignGenMixin.vp 守卫后 .Ty/.Pointee 不误收窄为 CPtr[None]', () = // 核心断言 1:`if target_alloca.Ty is None:` 守卫之后,.Ty 不再被误窄化为 CPtr[None] //(None 是空指针取值,is None 正测试不改变 CPtr 指针类型本身) expect(tyTypes.some((s) => s.includes('[None]'))).toBe(false); - expect(tyTypes.every((s) => s === 'CPtr[LLVMType]')).toBe(true); + expect(tyTypes.every((s) => s === 'rawptr[LLVMType]')).toBe(true); // 核心断言 2:守卫之后 .Pointee 可正常解析(原报"无法访问类'None'的属性'Pointee'") expect(pointeeTypes.some((s) => s === 'Unknown')).toBe(false); - expect(pointeeTypes.every((s) => s === 'CPtr[LLVMType]')).toBe(true); + expect(pointeeTypes.every((s) => s === 'rawptr[LLVMType]')).toBe(true); - // 核心断言 3:L795-830 范围内不应再出现"无法访问/未定义"诊断 + // 核心断言 3:L944-968 范围内不应再出现"无法访问/未定义"诊断 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 >= 795 && line <= 830; + return line >= 944 && line <= 968; }); expect(rangeDiags.length).toBe(0); rangeDiags.forEach((d: any) => { @@ -133,34 +133,42 @@ test('AssignGenMixin.vp match 变体模式内 rhs_val.Ty 保持指针形态 CPtr class TyCollector extends ParseTreeWalker { override visitMemberAccess(node: MemberAccessNode) { const pos = convertOffsetToPosition(node.start, lines); - if (pos.line < 1525 || pos.line > 1560) { + if (pos.line < 2012 || pos.line > 2051) { return true; } const memberName = (node.d.member as any)?.d?.value ?? '?'; - const t = ev.getType(node as any); - if (memberName === 'Ty') { - tyByLine.push({ line: pos.line + 1, type: t ? program.printType(t) : '(undefined)' }); + const baseName = (node.d.leftExpr as any)?.d?.value; + // 只关注 `rhs_val.Ty`;同范围内还有 `tgt_alloca.Ty`(另一个变量)不属于本用例。 + if (memberName !== 'Ty' || baseName !== 'rhs_val') { + return true; } + 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)'}`); return true; } } new TyCollector().walk(parseResults.parserOutput.parseTree); - // 核心断言 1:Struct case 体内(L1537/L1540/L1559)的 .Ty 保持指针形态 CPtr[Struct], - // 而非误收窄为值类型 Struct(原导致 build_alloca 等报"Struct 不可分配给 CPtr[LLVMType]") - const caseBodyTy = tyByLine.filter((e) => e.line >= 1537); + // 核心断言 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 === 'CPtr[Struct]')).toBe(true); - // 全范围不允许出现纯值类型 Struct + 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); - // 核心断言 2:L1525-1560 范围内不应再出现"不可分配给"诊断 + // 核心断言 2:L2013-2052 范围内不应再出现"不可分配给"诊断 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 >= 1525 && line <= 1560; + return line >= 2013 && line <= 2052; }); expect(rangeDiags.length).toBe(0); rangeDiags.forEach((d: any) => { diff --git a/packages/vipeye-internal/src/tests/viperUnaryPointer.test.ts b/packages/vipeye-internal/src/tests/viperUnaryPointer.test.ts index 8b818b95..a72528ce 100644 --- a/packages/vipeye-internal/src/tests/viperUnaryPointer.test.ts +++ b/packages/vipeye-internal/src/tests/viperUnaryPointer.test.ts @@ -228,37 +228,37 @@ def make_ptr(x: t.CInt) -> t.CInt*: // T* → CPtr[CInt](L7) const p1 = anns.find((a) => a.line === 7); assert(p1, 'p1 annotation not found (L7)'); - assert(p1.type.includes('CPtr[CInt]'), `p1 should be CPtr[CInt]: ${p1.type}`); + assert(p1.type.includes('rawptr[CInt]'), `p1 should be rawptr[CInt]: ${p1.type}`); - // T** → CPtr[CPtr[CInt]](L8) + // T** → rawptr[rawptr[CInt]](L8) const p2 = anns.find((a) => a.line === 8); assert(p2, 'p2 annotation not found (L8)'); - assert(p2.type.includes('CPtr[CPtr[CInt]]'), `p2 should be CPtr[CPtr[CInt]]: ${p2.type}`); + assert(p2.type.includes('rawptr[rawptr[CInt]]'), `p2 should be rawptr[rawptr[CInt]]: ${p2.type}`); - // T*** → CPtr[CPtr[CPtr[CInt]]](L9) + // T*** → rawptr[rawptr[rawptr[CInt]]](L9) const p3 = anns.find((a) => a.line === 9); assert(p3, 'p3 annotation not found (L9)'); - assert(p3.type.includes('CPtr[CPtr[CPtr[CInt]]]'), `p3 should be CPtr[CPtr[CPtr[CInt]]]: ${p3.type}`); + assert(p3.type.includes('rawptr[rawptr[rawptr[CInt]]]'), `p3 should be rawptr[rawptr[rawptr[CInt]]]: ${p3.type}`); - // 结构体指针 MyStruct* → CPtr[MyStruct](L11) + // 结构体指针 MyStruct* → rawptr[MyStruct](L11) const sp = anns.find((a) => a.line === 11); assert(sp, 'sp annotation not found (L11)'); - assert(sp.type.includes('CPtr[MyStruct]'), `sp should be CPtr[MyStruct]: ${sp.type}`); + assert(sp.type.includes('rawptr[MyStruct]'), `sp should be rawptr[MyStruct]: ${sp.type}`); // 参数 q: t.CInt*(L15) const q = anns.find((a) => a.line === 15); assert(q, 'q annotation not found (L15)'); - assert(q.type.includes('CPtr[CInt]'), `q should be CPtr[CInt]: ${q.type}`); + assert(q.type.includes('rawptr[CInt]'), `q should be rawptr[CInt]: ${q.type}`); // 参数 pp: t.CInt**(L18) const pp = anns.find((a) => a.line === 18); assert(pp, 'pp annotation not found (L18)'); - assert(pp.type.includes('CPtr[CPtr[CInt]]'), `pp should be CPtr[CPtr[CInt]]: ${pp.type}`); + assert(pp.type.includes('rawptr[rawptr[CInt]]'), `pp should be rawptr[rawptr[CInt]]: ${pp.type}`); // 返回类型 -> t.CInt*(L21) const ret = anns.find((a) => a.line === 21); assert(ret, 'return annotation not found (L21)'); - assert(ret.type.includes('CPtr[CInt]'), `return should be CPtr[CInt]: ${ret.type}`); + assert(ret.type.includes('rawptr[CInt]'), `return should be rawptr[CInt]: ${ret.type}`); // 不得有任何 Error 类别诊断(指针类型注解不应产生错误) const diagnostics = getDiagnostics(program, configOptions, uri); @@ -303,13 +303,13 @@ def main() -> int: console.log('=== 无空格 *=/ **= 注解类型 ==='); anns.forEach((a) => console.log(` L${a.line}: ${a.type}`)); - // L5: px: int*=&x → CPtr[int];L6: ppx: int**=&px → CPtr[CPtr[int]] + // L5: px: int*=&x → rawptr[int];L6: ppx: int**=&px → rawptr[rawptr[int]] const px = anns.find((a) => a.line === 5); assert(px, 'px annotation not found (L5)'); - assert(px.type.includes('CPtr[int]'), `px should be CPtr[int]: ${px.type}`); + assert(px.type.includes('rawptr[int]'), `px should be rawptr[int]: ${px.type}`); const ppx = anns.find((a) => a.line === 6); assert(ppx, 'ppx annotation not found (L6)'); - assert(ppx.type.includes('CPtr[CPtr[int]]'), `ppx should be CPtr[CPtr[int]]: ${ppx.type}`); + assert(ppx.type.includes('rawptr[rawptr[int]]'), `ppx should be rawptr[rawptr[int]]: ${ppx.type}`); // 不得有 Error 类别诊断(含内部错误) const diagnostics = getDiagnostics(program, configOptions, uri); @@ -322,6 +322,60 @@ def main() -> int: serviceProvider.dispose(); }); +test('Viper 下标实参内指针后缀 + 外层指针:list[ast.AST*]* 不误报「[ 未关闭」', () => { + const code = `import t, c +import ast + +class Holder: + ch_r: list[ast.AST*]* + direct: rawptr[ast.AST] +`; + const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code); + + const evaluator = program.evaluator as TypeEvaluator | undefined; + assert(evaluator, 'evaluator not available'); + const ev = evaluator; + + const anns: { line: number; type: string }[] = []; + class AnnCollector extends ParseTreeWalker { + override visitTypeAnnotation(node: TypeAnnotationNode) { + const t = ev.getTypeOfAnnotation(node.d.annotation); + anns.push({ + line: convertOffsetToPosition(node.d.annotation.start, parseResults.parserOutput.lines).line + 1, + type: t ? program.printType(t) : '(undefined)', + }); + return true; + } + } + new AnnCollector().walk(parseResults.parserOutput.parseTree); + + // L5: ch_r: list[ast.AST*]* → rawptr[list[rawptr[AST]]] + const chR = anns.find((a) => a.line === 5); + assert(chR, 'ch_r annotation not found (L5)'); + assert( + chR.type.includes('rawptr[list[rawptr['), + `ch_r should be rawptr[list[rawptr[AST]]]: ${chR.type}` + ); + + // L6: direct: rawptr[ast.AST] → rawptr[AST](rawptr 由 builtins.vpi 存根声明, + // 不得报「"rawptr" 未定义」) + const direct = anns.find((a) => a.line === 6); + assert(direct, 'direct annotation not found (L6)'); + assert(direct.type.includes('rawptr[AST]'), `direct should be rawptr[AST]: ${direct.type}`); + + // 不得出现「"[" 未关闭」「rawptr 未定义」等错误(category=0 = Error) + const diagnostics = getDiagnostics(program, configOptions, uri); + const errors = diagnostics.filter((d) => d.includes('category=0')); + assert(errors.length === 0, `list[ast.AST*]* should parse without errors: ${JSON.stringify(errors)}`); + assert( + !diagnostics.some((d) => d.includes('rawptr') && d.includes('未定义')), + `rawptr should resolve from builtins.vpi: ${JSON.stringify(diagnostics)}` + ); + + program.dispose(); + serviceProvider.dispose(); +}); + test('Viper 解引用赋值:*p = value(联合形式与 T* 形式)', () => { const code = `import t, c @@ -356,13 +410,13 @@ def main() -> int: console.log('=== 解引用赋值 注解类型 ==='); anns.forEach((a) => console.log(` L${a.line}: ${a.type}`)); - // L5: px: int | t.CPtr → CPtr[int](联合折叠);L8: pz: int* → CPtr[int] + // L5: px: int | t.CPtr → rawptr[int](联合折叠);L8: pz: int* → rawptr[int] const px = anns.find((a) => a.line === 5); assert(px, 'px annotation not found (L5)'); - assert(px.type.includes('CPtr[int]'), `px should fold to CPtr[int]: ${px.type}`); + assert(px.type.includes('rawptr[int]'), `px should fold to rawptr[int]: ${px.type}`); const pz = anns.find((a) => a.line === 8); assert(pz, 'pz annotation not found (L8)'); - assert(pz.type.includes('CPtr[int]'), `pz should be CPtr[int]: ${pz.type}`); + assert(pz.type.includes('rawptr[int]'), `pz should be rawptr[int]: ${pz.type}`); // *px = 100(L6)与 *pz = 7(L8)不得报"表达式不能是赋值目标"; // y: int = *px 解引用右值赋值兼容。 diff --git a/packages/vipeye-internal/src/tests/viperVariadicArgs.test.ts b/packages/vipeye-internal/src/tests/viperVariadicArgs.test.ts index 7a694be9..6fe89223 100644 --- a/packages/vipeye-internal/src/tests/viperVariadicArgs.test.ts +++ b/packages/vipeye-internal/src/tests/viperVariadicArgs.test.ts @@ -30,7 +30,7 @@ import { FullAccessHost } from '../common/fullAccessHost'; const VIPERLIB_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\viperlib.vp'; const MEMHUB_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\memhub.vp'; -const REQUESTS_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\requests.vp'; +const REQUESTS_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\requests\\__init__.vp'; const ARGPARSE_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\argparse.vp'; const TYPES_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\llvmlite\\__types.vp'; const OS_INIT_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\os\\__init__.vp'; @@ -250,7 +250,7 @@ test('requests.vp 中 str = None 默认值不展开为 str | None,无 Optional const diagnostics = getDiagnostics(program, configOptions, uri); - // 命中场景:requests.vp L258 `def post(url: str, data: str = None, ...)`。 + // 命中场景:requests/__init__.vp L679 `def post(url: str, data: str = None, ...)`。 // Viper 下 str 是 i8*(可空指针),None 是空指针,不应展开为 str | None, // 否则 data[0] 等下标访问会误报 reportOptionalSubscript。 const optionalErrors = diagnostics.filter( @@ -283,9 +283,9 @@ test('__sha256.vp 中 CArray 传给 CPtr 参数(数组名退化)不报类型 (d) => d.message.includes('cannot be assigned to parameter "block"') || d.message.includes('参数 "block"') || - (d.message.includes('CArray') && d.message.includes('CPtr')) + (d.message.includes('CArray') && (d.message.includes('rawptr') || d.message.includes('CPtr'))) ); - assert.strictEqual(cptrErrors.length, 0, `unexpected CArray->CPtr errors: ${JSON.stringify(cptrErrors)}`); + assert.strictEqual(cptrErrors.length, 0, `unexpected CArray->rawptr errors: ${JSON.stringify(cptrErrors)}`); program.dispose(); serviceProvider.dispose(); @@ -305,9 +305,9 @@ test('__sha512.vp 中 CPtr 传给 CArray 参数(形参调整)不报类型错 (d) => d.message.includes('cannot be assigned to parameter "block"') || d.message.includes('参数 "block"') || - (d.message.includes('CArray') && d.message.includes('CPtr')) + (d.message.includes('CArray') && (d.message.includes('rawptr') || d.message.includes('CPtr'))) ); - assert.strictEqual(cptrErrors.length, 0, `unexpected CPtr->CArray errors: ${JSON.stringify(cptrErrors)}`); + assert.strictEqual(cptrErrors.length, 0, `unexpected rawptr->CArray errors: ${JSON.stringify(cptrErrors)}`); program.dispose(); serviceProvider.dispose(); @@ -366,28 +366,38 @@ function getSemanticTokens(program: Program, uri: any): Map { return map; } -test('ast/__init__.vp 中不存在的 from .visitor import ASTVisitor 标黄且标白(undefined)', () => { - const { program, serviceProvider, uri, configOptions } = buildProgram(AST_INIT_PATH); +test('不存在的相对导入 from .visitor import ASTVisitor 标黄且标白(undefined)', () => { + // 自包含用例:临时包 pkg/__init__.vp 中写 `from .visitor import ASTVisitor`。 + // (原用例指向 ast/__init__.vp,但该 import 已被源码注释掉,断言随之过期。) + const fs = require('fs'); + const testRoot = path.join(__dirname, '__visitor_undef__'); + fs.rmSync(testRoot, { recursive: true, force: true }); + const pkgDir = path.join(testRoot, 'pkg'); + fs.mkdirSync(pkgDir, { recursive: true }); + const filePath = path.join(pkgDir, '__init__.vp'); + fs.writeFileSync(filePath, 'from .visitor import ASTVisitor\n', 'utf8'); + + const { program, serviceProvider, uri, configOptions } = buildProgram(filePath, testRoot, [TRANSPYC_INCLUDES]); // 1. 诊断:不存在的模块 `.visitor` 应标黄(warning,cat=1=Warning) const diagnostics = getDiagnostics(program, configOptions, uri); const visitorDiag = diagnostics.find( - (d) => d.range === 'L51' && d.message.includes('visitor') + (d) => d.range === 'L1' && d.message.includes('visitor') ); assert(visitorDiag, `expected unresolved '.visitor' diagnostic, got: ${JSON.stringify(diagnostics)}`); assert.strictEqual(visitorDiag.category, 1, `expected warning(1) for .visitor, got cat=${visitorDiag.category}`); // 2. 语义 token:`visitor` 和 `ASTVisitor` 都应为 undefined(白色,type=11) const tokensByLine = getSemanticTokens(program, uri); - const line51 = tokensByLine.get(50); // L51 → 行索引 50 - assert(line51, 'expected tokens on line 51'); + const line1 = tokensByLine.get(0); + assert(line1, 'expected tokens on line 1'); - const visitorToken = line51.find((t) => t.text === 'visitor'); - assert(visitorToken, 'expected "visitor" token on line 51'); + const visitorToken = line1.find((t) => t.text === 'visitor'); + assert(visitorToken, 'expected "visitor" token on line 1'); assert.strictEqual(visitorToken.tokenType, 11, `"visitor" should be undefined(11), got ${visitorToken.tokenType}`); - const astVisitorToken = line51.find((t) => t.text === 'ASTVisitor'); - assert(astVisitorToken, 'expected "ASTVisitor" token on line 51'); + const astVisitorToken = line1.find((t) => t.text === 'ASTVisitor'); + assert(astVisitorToken, 'expected "ASTVisitor" token on line 1'); assert.strictEqual( astVisitorToken.tokenType, 11, @@ -396,6 +406,7 @@ test('ast/__init__.vp 中不存在的 from .visitor import ASTVisitor 标黄且 program.dispose(); serviceProvider.dispose(); + fs.rmSync(testRoot, { recursive: true, force: true }); }); /** diff --git a/packages/vscode-vipeye/CHANGELOG.md b/packages/vscode-vipeye/CHANGELOG.md index 6bef4b35..e31ba45e 100644 --- a/packages/vscode-vipeye/CHANGELOG.md +++ b/packages/vscode-vipeye/CHANGELOG.md @@ -6,6 +6,9 @@ - **内置 rawptr**:编译器内置裸指针 typedef(`rawptr: t.CTypedef = t.CPtr`),任意工程可直接用 `rawptr[T]`,不依赖项目 includes 配置 - 类型检查:`-> None` 函数只允许裸 `return` / `return None`,`return` 值为 None 的表达式(如调用返回 None 的函数)报错 - 导入解析:builtins.vp 发现扩展到被分析文件祖先目录与 `includes/` 子目录,修复未显式配置 includes 时 `uint64` 等 typedef 报"未定义" +- 语法:类型注解支持下标实参内的 C 指针后缀(如 `list[ast.AST*]*`、`Node**`、`t.CInt*`),不再误报"`[` 未关闭" +- 类型显示:`t.CPtr` 统一显示为编译器内置 `rawptr`(如 `CPtr[list[CPtr[AST]]]` → `rawptr[list[rawptr[AST]]]`) +- 类型显示:无类型参数的别名被下标特化时保留类型实参(如 `rawptr[T]` 不再显示成裸 `rawptr`),修复 `Ty: rawptr[LLVMType]` 的成员访问丢失 `[LLVMType]` ## 1.1.412 diff --git a/packages/vscode-vipeye/src/extension.ts b/packages/vscode-vipeye/src/extension.ts index fd5f7151..97545413 100644 --- a/packages/vscode-vipeye/src/extension.ts +++ b/packages/vscode-vipeye/src/extension.ts @@ -14,6 +14,7 @@ import { commands, ExtensionContext, ExtensionMode, + extensions, Position, Range, TextEditor, @@ -51,7 +52,37 @@ let languageClient: LanguageClient | undefined; // Request a heap size of 3GB. This is reasonable for modern systems. const defaultHeapSize = 3072; +// 扩展宿主中是否已加载"贡献了 viper.* 命令的开发版"(即 --extensionDevelopmentPath 的调试副本)。 +// 判据用命令 ID 前缀而非扩展 ID:发布版与开发版的包名可能不同 +// (已安装的 TermiNexus.vipeye 与开发中的 TermiNexus.vscode-vipeye), +// 但两者贡献的命令 ID 相同,这正是命令重复注册冲突的来源。 +function isDevelopmentCopyLoaded(): boolean { + return extensions.all.some((ext) => { + if (ext.extensionMode !== ExtensionMode.Development) { + return false; + } + const contributedCommands: unknown = ext.packageJSON?.contributes?.commands; + return ( + Array.isArray(contributedCommands) && + contributedCommands.some( + (command: any) => typeof command?.command === 'string' && command.command.startsWith('viper.') + ) + ); + }); +} + export async function activate(context: ExtensionContext) { + // 调试宿主让路:launch.json 用 --extensionDevelopmentPath 加载开发版时,VS Code 不会 + // 自动停用已安装的发布版,两者在同一扩展宿主内同时激活并注册同名 'viper.*' 命令, + // 后激活者会失败: + // Activating extension ... failed: command 'viper.organizeimports' already exists. + // 因此发布版(ExtensionMode.Production)一旦发现宿主中存在"贡献了 viper.* 命令的开发版" + // (ExtensionMode.Development),就直接返回,不注册任何命令/视图/语言客户端, + // 把位置完整让给调试版。 + if (context.extensionMode === ExtensionMode.Production && isDevelopmentCopyLoaded()) { + return; + } + // Pylance detection disabled for Viper — this extension handles .vp/.vpi files, // which Pylance does not recognize, so there is no functional overlap.