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
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:
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;
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in new issue
Block a user