fix(vipeye-internal): 无类型参数的别名被下标特化时保留类型实参显示
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

- typePrinter: rawptr[T] 不再退化为裸 rawptr,修复 Ty: rawptr[LLVMType] 成员访问丢参
- parser: 补充注解内 C 指针后缀 list[ast.AST*]* 的回归用例
- tests: viperTyNarrowDump 行号刷新/断言收窄到 rhs_val.Ty,修复 viperVariadicArgs 与 parser.test 既有失败
- vscode-vipeye: 调试模式下发布版自动让路给开发版,避免 command 'viper.*' already exists
- docs(changelog): 补充 rawptr[T] 类型实参显示修复条目
This commit is contained in:
TermiNexus committed 2026-09-18 21:59:57 +08:00
1 parent 86e858667e
commit d5398ab4e2
11 files changed
+255 -108

No files matched your search

@@ -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;
+67 -8
View File
@@ -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) 嵌套表示:
@@ -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]
@@ -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]
@@ -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', () => {
@@ -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 = (() => {
@@ -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) => {
@@ -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 解引用右值赋值兼容。
@@ -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<number, any[]> {
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 });
});
/**
+3
View File
@@ -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
+31
View File
@@ -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.