feat(vipeye-internal): Viper 1.1.426 语法/语义/补全增强

把 TransPyC 现行语法与 Viper 的类型组合语义补齐到 LSP 侧(编译器 TPC/TPV 不改动):

- 类型描述符:`const` / `volatile` 可前缀任意类型注解并成为硬关键字(仅注解前缀
  合法,`const = 1` / `a: const` 报语法错误),解析层脱糖为等价的 `CConst | T` /
  `CVolatile | T`,与手写 TypeSet 完全同形;`const T*` 得 `CConst | CPtr[T]`;
  `T*` / `&x` 不再要求 import t(回退 builtins 的 rawptr);描述符按「前缀限定符」
  渲染(`const int` / `const volatile int` / `const rawptr[int]`)而不泄漏为联合成员
- 新增 `inline while` / `inline for`;`const` / `volatile` / `inline` 高亮为修饰符
- 新增内建类型名 uint / uchar,并把 rawptr / sizet / int8..uint64 / uintfast* /
  uintleast* 等内建到 builtins.vpi,任意 Viper 工程无需配置 includes
- 新增 `virtual` / `novirtual` 声明修饰符(等价 @t.CVTable / @t.NoVTable),
  含放置位置校验,并并入补全与高亮
- `(T**)x` 两级指针强转修复:指针类型后缀统一按类型表达式求值,不再被当成解引用
- 裸内置内联汇编 / LLVM IR:`__asm__` / `asminp` / `asmout` / `__llvmir__` /
  `irinp` / `irout` / `ASM_DESCR`,与 c.* / t.ASM_DESCR 写法等价
- `X | t.CPtr` 在 X 本身是指针时再包一层(HANDLE | t.CPtr = void**),
  修复 CreatePipe 一类 LPHANDLE 的「跨级指针隐式转换不允许」误报
- 补全并入 Viper 专有关键字(define / const / volatile / typedef / inline / export /
  extern / virtual / novirtual / enum / rnum / union / struct / fn / fun / func / function)
- `T*(x)`:裸模块名 + 模块内同名类归一为 CPtr[bootinfo];并按 TPC `_AtPtrSuffixCall`
  的判据把「类型原子 + 紧贴星串 + 紧贴 (」解析为调用,使
  `uint32*(info.vendor_id)[0] = res.ebx` 成为合法赋值目标
- 新增「重复声明」检查:模块级已有同名运行期变量时,函数内再用类型注解声明同名
  变量无条件报 TypeError(与 TPV AssignGenMixin 同口径);排除 CDefine 宏、CTypedef
  别名、同名函数/类与类体成员。合法写法只剩 `global x` + 无注解赋值,或换名
- 支持 `__attribute__`:装饰器 `@__attribute__(...)` 脱糖为
  `@c.Attribute(t.attr.X, ...)`;注解修饰器 `a: __attribute__(...) const int`
  脱糖为 `t.attr.X | (CConst | int)`;注解联合中的 `t.attr.*` 标记求值为 t.CAttr
  修饰符(运算/下标/赋值/展示均按被修饰的基础类型处理);值位置
  `volatile f()` / `volatile 结构体()` 前缀;`__a` 补全、括号内属性名补全与悬停、
  TextMate 高亮
- 注解位置小写 `callable[[A, B], R]` 等价 t.Callable:parser 仅在类型注解位置把裸名
  `callable` 脱糖为 `Callable`,builtins.vpi 内建 `Callable` 别名(无需 import t);
  值位置 `callable(x)` 仍是内置函数
- 版本号提升至 1.1.426 并更新 CHANGELOG;新增 6 个回归测试文件、更新 4 个,
  全量 64 套件 / 794 用例全绿(另有 1 套件 8 用例为既有 skip)
This commit is contained in:
TermiNexus committed 2026-09-27 12:50:02 +08:00
1 parent b98102b78f
commit baec16e540
37 files changed
+3719 -77

No files matched your search

+8
View File
@@ -138,6 +138,14 @@ packages/vipeye-internal/src/tests/benchmarks/.generated/
# 测试运行生成的产物(每次跑 jest 都会被重写,不入库)
packages/vipeye-internal/src/tests/__ty_narrow_dump.txt
packages/vipeye-internal/src/tests/__unary_ptr__/
packages/vipeye-internal/src/tests/__global_redeclare__/
packages/vipeye-internal/src/tests/__viper_attribute__/
packages/vipeye-internal/src/tests/__viper_attribute_completion__/
packages/vipeye-internal/src/tests/__viper_attribute_hover__/
packages/vipeye-internal/src/tests/__viper_attribute_prefix__/
packages/vipeye-internal/src/tests/__viper_volatile__/
packages/vipeye-internal/src/tests/__viper_volatile_bad__/
packages/vipeye-internal/src/tests/__viper_callable__/
# perfCompare.py cached commit bundles
/build/perfCompare/
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "vipeye-internal",
"version": "1.1.425",
"version": "1.1.426",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vipeye-internal",
"version": "1.1.425",
"version": "1.1.426",
"license": "MIT",
"dependencies": {
"@yarnpkg/fslib": "2.10.4",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "vipeye-internal",
"displayName": "vipeye",
"description": "Type checker for the Viper language",
"version": "1.1.425",
"version": "1.1.426",
"license": "MIT",
"private": true,
"files": [
@@ -161,6 +161,7 @@ import {
Variance,
combineTypes,
isAnyOrUnknown,
isCDefineType,
isClass,
isClassInstance,
isFunction,
@@ -1761,6 +1762,11 @@ export class Checker extends ParseTreeWalker {
override visitTypeAnnotation(node: TypeAnnotationNode): boolean {
this._evaluator.getType(node.d.annotation);
// Viper 扩展:函数内用类型注解重新声明模块级同名变量(`x: T` / `x: T = v`)
// → 「重复声明」(TypeError),无论是否声明 global/nonlocal。
this._validateViperGlobalRedeclaration(node);
return true;
}
@@ -2218,6 +2224,84 @@ export class Checker extends ParseTreeWalker {
);
}
// Viper 扩展:模块级已有同名**运行期变量**时,函数内不得再用**类型注解**声明同名变量。
// 与 TPV(TransPyV/App/lib/core/Gen/AssignGenMixin.vp 的「重复声明」检测)同口径,
// **无条件**失败(判据不含"类型是否一致":注解类型即便与全局完全相同也是重复声明):
// ① 无 global/nonlocal 声明 → 会把全局遮蔽成局部,属重复声明;
// ② 有 global/nonlocal 声明 → 注解与既有全局声明重复,属重复声明。
// 合法的两种写法:函数内 `global x` + **无注解**赋值 `x = v`;或换用不重名的局部名。
// 范围仅函数内——模块级自身的重复注解、类体成员声明都不检查(模块级由全局变量表登记,
// 本就不构成"遮蔽")。
private _validateViperGlobalRedeclaration(node: TypeAnnotationNode) {
if (!IsViperDialect(this._fileInfo.executionEnvironment.dialect)) {
return;
}
const nameNode = node.d.valueExpr;
if (nameNode.nodeType !== ParseNodeType.Name) {
return;
}
// 仅「变量声明语句」:`x: T`(语句本身)与 `x: T = v`(赋值的左值)。
const parent = node.parent;
if (parent?.nodeType !== ParseNodeType.Assignment && parent?.nodeType !== ParseNodeType.StatementList) {
return;
}
const scope = getScopeForNode(node);
if (scope?.type !== ScopeType.Function) {
return;
}
const globalScope = scope.getGlobalScope().scope;
if (globalScope.type !== ScopeType.Module) {
return;
}
const moduleSymbol = globalScope.lookUpSymbol(nameNode.d.value);
if (!moduleSymbol || !this._isViperGlobalRuntimeVariable(moduleSymbol)) {
return;
}
const symbolWithScope = this._evaluator.lookUpSymbolRecursive(
nameNode,
nameNode.d.value,
/* honorCodeFlow */ false
);
const hasGlobalBinding =
!!symbolWithScope?.usesGlobalBinding || !!symbolWithScope?.usesNonlocalBinding;
this._evaluator.addDiagnostic(
DiagnosticRule.reportGeneralTypeIssues,
hasGlobalBinding
? LocMessage.viperGlobalRedeclarationWithGlobal().format({ name: nameNode.d.value })
: LocMessage.viperGlobalRedeclarationWithoutGlobal().format({ name: nameNode.d.value }),
nameNode
);
}
// Viper 扩展:模块级符号是否为「运行期变量」(会占据全局存储、可被同名注解遮蔽)。
// 排除函数/类/导入别名/显式类型别名声明,以及不产生全局存储的编译期元指令:
// - CDefine 宏常量:编译期展开,无全局变量(与 TPV 的模块变量表同口径);
// - CTypedef 别名:仅登记类型,不创建全局符号。
private _isViperGlobalRuntimeVariable(moduleSymbol: Symbol): boolean {
const declarations = moduleSymbol.getDeclarations();
const isVariable = declarations.some(
(decl) =>
decl.type === DeclarationType.Variable &&
!(
decl.typeAnnotationNode?.nodeType === ParseNodeType.MemberAccess &&
decl.typeAnnotationNode.d.member.d.value === 'CTypedef'
)
);
if (!isVariable) {
return false;
}
return !isCDefineType(this._evaluator.getEffectiveTypeOfSymbol(moduleSymbol));
}
// Verifies that the target of a nonlocal statement is not a PEP 695-style
// TypeParameter. This situation results in a runtime exception.
private _validateNonlocalTypeParam(node: NameNode) {
@@ -63,8 +63,10 @@ import {
isClassInstance,
isCConstType,
isCDefineType,
isCModifierType,
isCPtrType,
isCStaticType,
isCVolatileType,
isCTypeSubclassType,
isCVoidType,
isFunctionOrOverloaded,
@@ -123,6 +125,7 @@ export function validateBinaryOperation(
leftTypeResult: TypeResult,
rightTypeResult: TypeResult,
errorNode: ExpressionNode,
leftExpression: ExpressionNode,
inferenceContext: InferenceContext | undefined,
diag: DiagnosticAddendum,
options: BinaryOperationOptions
@@ -251,14 +254,11 @@ export function validateBinaryOperation(
// `(T*)(x)`,结果类型为指针类型 CPtr[T] 的实例。
// 左操作数为真值类型(int/float 等)时不受影响:`2 * 3`、`offset * 2`
// 等真实乘法仍走正常路径。
if (
!type &&
IsViperDialect(getFileInfo(errorNode).executionEnvironment.dialect) &&
operator === OperatorType.Multiply &&
isInstantiableClass(leftType)
) {
const typeArg = isClass(leftType) && !isClassInstance(leftType) ? convertToInstance(leftType) : leftType;
type = getViperCPtrTypeForTypeArg(evaluator, errorNode, typeArg);
if (!type && IsViperDialect(getFileInfo(errorNode).executionEnvironment.dialect) && operator === OperatorType.Multiply) {
const typeArg = getViperCastTypeArg(evaluator, leftExpression, leftType);
if (typeArg) {
type = getViperCPtrTypeForTypeArg(evaluator, errorNode, typeArg);
}
}
if (!type) {
@@ -404,7 +404,10 @@ export function getTypeOfBinaryOperation(
adjustedLeftType = convertToInstantiable(evaluator.getNoneType());
}
if (isUnionableType([adjustedLeftType, adjustedRightType])) {
if (
isUnionableType([adjustedLeftType, adjustedRightType]) ||
isViperTypeDescriptorUnion(node, adjustedLeftType, adjustedRightType)
) {
if (isInstantiableClass(adjustedLeftType)) {
adjustedLeftType = specializeWithDefaultTypeArgs(adjustedLeftType);
}
@@ -470,6 +473,7 @@ export function getTypeOfBinaryOperation(
{ type: leftType, isIncomplete: leftTypeResult.isIncomplete },
{ type: rightType, isIncomplete: rightTypeResult.isIncomplete },
node,
node.d.leftExpr,
inferenceContext,
diag,
{ isLiteralMathAllowed, isTupleAddAllowed }
@@ -673,6 +677,7 @@ export function getTypeOfAugmentedAssignment(
{ type: leftSubtypeUnexpanded, isIncomplete: leftTypeResult.isIncomplete },
{ type: rightSubtypeUnexpanded, isIncomplete: rightTypeResult.isIncomplete },
node,
node.d.leftExpr,
inferenceContext,
diag,
{ isLiteralMathAllowed, isTupleAddAllowed }
@@ -756,6 +761,15 @@ export function getTypeOfUnaryOperation(
// 如 t.CInt、ast.AST),则 `T*` 是 C 指针类型表达式 CPtr[T],而非对值的解引用。
// 于是 `(T*)x`、`(T*)(x)`、`T*(x)` 中的括号内/实参里的 `T*` 都被求值为指针类型。
// 只有真值(CPtr / str / bytes / 数值等)才走解引用路径。
//
// Viper 扩展:parser 标记的「指针类型后缀」节点(`_parseViperPointerTypeSuffix`
// 打上 isViperPointerTypeSuffix)在值上下文里一律是类型表达式,不做解引用。
// 多级指针 `T**`(AST = *(*T))的外层 '*' 其操作数是内层已求值出的 CPtr[T]
// 实例(非可实例化类),若按上一条判据会落入解引用 → 得到 T,使
// `(T**)(x)` 的括号内类型退化成 T(报"T 的对象不可调用")并丢失二级指针。
if (node.d.isViperPointerTypeSuffix) {
return getTypeOfViperPointerAnnotation(evaluator, node);
}
if (isInstantiableClass(exprType)) {
return getTypeOfViperPointerAnnotation(evaluator, node);
}
@@ -984,6 +998,7 @@ function getTypeOfViperPointerAnnotation(evaluator: TypeEvaluator, node: UnaryOp
const innerType = innerResult.type;
const resultTypes: Type[] = [];
let sawInvalidType = false;
const isViperDialect = IsViperDialect(getFileInfo(node).executionEnvironment.dialect);
mapSubtypes(innerType, (subtype) => {
if (isAnyOrUnknown(subtype)) {
@@ -991,7 +1006,21 @@ function getTypeOfViperPointerAnnotation(evaluator: TypeEvaluator, node: UnaryOp
return subtype;
}
const typeArg = isClass(subtype) && !isClassInstance(subtype) ? convertToInstance(subtype) : subtype;
// Viper 扩展:类型描述符(const / volatile / static 等修饰符)不出现在指针
// 的类型参数里,原样保留在结果中:
// `const int*`(AST = `(CConst | int)*`)→ `CConst | CPtr[int]`
// 与 Viper 惯用的 `CConst | CChar | CPtr`(const char*)表示一致,
// 避免产生 `CPtr[CConst] | CPtr[int]` 这类"指针的联合"。
if (isViperDialect && isClass(subtype) && isCModifierType(subtype)) {
resultTypes.push(subtype);
return subtype;
}
// Viper 扩展:`T*` 的 T 是裸模块名、且模块内有同名类时(见
// getViperModuleSameNameClass),取该类作为类型参数:
// `bootinfo*(saved_rdi)` → CPtr[bootinfo](而非 CPtr[Module])。
const sameNameClass = getViperModuleSameNameClass(evaluator, node.d.expr, subtype);
const typeArg = sameNameClass ?? (isClass(subtype) && !isClassInstance(subtype) ? convertToInstance(subtype) : subtype);
const cptrType = getViperCPtrTypeForTypeArg(evaluator, node, typeArg);
if (cptrType) {
resultTypes.push(cptrType);
@@ -1037,32 +1066,91 @@ function getTypeOfViperAddrOf(
// 构造 CPtr[typeArg](Viper 指针类型),通过 t 模块的 CPtr 泛型类特化。
function getViperCPtrTypeForTypeArg(evaluator: TypeEvaluator, node: ParseNode, typeArg: Type): Type | undefined {
const cptrClass = getViperCPtrClass(evaluator, node);
if (cptrClass) {
return convertToInstance(ClassType.specialize(cptrClass, [typeArg]));
}
return undefined;
}
// 解析 Viper C 风格强转 `T*(x)` 左操作数 `T` 的被指向类型。
// - 常规:`T` 是可实例化类(t.CInt / bootinfo.bootinfo 等)→ 取其**实例**形态作为被指向类型;
// - 模块同名类:见 getViperModuleSameNameClass;
// - 其余(真值类型)返回 undefined,不兜底:交由常规算术校验给出真实报错。
function getViperCastTypeArg(evaluator: TypeEvaluator, leftExpression: ExpressionNode, leftType: Type): Type | undefined {
if (isInstantiableClass(leftType)) {
return isClass(leftType) && !isClassInstance(leftType) ? convertToInstance(leftType) : leftType;
}
return getViperModuleSameNameClass(evaluator, leftExpression, leftType);
}
// Viper 扩展:裸名同时是「模块名」与「模块内同名类」时,取该类(实例形态)。
// 典型场景(ViperOS VKernel/Kernel/main.vp):`import bootinfo`(模块)与
// bootinfo.vp 的 `class bootinfo` 同名,代码写作 `BootInfo = bootinfo*(saved_rdi)`。
// TPC 的 struct 注册表按短名全局查找,故裸名 bootinfo 取到该结构体类;LSP 按
// 「模块内同名类」等价还原。模块内没有同名类时返回 undefined(不兜底)。
function getViperModuleSameNameClass(
evaluator: TypeEvaluator,
exprNode: ExpressionNode,
type: Type
): Type | undefined {
if (!isModule(type) || exprNode.nodeType !== ParseNodeType.Name) {
return undefined;
}
const sameNameSymbol = ModuleType.getField(type, exprNode.d.value);
if (!sameNameSymbol) {
return undefined;
}
const memberType = evaluator.getEffectiveTypeOfSymbolForUsage(
sameNameSymbol,
/* usageNode */ undefined,
/* useLastDecl */ true
).type;
return isInstantiableClass(memberType) ? convertToInstance(memberType) : undefined;
}
// 取 Viper 的 CPtr 泛型类(可实例化形式)。
// - 首选源码 `import t` 可见的 `t.CPtr`;
// - 回退到 builtins stub 的编译器内置 `rawptr`(等价 t.CPtr,见 builtins.vpi),
// 使 `T*` / `&x` 指针语法在未 `import t` 的文件中同样可用
// (与 T* 注解、typedef、[SIZE]T 一致:不强制源码 import t)。
function getViperCPtrClass(evaluator: TypeEvaluator, node: ParseNode): ClassType | undefined {
const tSymbolWithScope = evaluator.lookUpSymbolRecursive(node, 't', /* honorCodeFlow */ true);
if (!tSymbolWithScope) {
return undefined;
if (tSymbolWithScope) {
const tEffectiveType = evaluator.getEffectiveTypeOfSymbolForUsage(
tSymbolWithScope.symbol,
/* usageNode */ undefined,
/* useLastDecl */ true
).type;
if (isModule(tEffectiveType)) {
const cptrSymbol = ModuleType.getField(tEffectiveType, 'CPtr');
if (cptrSymbol) {
const cptrType = evaluator.getEffectiveTypeOfSymbolForUsage(
cptrSymbol,
/* usageNode */ undefined,
/* useLastDecl */ true
).type;
if (isInstantiableClass(cptrType)) {
return cptrType;
}
}
}
}
const tEffectiveType = evaluator.getEffectiveTypeOfSymbolForUsage(
tSymbolWithScope.symbol,
/* usageNode */ undefined,
/* useLastDecl */ true
).type;
if (!isModule(tEffectiveType)) {
return undefined;
}
const cptrSymbol = ModuleType.getField(tEffectiveType, 'CPtr');
if (!cptrSymbol) {
return undefined;
}
const cptrType = evaluator.getEffectiveTypeOfSymbolForUsage(
cptrSymbol,
/* usageNode */ undefined,
/* useLastDecl */ true
).type;
if (isInstantiableClass(cptrType)) {
return convertToInstance(ClassType.specialize(cptrType, [typeArg]));
// 回退:builtins 中的 rawptr(= t.CPtr)。
const rawptrSymbolWithScope = evaluator.lookUpSymbolRecursive(node, 'rawptr', /* honorCodeFlow */ true);
if (rawptrSymbolWithScope) {
const rawptrType = evaluator.getEffectiveTypeOfSymbolForUsage(
rawptrSymbolWithScope.symbol,
/* usageNode */ undefined,
/* useLastDecl */ true
).type;
if (isInstantiableClass(rawptrType)) {
return rawptrType;
}
}
return undefined;
@@ -1118,6 +1206,57 @@ export function getTypeOfTernaryOperation(
return { type: combineTypes(typesToCombine), isIncomplete, typeErrors };
}
// Viper 扩展:`X | t.CPtr` 的 Viper 语义是"指向 X 的指针"(X*)。combineTypes 里的
// Struct | CPtr 折叠覆盖了 X 不是指针的情形;但当 X 自身就是指针时(如
// `HANDLE = VOIDPTR = t.CPtr`,两侧在类型层完全同形,都是 type[CPtr[Unknown]]),
// 类型层已无法区分"外层指针模板"与"被指向类型",两者会被并成一级指针,使
// `HANDLE | t.CPtr` 从 void**(void 的指针的指针)退化成 void*,于是取值
// `&handle`(void**)传参/返回会被报"跨级指针隐式转换不允许"(误报)。
// 此处按语法分辨:右操作数就是裸 CPtr 类(`t.CPtr` / `rawptr`)且左操作数已是指针
// 形态时,直接把左操作数包一层指针(保持输入的实例 / 可实例化形态)。
function maybeWrapViperCPtrUnion(
node: BinaryOperationNode,
flags: EvalFlags,
leftType: Type,
rightType: Type
): Type | undefined {
// 只在类型表达式位置生效,值上下文的 `a | b` 不受影响。
if ((flags & EvalFlags.TypeExpression) === 0) {
return undefined;
}
if (!isViperBareCPtrClassReference(node.d.rightExpr)) {
return undefined;
}
// 右操作数须确实解析为 CPtr 类(挡住用户自定义的同名成员,如 `Foo.CPtr`)。
if (!isClass(rightType) || !isCPtrType(rightType)) {
return undefined;
}
// 左操作数须已是指针;否则交给 combineTypes 的 Struct | CPtr 折叠
// (如 `Scheduler | t.CPtr` → CPtr[Scheduler]、`CConst | CChar | CPtr` → const char*)。
if (!someSubtypes(leftType, (subtype) => isClass(subtype) && isCPtrType(subtype))) {
return undefined;
}
const isInstantiableForm = TypeBase.isInstantiable(leftType);
const pointee = isClass(leftType) && isInstantiableForm ? ClassType.cloneAsInstance(leftType) : leftType;
const template = TypeBase.isInstantiable(rightType) ? rightType : ClassType.cloneAsInstantiable(rightType);
const wrapped = ClassType.specialize(template, [pointee]);
return isInstantiableForm ? wrapped : ClassType.cloneAsInstance(wrapped);
}
/** Viper:右操作数是否是裸 CPtr 类引用(`t.CPtr` / `CPtr` / `rawptr`,不带下标实参)。 */
function isViperBareCPtrClassReference(node: ExpressionNode): boolean {
const isCPtrName = (name: string) => name === 'CPtr' || name === 'rawptr';
if (node.nodeType === ParseNodeType.Name) {
return isCPtrName(node.d.value);
}
if (node.nodeType === ParseNodeType.MemberAccess) {
return node.d.member.nodeType === ParseNodeType.Name && isCPtrName(node.d.member.d.value);
}
return false;
}
function createUnionType(
evaluator: TypeEvaluator,
node: BinaryOperationNode,
@@ -1166,6 +1305,13 @@ function createUnionType(
flags | EvalFlags.InstantiableType
);
// Viper 扩展:`X | t.CPtr` 且 X 已是指针时,按"包一层指针"处理
// (见 maybeWrapViperCPtrUnion),此时结果不是联合类型,直接返回。
const wrappedPointerType = maybeWrapViperCPtrUnion(node, flags, adjustedLeftType, adjustedRightType);
if (wrappedPointerType) {
return { type: wrappedPointerType };
}
let newUnion = combineTypes([adjustedLeftType, adjustedRightType], { skipElideRedundantLiterals: true });
// Viper 扩展:若 combineTypes 已将 Struct | CPtr 折叠为 CPtr[Struct],
@@ -1392,6 +1538,29 @@ function calcLiteralForBinaryOp(operator: OperatorType, leftType: Type, rightTyp
return undefined;
}
// Viper 扩展:判断 `|` 是否为「类型描述符组合」(TypeSet),如
// `CConst | int`(const int)、`CConst | int*`(const int*,右侧求值为 CPtr[int] 实例)。
// Python 的 `|` 走联合要求两侧同为可实例化类型;而 `T*` 求值出的是**实例**
// (CPtr[int]),故 `CConst | int*` 会落到 `__or__` 分支并退化为 Unknown。
// 描述符(const / volatile / static 等)本身只是编译期修饰,不含运行时 `__or__`,
// 这里把「任一侧含描述符」的组合按联合处理,交由 combineTypes 归一。
// 注:两侧同为可实例化类型的组合(如 CConst | CChar | CPtr)本就走 isUnionableType,
// 不受此处影响。
function isViperTypeDescriptorUnion(node: ParseNode, leftType: Type, rightType: Type): boolean {
if (!IsViperDialect(getFileInfo(node).executionEnvironment.dialect)) {
return false;
}
const containsDescriptor = (type: Type): boolean => {
if (isUnion(type)) {
return type.priv.subtypes.some((subtype) => isCModifierType(subtype));
}
return isCModifierType(type);
};
return containsDescriptor(leftType) || containsDescriptor(rightType);
}
function customMetaclassSupportsMethod(type: Type, methodName: string): boolean {
if (!isInstantiableClass(type)) {
return false;
@@ -1594,7 +1763,8 @@ function validateArithmeticOperation(
if (isViperDialect) {
if (
isCStaticType(leftSubtypeUnexpanded) || isCStaticType(rightSubtypeUnexpanded) ||
isCConstType(leftSubtypeUnexpanded) || isCConstType(rightSubtypeUnexpanded)
isCConstType(leftSubtypeUnexpanded) || isCConstType(rightSubtypeUnexpanded) ||
isCVolatileType(leftSubtypeUnexpanded) || isCVolatileType(rightSubtypeUnexpanded)
) {
return UnknownType.create(isIncomplete);
}
@@ -259,8 +259,10 @@ import {
getCPtrLevel,
isCConstType,
isCStaticType,
isCVolatileType,
isCModifierType,
isPureCModifierType,
isViperAttrMarkerType,
isCEnumType,
isCEnumSubclassType,
isCTypeSubclassType,
@@ -3302,7 +3304,8 @@ export function createTypeEvaluator(
const typeContainsCPtr = findSubtype(type, (s) =>
isClassInstance(s) && isCPtrType(s)) !== undefined;
if (typeContainsCPtr && (
isCConstType(subtype) || isCStaticType(subtype) || isCVoidType(subtype) ||
isCConstType(subtype) || isCStaticType(subtype) || isCVolatileType(subtype) ||
isCVoidType(subtype) ||
ClassType.isBuiltIn(subtype, 'int')
)) {
return UnknownType.create(isIncomplete);
@@ -3453,7 +3456,8 @@ export function createTypeEvaluator(
const typeContainsCPtr = findSubtype(type, (s) =>
isClassInstance(s) && isCPtrType(s)) !== undefined;
if (typeContainsCPtr && (
isCConstType(subtype) || isCStaticType(subtype) || isCVoidType(subtype) ||
isCConstType(subtype) || isCStaticType(subtype) || isCVolatileType(subtype) ||
isCVoidType(subtype) ||
ClassType.isBuiltIn(subtype, 'int')
)) {
return UnknownType.create();
@@ -3891,6 +3895,8 @@ export function createTypeEvaluator(
// Viper 扩展:禁止函数内部隐式写入全局变量(无 global 声明且无局部声明)。
// 规则:
// 1. 全局变量存在 + 局部再次声明赋值(无 global)→ 不报错(局部优先)
// (注:**带类型注解**的局部再声明不适用本条——那属「重复声明」,
// 由 checker._validateViperGlobalRedeclaration 按 TPV 口径报 TypeError)
// 2. 全局变量存在 + 局部无声明 + 有 global + 赋值 → 不报错
// 3. 全局变量存在 + 局部无声明 + 无 global + 仅读取 → 不报错(读取处不检查)
// 4. 全局变量存在 + 局部无声明 + 无 global + 赋值 → 报错
@@ -5367,6 +5373,12 @@ export function createTypeEvaluator(
// import t,合成 `t` 无法解析时同样静默退化为 Unknown;
// TypeAlias 识别只看注解成员名文本 'CTypedef',不依赖 t 符号。
type = UnknownType.create();
} else if (node.d.isViperTypeDescriptor) {
// Viper 扩展:类型描述符 `const T` / `volatile T` 脱糖出的合成
// `CConst` / `CVolatile` 名(见 parser._parseViperTypeDescriptorPrefix)。
// 它们是 builtins stub 中的别名,正常 Viper 工程均可解析;
// 解析不到时静默退化为 Unknown,不报「未定义」(与 [SIZE]T 同一取舍)。
type = UnknownType.create();
} else {
// Viper 扩展:前向引用支持(收紧版)。
// Viper 是 AOT 语言,函数/类的定义顺序无关(类似 C 语言的前向声明),
@@ -6073,6 +6085,19 @@ export function createTypeEvaluator(
}
function getTypeOfMemberAccess(node: MemberAccessNode, flags: EvalFlags): TypeResult {
// Viper 扩展:注解位置的 `t.attr.<name>`(如 `t.attr.llvm.nounwind`)属性标记
// 求值为 t.CAttr,避免其泄漏为联合成员(`CInt | nounwind`)。仅类型表达式
// 上下文拦截,保证成员补全(`t.attr.llvm.`)等值上下文路径不受影响。
if (
(flags & EvalFlags.TypeExpression) !== 0 &&
IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect)
) {
const markerType = getViperAttributeMarkerType(node);
if (markerType) {
return { type: markerType };
}
}
// Compute flags specifically for evaluating the left expression.
let leftExprFlags = EvalFlags.MemberAccessBaseDefaults;
leftExprFlags |=
@@ -8174,7 +8199,7 @@ export function createTypeEvaluator(
// Viper 扩展:TypeSet 类型组合的下标访问语义。
// Viper 中 `|` 表示类型组合(如 CConst | str = const char*),
// 对 TypeSet 中的修饰类型(CConst/CStatic/CPtr/CVoid/CDefine/CArray)跳过下标访问,
// 对 TypeSet 中的修饰类型(CConst/CStatic/CPtr/CVoid/CDefine/CArray/CAttr)跳过下标访问,
// 让 combineTypes 只合并非修饰类型的结果(如 str/bytes 的下标返回 int/CChar)。
const viperIndexFileInfo = AnalyzerNodeInfo.getFileInfo(node);
const isViperTypeSetIndex =
@@ -8185,9 +8210,11 @@ export function createTypeEvaluator(
isClassInstance(subtype) &&
(isCConstType(subtype) ||
isCStaticType(subtype) ||
isCVolatileType(subtype) ||
isCPtrType(subtype) ||
isCVoidType(subtype) ||
isCDefineType(subtype) ||
isViperAttrMarkerType(subtype) ||
isCArrayType(subtype))
);
@@ -8215,9 +8242,11 @@ export function createTypeEvaluator(
isClassInstance(concreteSubtype) &&
(isCConstType(concreteSubtype) ||
isCStaticType(concreteSubtype) ||
isCVolatileType(concreteSubtype) ||
isCPtrType(concreteSubtype) ||
isCVoidType(concreteSubtype) ||
isCDefineType(concreteSubtype) ||
isViperAttrMarkerType(concreteSubtype) ||
isCArrayType(concreteSubtype))
) {
return undefined;
@@ -8808,7 +8837,8 @@ export function createTypeEvaluator(
// 不报错,让 mapSubtypesExpandTypeVars 处理其他子类型时获得正确方法。
if (
IsViperDialect(fileInfo.executionEnvironment.dialect) &&
(isCConstType(baseType) || isCStaticType(baseType) || isCPtrType(baseType) || isCVoidType(baseType) ||
(isCConstType(baseType) || isCStaticType(baseType) || isCVolatileType(baseType) ||
isCPtrType(baseType) || isCVoidType(baseType) ||
isCDefineType(baseType) || isCArrayType(baseType))
) {
return { type: UnknownType.create() };
@@ -9412,6 +9442,61 @@ export function createTypeEvaluator(
return undefined;
}
// Viper 扩展:解析 `t.attr.<name>` / `t.attr.<name>(...)` 属性标记 → t.CAttr 实例。
//
// 形态(与 TransPyC HandlesAssign._TAttrName / TransPyV ClassGenBase._tattr_name
// 同判据:只看最外层属性名):
// t.attr.packed (Attribute:t.attr.llvm.nounwind → 最外层 nounwind)
// t.attr.section(".x") (Call:取 func 的最外层属性名)
// 这类表达式出现在类型注解联合中(`T | t.attr.section(".x")`、
// `T | t.attr.llvm.nounwind`),或由 parser 从 `__attribute__(...)` 修饰器脱糖而来;
// 注解位置求值为 CAttr 标记——与 CConst / CVolatile 同族的修饰符
// (isViperAttrMarkerType → isCModifierType),只携带编译期元数据,
// 不参与类型检查(运算/下标/赋值按被修饰的基础类型处理),展示时也不泄漏为联合成员。
// 返回 undefined 表示该表达式不是 t.attr 属性标记。
function getViperAttributeMarkerType(node: ExpressionNode): Type | undefined {
let cur: ExpressionNode = node;
if (cur.nodeType === ParseNodeType.Call) {
cur = cur.d.leftExpr;
}
const members: string[] = [];
while (cur.nodeType === ParseNodeType.MemberAccess) {
members.unshift(cur.d.member.d.value);
cur = cur.d.leftExpr;
}
// 形态:<root>.attr.<name>[...];至少两级成员且首段为 attr。
if (cur.nodeType !== ParseNodeType.Name || members.length < 2 || members[0] !== 'attr') {
return undefined;
}
const moduleSymbolWithScope = lookUpSymbolRecursive(cur, cur.d.value, /* honorCodeFlow */ true);
if (!moduleSymbolWithScope) {
return undefined;
}
const moduleType = getEffectiveTypeOfSymbolForUsage(
moduleSymbolWithScope.symbol,
/* usageNode */ undefined,
/* useLastDecl */ true
).type;
if (!moduleType || !isModule(moduleType)) {
return undefined;
}
const attrMarkerClassSymbol = ModuleType.getField(moduleType, 'CAttr');
if (!attrMarkerClassSymbol) {
return undefined;
}
const attrMarkerClassType = getEffectiveTypeOfSymbolForUsage(
attrMarkerClassSymbol,
/* usageNode */ undefined,
/* useLastDecl */ true
).type;
if (attrMarkerClassType && isInstantiableClass(attrMarkerClassType)) {
return convertToInstance(attrMarkerClassType);
}
return undefined;
}
// Viper 扩展:判断调用节点是否为内置 enumerate 调用。
// Viper 语义下 enumerate 的迭代元素类型按 for 原迭代逻辑计算
//(str/bytes = i8* → t.CChar、CPtr[T] → T),索引类型为 t.CSizeT(size_t)。
@@ -9709,6 +9794,19 @@ export function createTypeEvaluator(
flags: EvalFlags,
inferenceContext: InferenceContext | undefined
): TypeResult {
// Viper 扩展:注解位置的 `t.attr.<name>(...)`(如 `t.attr.section(".x")`、
// 由 `__attribute__(...)` 修饰器脱糖而来)属性标记求值为 t.CAttr;
// 顺带跳过「类型注解中不允许调用」的 typeAnnotationCall 诊断。
if (
(flags & EvalFlags.TypeExpression) !== 0 &&
IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect)
) {
const markerType = getViperAttributeMarkerType(node);
if (markerType) {
return { type: markerType };
}
}
let baseTypeResult: TypeResult | undefined;
// Check for the use of `type(x)` within a type annotation. This isn't
@@ -26687,7 +26785,7 @@ export function createTypeEvaluator(
if (srcIsStrBytes) {
const destVariants = isUnion(destType) ? destType.priv.subtypes : [destType];
const hasCStringLike = destVariants.some((variant) =>
isCConstType(variant) || isCPtrType(variant) || isCVoidType(variant)
isCConstType(variant) || isCVolatileType(variant) || isCPtrType(variant) || isCVoidType(variant)
);
if (hasCStringLike) {
return true;
@@ -30,6 +30,7 @@ import {
isTypeVarTuple,
isUnknown,
isUnpacked,
isViperAttrMarkerType,
maxTypeRecursionCount,
OverloadedType,
SentinelLiteral,
@@ -572,6 +573,21 @@ function printTypeInternal(
return specialFormText;
}
// Viper 扩展:联合类型中的类型描述符按**前缀限定符**渲染,而不是
// 联合成员:`CConst | CPtr[int]` → `const rawptr[int]`、
// `CVolatile | int` → `volatile int`。见 printViperModifierPrefix。
const modifierPrefixText = printViperModifierPrefix(
type,
printTypeFlags,
returnTypeCallback,
uniqueNameMap,
recursionTypes,
recursionCount
);
if (modifierPrefixText !== undefined) {
return modifierPrefixText;
}
// If we're using "|" notation, enclose callable subtypes in parens.
const updatedPrintTypeFlags =
printTypeFlags & PrintTypeFlags.PEP604
@@ -706,6 +722,113 @@ function printTypeInternal(
}
}
// Viper 扩展:联合类型中修饰符(类型描述符)对应的展示关键字。
// Viper 用联合(TypeSet)表示「类型 + 描述符」,如 `CConst | CPtr[int]`(const int*)、
// `CVolatile | int`(volatile int)。展示时须把它们当作**前缀限定符**,
// 而不是联合成员,否则会暴露出内部表示("CConst | rawptr[int]")。
// 仅覆盖有唯一 C 关键字的修饰符;State / CExport / CPass / CDefine
// (声明标记、编译期元指令)另有语义,保持原样。
function getViperModifierKeyword(type: Type): string | undefined {
if (!isClass(type)) {
return undefined;
}
switch (type.shared.name) {
case 'CConst':
return 'const';
case 'CVolatile':
return 'volatile';
case 'CStatic':
return 'static';
case 'CRegister':
return 'register';
case 'CAuto':
return 'auto';
case 'CInline':
return 'inline';
case 'CExtern':
return 'extern';
default:
return undefined;
}
}
// Viper 扩展:把联合类型渲染为「修饰符前缀 + 其余类型」。
// `CConst | CPtr[int]` → `const rawptr[int]`
// `CVolatile | int` → `volatile int`
// `CConst | CVolatile | int` → `const volatile int`(修饰符全部提到最前)
// `CConst | int | str` → `const (int | str)`(其余多于一个时加括号,
// 避免前缀限定范围歧义)
// 无修饰符(或全是修饰符)时返回 undefined,交回常规联合渲染。
function printViperModifierPrefix(
type: UnionType,
printTypeFlags: PrintTypeFlags,
returnTypeCallback: FunctionReturnTypeCallback,
uniqueNameMap: UniqueNameMap,
recursionTypes: Type[],
recursionCount: number
): string | undefined {
const modifierKeywords: string[] = [];
const remainingSubtypes: Type[] = [];
let sawAttrMarker = false;
for (const subtype of type.priv.subtypes) {
// Viper 扩展:`__attribute__` 属性标记(t.CAttr)在源码中没有对应关键字,
// 展示时直接丢弃:`CInt | t.attr.weak` → `int`。
if (isViperAttrMarkerType(subtype)) {
sawAttrMarker = true;
continue;
}
const keyword = getViperModifierKeyword(subtype);
if (keyword !== undefined) {
modifierKeywords.push(keyword);
} else {
remainingSubtypes.push(subtype);
}
}
if ((modifierKeywords.length === 0 && !sawAttrMarker) || remainingSubtypes.length === 0) {
return undefined;
}
if (recursionCount >= maxTypeRecursionCount) {
return undefined;
}
recursionTypes.push(type);
try {
let remainingText: string;
if (remainingSubtypes.length === 1) {
remainingText = printTypeInternal(
remainingSubtypes[0],
printTypeFlags,
returnTypeCallback,
uniqueNameMap,
recursionTypes,
recursionCount + 1
);
} else {
const parts = remainingSubtypes.map((subtype) =>
printTypeInternal(
subtype,
printTypeFlags,
returnTypeCallback,
uniqueNameMap,
recursionTypes,
recursionCount + 1
)
);
remainingText = `(${parts.join(' | ')})`;
}
const modifierText = modifierKeywords.join(' ');
return modifierText ? `${modifierText} ${remainingText}` : remainingText;
} finally {
recursionTypes.pop();
}
}
function printUnionType(
type: UnionType,
printTypeFlags: PrintTypeFlags,
+26 -2
View File
@@ -3299,13 +3299,35 @@ export function isCStaticType(type: Type): boolean {
return type.shared.name === 'CStatic' || type.shared.fullName === 't.CStatic';
}
// Viper 扩展:检测类型是否为 t.CVolatile(volatile 类型限定符)。
// CVolatile 在联合类型中用于表示 volatile 修饰(如 CVolatile | CInt 表示 volatile int)。
// 与 isCConstType 对称:语义上不影响数据类型,仅在 TypeSet 联合中充当描述符。
export function isCVolatileType(type: Type): boolean {
if (!isClass(type)) {
return false;
}
return type.shared.name === 'CVolatile' || type.shared.fullName === 't.CVolatile';
}
// Viper 扩展:检测类型是否为 `t.CAttr`(`__attribute__` 属性标记)。
// 注解里的属性标记(`T | t.attr.section(".x")`)与 `__attribute__(...) T` 修饰器
// 都求值为 CAttr 实例:与 CConst / CVolatile 同族的修饰符——只携带编译期元数据,
// 不参与类型检查(运算/下标/赋值按被修饰的基础类型处理),展示时也不作为联合成员。
export function isViperAttrMarkerType(type: Type): boolean {
if (!isClass(type)) {
return false;
}
return type.shared.name === 'CAttr' || type.shared.fullName === 't.CAttr';
}
// Viper 扩展:检测类型是否为 C 修饰符/描述符(编译时元指令,非数据类型)。
// 这些类型在联合类型中应被过滤,只保留实际数据类型进行类型检查。
// 依据 t.py 中 position 属性:含 PREFIX 的为修饰符(存储类/类型限定符/前缀)。
// 特例:CDefine/CPass 虽 position=BASE,但语义是编译时元指令,也归为修饰符。
// 注意:CUnsigned/CUnsignedChar 等是数据类型(position=BASE),不是修饰符。
// 修饰符列表:CInline, CStatic, CConst, CExtern, CVolatile, CRegister,
// CExport, CAuto, CPass, CDefine, State(State 等价 CExport | CExtern)
// CExport, CAuto, CPass, CDefine, State(State 等价 CExport | CExtern),
// CAttr(`__attribute__` 属性标记,见 isViperAttrMarkerType)
export function isCModifierType(type: Type): boolean {
if (!isClass(type)) {
return false;
@@ -3322,7 +3344,8 @@ export function isCModifierType(type: Type): boolean {
name === 'CAuto' ||
name === 'CPass' ||
name === 'CDefine' ||
name === 'State'
name === 'State' ||
name === 'CAttr'
);
}
@@ -4204,6 +4227,7 @@ export function combineTypes(subtypes: Type[], options?: CombineTypesOptions): T
!isCArrayType(subtype) &&
!isCConstType(subtype) &&
!isCStaticType(subtype) &&
!isCVolatileType(subtype) &&
!isCDefineType(subtype) &&
!ClassType.isBuiltIn(subtype, 'object')
) {
@@ -79,6 +79,7 @@ import { appendArray } from '../common/collectionUtils';
import { ExecutionEnvironment } from '../common/configOptions';
import * as debug from '../common/debug';
import { fail } from '../common/debug';
import { IsViperDialect } from '../common/dialect';
import { ProgramView } from '../common/extensibility';
import { fromLSPAny, toLSPAny } from '../common/lspUtils';
import { convertOffsetToPosition, convertPositionToOffset } from '../common/positionUtils';
@@ -137,6 +138,7 @@ import {
import { DocumentSymbolCollector } from './documentSymbolCollector';
import { getImportFromTarget, getModuleNameCompletionSuggestions } from './importStatementCandidates';
import { getAutoImportText, getDocumentationPartsForTypeAndDecl } from './tooltipUtils';
import { findViperAttributeContext, getViperAttributeSymbols } from './viperAttributeUtils';
namespace Keywords {
const base: string[] = [
@@ -184,7 +186,39 @@ namespace Keywords {
const python3_15: string[] = [...python3_10, 'lazy'];
export function forVersion(version: PythonVersion): string[] {
// Viper 扩展:Viper 方言专有关键字(Vipeye 仅支持 Viper,故一律并入补全)。
// CPython 的关键字表里没有这些词,但它们在 Viper 里都是合法的语句/声明前缀,
// 不并入就会"看不见"(如 define/typedef/const 等):
// - 硬关键字:define(宏函数,等价 def)、const / volatile(类型描述符前缀);
// - 软关键字:typedef(类型别名);inline / export / extern / virtual /
// novirtual(C 风格修饰);enum / rnum / union / struct(类/枚举声明前缀);
// fn / fun / func / function(def 别名)。
// - `__attribute__`:宏属性装饰器/注解修饰器(`@__attribute__(...)` /
// `a: __attribute__(...) T`),非关键字(普通标识符),但写法特殊,
// 单独键入 `__a` 时若不列出就"看不见"。
const viper: string[] = [
'define',
'const',
'volatile',
'typedef',
'inline',
'export',
'extern',
'virtual',
'novirtual',
'enum',
'rnum',
'union',
'struct',
'fn',
'fun',
'func',
'function',
'__attribute__',
];
/** 版本对应的 CPython 关键字。 */
function versionKeywords(version: PythonVersion): string[] {
if (PythonVersion.isGreaterOrEqualTo(version, pythonVersion3_15)) {
return python3_15;
}
@@ -197,6 +231,10 @@ namespace Keywords {
return base;
}
export function forVersion(version: PythonVersion): string[] {
return [...versionKeywords(version), ...viper];
}
// Keywords that can only begin a statement and can never begin an expression,
// so they are suppressed in the expression-only slots recognized here. (Some
// of these, like `from`, can still appear *within* an expression form such as
@@ -1300,6 +1338,15 @@ export class CompletionProvider {
return undefined;
}
// Viper 扩展:`__attribute__(...)` 括号内提示属性名(t.attr 的属性方法)。
// 必须在常规 parse 树分派之前:`__attribute__(` 前缀会脱糖为
// `t.attr.X | 内层类型`,空实参时该节点不保留在 parse 树里,
// 光标位置没有可命中的节点,走常规流程会返回无关补全。
const attributeCompletions = this._getViperAttributeCompletions(offset);
if (attributeCompletions) {
return attributeCompletions;
}
let node = ParseTreeUtils.findNodeByOffset(this.parseResults.parserOutput.parseTree, offset);
// See if we're inside a string literal or an f-string statement.
@@ -3471,6 +3518,48 @@ export class CompletionProvider {
});
}
// Viper 扩展:光标位于 `__attribute__(...)` 括号内时,提示 `t.attr` 的属性名
// (section / aligned / always_inline 等;与 TPC/TPV 的 `_MakeTAttrMarker` 一致,
// 实参只用无前缀属性名,故这里只列 t.attr 直接的属性方法,不含 llvm 描述符类)。
// 上下文识别与 t.attr 查找见 viperAttributeUtils(与 hoverProvider 共用)。
private _getViperAttributeCompletions(offset: number): CompletionMap | undefined {
const moduleNode = this.parseResults.parserOutput.parseTree;
if (!IsViperDialect(AnalyzerNodeInfo.getFileInfo(moduleNode).executionEnvironment.dialect)) {
return undefined;
}
if (!findViperAttributeContext(this._fileContents, offset)) {
return undefined;
}
const attributeInfo = getViperAttributeSymbols(this.evaluator, moduleNode);
if (!attributeInfo) {
return undefined;
}
// 光标所在行的光标前文本,用于过滤已输入的部分。
const lineTextRange = this.parseResults.tokenizerOutput.lines.getItemAt(this.position.line);
const textOnLine = this._fileContents.substr(lineTextRange.start, lineTextRange.length);
const priorText = textOnLine.substr(0, this.position.character);
const priorWordIndex = priorText.search(/[\p{L}\p{N}\p{Pc}\p{Mn}\p{Mc}]+$/u);
const priorWord = priorWordIndex >= 0 ? priorText.substr(priorWordIndex) : '';
const completionMap = new CompletionMap();
attributeInfo.symbolTable.forEach((symbol, name) => {
if (completionMap.has(name)) {
return;
}
this.addSymbol(name, symbol, priorWord, completionMap, {
boundObjectOrClass: attributeInfo.attrClass,
declaredOnBoundObjectOrClass: true,
funcParensDisabled: false,
extraCommitChars: !!priorWord,
});
});
return completionMap;
}
private _addSymbols(node: ParseNode, priorWord: string, completionMap: CompletionMap) {
let curNode: ParseNode | undefined = node;
@@ -68,7 +68,8 @@ import {
getToolTipForType,
getTypeForToolTip,
getWrappedFunctionType,
} from './tooltipUtils';
} from './tooltipUtils';
import { findViperAttributeContext, getViperAttributeSymbols } from './viperAttributeUtils';
export interface HoverTextPart {
// 该部分按代码块渲染(markdown 下加代码围栏,见 convertHoverResults)。
@@ -435,6 +436,49 @@ export class HoverProvider {
return this._evaluator.printType(type, this._getPrintTypeOptions(options));
}
// Viper 扩展:光标位于 `__attribute__(...)` 实参括号内时,列出该修饰器可用的
// 全部属性名(section / aligned / always_inline / weak 等)。
// 上下文识别与 `t.attr` 成员查找复用 viperAttributeUtils,与括号内补全同源。
private _getViperAttributeHover(offset: number): HoverResults | null {
if (!this._parseResults) {
return null;
}
const moduleNode = this._parseResults.parserOutput.parseTree;
if (!IsViperDialect(getFileInfo(moduleNode).executionEnvironment.dialect)) {
return null;
}
const context = findViperAttributeContext(this._parseResults.text, offset);
if (!context) {
return null;
}
const attributeInfo = getViperAttributeSymbols(this._evaluator, moduleNode);
if (!attributeInfo) {
return null;
}
const names = Array.from(attributeInfo.symbolTable.keys()).sort();
return {
parts: [
{ text: '`__attribute__(...)` 可用属性名:' },
{ text: names.join(' '), python: true },
{
text:
'\n\n带参数者写成调用,如 `section(".text.wo")` / `aligned(16)` / ' +
'`format(printf, 1, 2)`;无参数者可直接写属性名(`packed`)或写调用(`packed()`)。' +
'\n\n属性清单来自 `t.attr`(TransPyC 的 `lib/includes/t.py`)。',
},
],
range: {
start: convertOffsetToPosition(context.nameStart, this._parseResults.tokenizerOutput.lines),
end: convertOffsetToPosition(context.openParen + 1, this._parseResults.tokenizerOutput.lines),
},
};
}
private _getHoverResult(): HoverResults | null {
throwIfCancellationRequested(this._token);
@@ -447,6 +491,14 @@ export class HoverProvider {
return null;
}
// Viper 扩展:光标位于 `__attribute__(...)` 实参括号内时,提示可用的全部属性名。
// 必须在常规 parse 树分派之前:`__attribute__(...)` 脱糖后原始调用节点不保留在
// parse 树中,光标位置(尤其实参为空/未写完时)没有可命中的节点。
const attributeHover = this._getViperAttributeHover(offset);
if (attributeHover) {
return attributeHover;
}
const node = ParseTreeUtils.findNodeByOffset(this._parseResults.parserOutput.parseTree, offset);
if (node === undefined) {
return null;
@@ -0,0 +1,111 @@
/*
* viperAttributeUtils.ts
*
* Viper `__attribute__(...)` 的共享工具:文本上下文识别 + `t.attr` 属性成员查找。
*
* 由 completionProvider(括号内属性名补全)与 hoverProvider(括号内属性名提示)共用,
* 避免两处各写一份 `t.attr` 查找逻辑导致口径漂移。
*
* 为什么用文本扫描定位上下文:`__attribute__(...)` 在 parser 里会被脱糖
* (装饰器 → `@c.Attribute(t.attr.X, ...)`;注解修饰器 → `t.attr.X | 内层类型`),
* 原始调用节点不保留在 parse 树中,光标位置(尤其实参为空或拼写未完成时)
* 没有可命中的节点。
*/
import { ScopeType } from '../analyzer/scope';
import { getScopeForNode } from '../analyzer/scopeUtils';
import { Symbol, SymbolTable } from '../analyzer/symbol';
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { ClassType, isFunctionOrOverloaded, isInstantiableClass, isModule, ModuleType } from '../analyzer/types';
import { ParseNode } from '../parser/parseNodes';
export interface ViperAttributeContext {
/** `__attribute__` 名字的起始 offset(用于 hover 范围锚点)。 */
nameStart: number;
/** `__attribute__` 名字之后 `(` 的 offset(光标必须在其右侧才算在括号内)。 */
openParen: number;
}
// 判断 `offset` 是否位于 `__attribute__(...)` 的实参括号内,是则返回上下文。
// 扫描方式:从光标向前找最近的未闭合 `(`(跳过已输入的属性名/实参),
// 该 `(` 左侧(允许空白分隔)的标识符必须是 `__attribute__`。
// 遇到行尾 / `;`(深度为 0 时)即停止,避免跨语句误判。
export function findViperAttributeContext(fileContents: string, offset: number): ViperAttributeContext | undefined {
let depth = 0;
let openParen = -1;
for (let i = offset - 1; i >= 0; i--) {
const curChar = fileContents[i];
if (curChar === ')') {
depth++;
} else if (curChar === '(') {
if (depth === 0) {
openParen = i;
break;
}
depth--;
} else if ((curChar === '\n' || curChar === ';') && depth === 0) {
break;
}
}
if (openParen < 0) {
return undefined;
}
// `(` 左侧(允许空白分隔)必须是 `__attribute__`。
let nameEnd = openParen;
while (nameEnd > 0 && /\s/.test(fileContents[nameEnd - 1])) {
nameEnd--;
}
let nameStart = nameEnd;
while (nameStart > 0 && /[\p{L}\p{N}_]/u.test(fileContents[nameStart - 1])) {
nameStart--;
}
if (fileContents.slice(nameStart, nameEnd) !== '__attribute__') {
return undefined;
}
return { nameStart, openParen };
}
// 取 `t.attr` 类**自身声明**的属性方法(只保留函数成员,排除嵌套的 llvm 描述符类等)。
// 返回 undefined 表示没有可用的 `t` 模块 / `attr` 类(如源码未 `import t`)。
// 只用 attr 类自身的符号表(不合并 MRO),避免把 CType/object 的继承成员
// (__init__ / __or__ 等)当成属性名。
export function getViperAttributeSymbols(
evaluator: TypeEvaluator,
scopeNode: ParseNode
): { symbolTable: SymbolTable; attrClass: ClassType } | undefined {
const scope = getScopeForNode(scopeNode);
if (!scope || scope.type !== ScopeType.Module) {
return undefined;
}
const tSymbol = scope.lookUpSymbol('t');
if (!tSymbol) {
return undefined;
}
const tType = evaluator.getEffectiveTypeOfSymbol(tSymbol);
if (!isModule(tType)) {
return undefined;
}
const attrSymbol = ModuleType.getField(tType, 'attr');
if (!attrSymbol) {
return undefined;
}
const attrType = evaluator.getEffectiveTypeOfSymbol(attrSymbol);
if (!isInstantiableClass(attrType)) {
return undefined;
}
const symbolTable: SymbolTable = new Map<string, Symbol>();
ClassType.getSymbolTable(attrType).forEach((symbol, name) => {
if (isFunctionOrOverloaded(evaluator.getEffectiveTypeOfSymbol(symbol))) {
symbolTable.set(name, symbol);
}
});
return { symbolTable, attrClass: attrType };
}
@@ -619,6 +619,14 @@ export namespace Localizer {
new ParameterizedString<{ name: string }>(
getRawString('Diagnostic.viperGlobalAssignmentWithoutGlobal')
);
export const viperGlobalRedeclarationWithoutGlobal = () =>
new ParameterizedString<{ name: string }>(
getRawString('Diagnostic.viperGlobalRedeclarationWithoutGlobal')
);
export const viperGlobalRedeclarationWithGlobal = () =>
new ParameterizedString<{ name: string }>(
getRawString('Diagnostic.viperGlobalRedeclarationWithGlobal')
);
export const importSymbolUnknown = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.importSymbolUnknown'));
export const incompatibleMethodOverride = () =>
@@ -1189,6 +1197,15 @@ export namespace Localizer {
export const unexpectedUnindent = () => getRawString('Diagnostic.unexpectedUnindent');
export const viperModifierRequiresDef = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperModifierRequiresDef'));
export const viperTypeDescriptorRequiresType = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperTypeDescriptorRequiresType'));
export const viperVolatileValueRequiresCall = () => getRawString('Diagnostic.viperVolatileValueRequiresCall');
export const viperVirtualModifierRequiresClassOrDef = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperVirtualModifierRequiresClassOrDef'));
export const viperVirtualDefPlacement = () => getRawString('Diagnostic.viperVirtualDefPlacement');
export const viperNoVirtualDefPlacement = () => getRawString('Diagnostic.viperNoVirtualDefPlacement');
export const viperVirtualDefInNoVirtualClass = () =>
getRawString('Diagnostic.viperVirtualDefInNoVirtualClass');
export const viperExternBodyInvalid = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperExternBodyInvalid'));
export const viperLenNoLenMethod = () =>
@@ -657,6 +657,8 @@
"viperUnresolvedImportName": "Unresolved import name \"{name}\"",
"viperUnknownMemberAccess": "Cannot access member \"{memberName}\" on unresolved type, object may be undefined",
"viperGlobalAssignmentWithoutGlobal": "Global variable \"{name}\" requires global declaration when writing inside a function",
"viperGlobalRedeclarationWithoutGlobal": "Duplicate declaration: \"{name}\" is already a module-level variable; inside a function it must not be declared again with a type annotation when no global statement is present (it would shadow the global). Rename it for an independent local variable, or add a global statement and drop this annotation to modify the global",
"viperGlobalRedeclarationWithGlobal": "Duplicate declaration: \"{name}\" is already a module-level variable and is declared again with a type annotation after a global/nonlocal statement. Drop this annotation and assign directly to modify the global",
"importSymbolUnknown": "\"{name}\" is unknown import symbol",
"incompatibleMethodOverride": "Method \"{name}\" overrides class \"{className}\" in an incompatible manner",
"inconsistentIndent": "Unindent amount does not match previous indent",
@@ -1628,6 +1630,12 @@
"unexpectedIndent": "Unexpected indentation",
"unexpectedUnindent": "Unindent not expected",
"viperModifierRequiresDef": "The modifier \"{name}\" must be used with 'def'",
"viperTypeDescriptorRequiresType": "The type descriptor \"{name}\" must be followed by a type (e.g. a: {name} int)",
"viperVolatileValueRequiresCall": "The volatile value prefix can only modify a call (e.g. a: int = volatile f() / a: int = volatile Struct())",
"viperVirtualModifierRequiresClassOrDef": "The modifier \"{name}\" must be used with 'class' or 'def'",
"viperVirtualDefPlacement": "'virtual def' must appear inside a 'class' or 'virtual class'",
"viperNoVirtualDefPlacement": "'novirtual def' must appear inside a 'class', 'virtual class' or 'novirtual class'",
"viperVirtualDefInNoVirtualClass": "'virtual def' cannot appear inside a 'novirtual class' (this class form emits no vtable)",
"viperExternBodyInvalid": "The extern function \"{name}\" must have a body of only 'pass' or 'None'",
"viperLenNoLenMethod": "Type \"{name}\" has no __len__ method and cannot be used with len()",
"unhashableDictKey": "Dictionary key must be hashable",
@@ -272,6 +272,8 @@
"viperUnresolvedImportName": "无法解析的导入名称“{name}”",
"viperUnknownMemberAccess": "无法解析类型上的成员“{memberName}”访问,对象可能未定义",
"viperGlobalAssignmentWithoutGlobal": "全局变量“{name}”在函数内写入需要 global 声明",
"viperGlobalRedeclarationWithoutGlobal": "重复声明:“{name}”已是模块级变量,函数内未声明 global 时不得再用类型注解声明同名变量(会把全局遮蔽为局部)。如需独立局部变量请换名;如需修改全局请加 global 声明并删除本行注解",
"viperGlobalRedeclarationWithGlobal": "重复声明:“{name}”已是模块级变量,此处声明 global/nonlocal 之后又用类型注解重新声明。请删除本行的类型注解,直接赋值即可修改全局",
"importSymbolUnknown": "“{name}”是未知的导入符号",
"incompatibleMethodOverride": "方法“{name}”以不兼容的方式替代类“{className}”",
"inconsistentIndent": "取消缩进量与以前的缩进不匹配",
@@ -620,6 +622,12 @@
"unexpectedIndent": "意外缩进",
"unexpectedUnindent": "不应取消缩进",
"viperModifierRequiresDef": "修饰关键字 \"{name}\" 必须与 def 搭配使用",
"viperTypeDescriptorRequiresType": "类型描述符 \"{name}\" 后必须跟类型(如 a: {name} int)",
"viperVolatileValueRequiresCall": "volatile 值前缀只能修饰调用(如 a: int = volatile f() / a: int = volatile Struct())",
"viperVirtualModifierRequiresClassOrDef": "修饰关键字 \"{name}\" 必须与 class 或 def 搭配使用",
"viperVirtualDefPlacement": "\"virtual def\" 必须位于 class 或 virtual class 内",
"viperNoVirtualDefPlacement": "\"novirtual def\" 必须位于 class、virtual class 或 novirtual class 内",
"viperVirtualDefInNoVirtualClass": "\"virtual def\" 不能位于 novirtual class 内(该形态的类不生成 vtable)",
"viperExternBodyInvalid": "extern 函数 \"{name}\" 的函数体必须为 pass 或 None",
"viperLenNoLenMethod": "类型 \"{name}\" 没有 __len__ 方法,不能用于 len()",
"unhashableDictKey": "字典密钥必须可哈希",
@@ -572,6 +572,12 @@ export interface ClassNode extends ParseNodeBase<ParseNodeType.Class> {
// Viper 扩展:由 `enum X:` 语法声明(等价 `class X(t.CEnum):`)。
// 成员可以只写成员名(裸名 / 逗号分隔的裸名),值由编译期推导。
isViperEnum: boolean;
// Viper 扩展:类声明前的 vtable 修饰关键字(`virtual class X` / `novirtual class X`)。
// 'virtual' 等价 `@t.CVTable class X`(启用多态 vtable);
// 'novirtual' 等价 `@t.NoVTable class X`(非多态,字段展平嵌入)。
// 未写修饰时为 undefined(普通 class)。
viperClassModifier?: string;
};
}
@@ -1691,6 +1697,13 @@ export interface NameNode extends ParseNodeBase<ParseNodeType.Name> {
// `T1: t.CTypedef = T2` 相同,但本写法不要求源码 import t,
// 故合成 `t` 解析失败时同样静默退化为 Unknown(不报"未定义 t")。
isViperTypedef?: boolean;
// Viper 扩展:类型描述符 `const T` / `volatile T` 脱糖
// (见 parser._parseViperTypeDescriptorPrefix)时合成出来的 `CConst` /
// `CVolatile` 名:零长度、不对应源码字符。语义与手写 `t.CConst | T` 相同,
// 但脱糖目标是 builtins stub 中的别名,不要求源码 import t;
// 名字解析不到时静默退化为 Unknown。
isViperTypeDescriptor?: boolean;
};
}
+531 -5
View File
@@ -251,6 +251,17 @@ export class Parser {
// Viper 扩展:正在解析 `[SIZE]T` 的元素类型(此时即使身处下标实参内,
// 也允许再出现 `[SIZE]T`,使 `list[[2][3]int]` 这类嵌套合法)。
private _isParsingViperBracketArrayElement = false;
// Viper 扩展:正在解析类型描述符(const / volatile)修饰的主体类型。
// 此时禁止在表达式层就地消费 C 指针后缀 `*`,使其统一冒泡到最外层
// _parseTypeAnnotation 应用 —— 保证 `const T*` 恒为 `(CConst | T)*`,
// 不因 `*` 之后是行尾还是下一行内容而在两条解析路径间摇摆
// (见 _isAtViperPointerTypeSuffix 对换行的处理)。
private _isParsingViperTypeDescriptorInner = false;
// Viper 扩展:当前所处的类声明形态,用于校验 vtable 修饰方法的出现位置:
// `virtual def` 仅允许位于 class / virtual class 内;
// `novirtual def` 允许位于 class / virtual class / novirtual class 内。
// 进入函数体(suite)时置为 undefined:方法体内的 def 不是类成员。
private _viperEnclosingClassKind: 'plain' | 'virtual' | 'novirtual' | undefined = undefined;
private _isParsingQuotedText = false;
private _isInFinallyBlock = false;
private _isInFinallyLoop = false;
@@ -443,6 +454,38 @@ export class Parser {
modifierToken.start,
modifierToken.start + modifierToken.length
);
// Viper 扩展:`virtual class X` / `novirtual class X`(类级 vtable 标记),
// `virtual def f(...)` / `novirtual def f(...)`(方法级 vtable 标记)。
// 语义等价 `@t.CVTable class X` / `@t.NoVTable class X`(见 t.vpi),
// 仅记录在节点上供编译器使用;类型层无额外语义。
// 必须在下方通用「修饰关键字 + def」分支之前,否则方法级修饰的
// 位置校验(virtual def 不得在 novirtual class 内 / 类外)会被跳过。
if (modifierText === 'virtual' || modifierText === 'novirtual') {
const nextKeywordType =
this._peekToken(1).type === TokenType.Keyword
? (this._peekToken(1) as KeywordToken).keywordType
: undefined;
if (nextKeywordType === KeywordType.Class) {
this._getNextToken();
return this._parseClassDef(/* decorators */ undefined, modifierText);
}
if (nextKeywordType === KeywordType.Def) {
this._validateViperVirtualMethodPlacement(modifierText, modifierToken);
this._getNextToken();
// 不传 defTokenOverride:`def` 关键字仍须由 _parseFunctionDef 消费。
return this._parseFunctionDef(
/* asyncToken */ undefined,
/* decorators */ undefined,
modifierText
);
}
this._addSyntaxError(
LocMessage.viperVirtualModifierRequiresClassOrDef().format({ name: modifierText }),
modifierToken
);
this._getNextToken();
return this._parseSimpleStatement();
}
if (
this._peekToken(1).type === TokenType.Keyword &&
(this._peekToken(1) as KeywordToken).keywordType === KeywordType.Def
@@ -465,6 +508,20 @@ export class Parser {
modifierToken
);
}
// Viper 扩展:`inline while cond:` / `inline for x in y:`(内联循环标记)。
// 内联是代码生成期的提示,类型层无额外语义,此处仅消费修饰关键字并
// 按普通 while / for 语句解析(高亮由 TextMate grammar 负责)。
if (modifierText === 'inline' && this._peekToken(1).type === TokenType.Keyword) {
const nextKeywordType = (this._peekToken(1) as KeywordToken).keywordType;
if (nextKeywordType === KeywordType.While) {
this._getNextToken();
return this._parseWhileStatement();
}
if (nextKeywordType === KeywordType.For) {
this._getNextToken();
return this._parseForStatement();
}
}
this._addSyntaxError(
LocMessage.viperModifierRequiresDef().format({ name: modifierText }),
modifierToken
@@ -2213,11 +2270,18 @@ export class Parser {
extendRange(suite, this._tokenizerOutput!.tokens.getItemAt(this._tokenIndex - 1));
}
} else {
// Viper 扩展:函数体内 `virtual def` / `novirtual def` 不是类成员,
// 解析函数体期间清空所处的类形态,使嵌套 def 的位置校验按"不在类内"处理。
const wasEnclosingClassKind = this._viperEnclosingClassKind;
this._viperEnclosingClassKind = undefined;
suite = this._parseSuite(/* isFunction */ true, this._parseOptions.skipFunctionAndClassBody, () => {
if (!functionTypeAnnotationToken) {
functionTypeAnnotationToken = this._getTypeAnnotationCommentText();
}
});
this._viperEnclosingClassKind = wasEnclosingClassKind;
}
this._isInExceptionGroup = wasInExceptionGroup;
@@ -2611,6 +2675,35 @@ export class Parser {
return this._parseFunctionDef(undefined, decoratorList);
} else if (nextToken.keywordType === KeywordType.Class) {
return this._parseClassDef(decoratorList);
} else if (nextToken.keywordType === KeywordType.ViperModifier) {
// Viper 扩展:装饰器之后的 vtable 修饰(如 `@Foo` + `virtual class X`
// / `virtual def f(...)`)。与 _parseStatement 的同名分支保持一致。
const modifierToken = this._peekToken();
const modifierText = this._fileContents!.slice(
modifierToken.start,
modifierToken.start + modifierToken.length
);
if (modifierText === 'virtual' || modifierText === 'novirtual') {
const afterModifierKeywordType =
this._peekToken(1).type === TokenType.Keyword
? (this._peekToken(1) as KeywordToken).keywordType
: undefined;
if (afterModifierKeywordType === KeywordType.Class) {
this._getNextToken();
return this._parseClassDef(decoratorList, modifierText);
}
if (afterModifierKeywordType === KeywordType.Def) {
this._validateViperVirtualMethodPlacement(modifierText, modifierToken);
this._getNextToken();
return this._parseFunctionDef(undefined, decoratorList, modifierText);
}
this._addSyntaxError(
LocMessage.viperVirtualModifierRequiresClassOrDef().format({ name: modifierText }),
modifierToken
);
}
// 其他修饰关键字(inline/export/extern)不在装饰器后支持,
// 保持原行为:落到下方 expectedAfterDecorator 报错。
}
}
@@ -2646,7 +2739,14 @@ export class Parser {
}
}
const decoratorNode = DecoratorNode.create(atOperator, expression);
// Viper 扩展:`@__attribute__(section(".x"), always_inline)` desugar 为
// `@c.Attribute(t.attr.section(".x"), t.attr.always_inline)`
// ——与 TPC/TPV 的 includes/ast/parser.vp `_DesugarMagicAttribute` 同形,
// class 与 def 共用(装饰器链统一走本函数);其它装饰器原样返回。
const decoratorNode = DecoratorNode.create(
atOperator,
this._desugarViperMagicAttribute(expression)
);
if (!this._consumeTokenIfType(TokenType.NewLine)) {
this._addSyntaxError(LocMessage.expectedDecoratorNewline(), this._peekToken());
@@ -2681,8 +2781,28 @@ export class Parser {
return afterName.type === TokenType.Operator && (afterName as OperatorToken).operatorType === OperatorType.Assign;
}
// Viper 扩展:校验 vtable 修饰方法(`virtual def` / `novirtual def`)的出现位置。
// - 必须位于类体内(模块级、函数体内均非法);
// - `virtual def` 不能位于 `novirtual class` 内(该形态的类不生成 vtable)。
private _validateViperVirtualMethodPlacement(modifierText: string, modifierToken: Token): void {
const enclosingClassKind = this._viperEnclosingClassKind;
if (!enclosingClassKind) {
this._addSyntaxError(
modifierText === 'virtual'
? LocMessage.viperVirtualDefPlacement()
: LocMessage.viperNoVirtualDefPlacement(),
modifierToken
);
return;
}
if (modifierText === 'virtual' && enclosingClassKind === 'novirtual') {
this._addSyntaxError(LocMessage.viperVirtualDefInNoVirtualClass(), modifierToken);
}
}
// classdef: 'class' NAME ['(' [arglist] ')'] suite
private _parseClassDef(decorators?: DecoratorNode[]): ClassNode {
private _parseClassDef(decorators?: DecoratorNode[], viperClassModifier?: string): ClassNode {
const classToken = this._getKeywordToken(KeywordType.Class);
let nameToken = this._getTokenIfIdentifier();
@@ -2714,6 +2834,12 @@ export class Parser {
}
}
// Viper 扩展:类体内的 vtable 修饰形态(供 `virtual def` / `novirtual def` 位置校验)。
const enclosingClassKind: 'plain' | 'virtual' | 'novirtual' =
viperClassModifier === 'virtual' ? 'virtual' : viperClassModifier === 'novirtual' ? 'novirtual' : 'plain';
const wasEnclosingClassKind = this._viperEnclosingClassKind;
this._viperEnclosingClassKind = enclosingClassKind;
const suite = this._parseSuite(
/* isFunction */ false,
this._parseOptions.skipFunctionAndClassBody,
@@ -2721,6 +2847,8 @@ export class Parser {
/* allowTrailingIndent */ classToken.isViperEnum === true
);
this._viperEnclosingClassKind = wasEnclosingClassKind;
const classNode = ClassNode.create(classToken, NameNode.create(nameToken), suite, typeParameters);
classNode.d.arguments = argList;
argList.forEach((arg) => {
@@ -2732,6 +2860,11 @@ export class Parser {
classNode.d.isViperEnum = true;
}
// Viper 扩展:`virtual class X` / `novirtual class X` 的修饰关键字。
if (viperClassModifier) {
classNode.d.viperClassModifier = viperClassModifier;
}
if (decorators) {
classNode.d.decorators = decorators;
if (decorators.length > 0) {
@@ -3882,7 +4015,7 @@ export class Parser {
this._parseOptions.dialect === 'viper' &&
nextOperator === OperatorType.Multiply
) {
if (this._isAtViperPointerTypeSuffix()) {
if (!this._isParsingViperTypeDescriptorInner && this._isAtViperPointerTypeSuffix()) {
leftExpr = this._parseViperPointerTypeSuffix(leftExpr);
peekToken = this._peekToken();
nextOperator = this._peekOperatorType();
@@ -3986,6 +4119,16 @@ export class Parser {
}
}
// Viper 扩展:值表达式中的 `volatile` 前缀 —— `a: int = volatile f()` /
// `a: int = volatile 结构体()`。语义:该调用(函数调用或结构体构造的
// __new__ / __before_init__ / __init__)被标记为 volatile(阻止编译器优化,
// 加 LLVM volatile)。仅 LSP 侧解析(TPC/TPV 不处理该形态):
// 消费 `volatile` 后必须紧跟「调用」形态,否则报语法错误;
// `volatile = 2` 这类裸用因下一 token 非表达式起点而不进入本分支,仍照旧失败。
if (this._isAtViperVolatileValuePrefix()) {
return this._parseViperVolatileValue();
}
let atomExpression = this._parseAtom();
if (atomExpression.nodeType === ParseNodeType.Error) {
return atomExpression;
@@ -3993,6 +4136,17 @@ export class Parser {
// Consume trailers.
while (true) {
// Viper 扩展:`T*(x)` / `T**(x)` —— 星号串与基类型、'(' 均**紧贴**时按 C 风格
// 指针强转调用解析(判据与 TPC ExprParser._AtPtrSuffixCall 同源)。
// 先把星串消费为「指针类型后缀」,紧接的 '(' 由下一轮 trailer 按调用处理,
// 产物与既有 `(T*)x` 逐字同形:Call(func = 标记的指针后缀, args = 实参)。
// 因此 `T*(x)[0] = v` 这类后续下标(乃至赋值目标)自然成立——TPC 同样把
// `T*(x)` 解析成调用,故 `uint32*(info.vendor_id)[0] = res.ebx` 是合法赋值目标。
if (this._isAtViperPointerSuffixCastCall(atomExpression)) {
atomExpression = this._parseViperPointerTypeSuffix(atomExpression);
continue;
}
// Is it a function call?
const startOfTrailerToken = this._peekToken();
if (this._consumeTokenIfType(TokenType.OpenParenthesis)) {
@@ -4409,7 +4563,30 @@ export class Parser {
}
if (nextToken.type === TokenType.Identifier) {
return NameNode.create(this._getNextToken() as IdentifierToken);
const identifierToken = this._getNextToken() as IdentifierToken;
// Viper 扩展:注解位置的裸 `callable` 等价 `Callable`(t.Callable /
// typing.Callable),支持 `callable[[A, B], R]`、`callable[..., R]`、
// `callable[[], R]`。**仅类型注解位置**脱糖:值位置的 `callable(x)`
// 仍是内置函数(isinstance 风格的 TypeIs 判定)。
// `Callable` 声明在 builtins stub 中(t.CTypedef = t.Callable),
// 故不要求源码显式 `import t`。
if (
this._parseOptions.dialect === 'viper' &&
this._isParsingTypeAnnotation &&
identifierToken.value === 'callable'
) {
return NameNode.create(
IdentifierToken.create(
identifierToken.start,
identifierToken.length,
'Callable',
identifierToken.comments
)
);
}
return NameNode.create(identifierToken);
}
if (nextToken.type === TokenType.String || nextToken.type === TokenType.FStringStart) {
@@ -5122,10 +5299,13 @@ export class Parser {
this._addSyntaxError(LocMessage.unpackedSubscriptIllegal(), startToken);
}
let result = this._parseViperBracketArrayTypeIfNeeded();
// Viper 扩展:注解主体(类型描述符前缀 / 定长数组 / 普通类型表达式)。
let result = this._parseViperTypeAnnotationCore();
// Viper 扩展:C 指针类型后缀 T* / T** / T***(须在还原 _isParsingTypeAnnotation 之前,
// 使 _parseArithmeticTerm/_parseArithmeticFactor 的星号守卫仍生效)。
// 后缀统一在最外层应用,故 `const int*` 得到 `(CConst | int)*`
// (指针指向被描述符修饰的类型),与 `int*` 中 `*` 绑定到类型的位置一致。
result = this._parseViperPointerTypeSuffix(result);
if (isUnpack) {
@@ -5138,6 +5318,302 @@ export class Parser {
return result;
}
// Viper 扩展:注解主体 = 类型描述符前缀(const / volatile)+ 类型表达式。
// 与 _parseTypeAnnotation 分离,使 C 指针后缀 `*` 由调用方在最外层统一应用:
// `const int*` 的脱糖结果 `CConst | int` 先成形,再整体加一层指针后缀。
private _parseViperTypeAnnotationCore(): ExpressionNode {
const attributeNode = this._parseViperAttributePrefix();
if (attributeNode) {
return attributeNode;
}
const descriptorNode = this._parseViperTypeDescriptorPrefix();
if (descriptorNode) {
return descriptorNode;
}
return this._parseViperBracketArrayTypeIfNeeded();
}
// Viper 扩展:注解位置的 `__attribute__(...)` 修饰器前缀。
// 解析层脱糖为等价的联合类型注解,与手写 `T | t.attr.section(".x")` 的 AST 同形
// (编译器按 AST 扫描属性:TPC HandlesAssign._ExtractGlobalAttrs /
// TPV TranslatorGenMixin._scan_global_tattr),从而完全复用既有属性标记语义:
// __attribute__(section(".x")) const int → (t.attr.section(".x") | (CConst | int))
// __attribute__(a, b) int → ((t.attr.a | t.attr.b) | int)
// 与 const / volatile 同族:只作修饰,不改变数据类型。
// 语法与装饰器形态一致(实参用无前缀属性名,逐项脱糖为 `t.attr.<name>`;
// 已是 `t.attr.X` / `c.Attribute(...)` 形态的实参原样保留,允许混写)。
// 注意:与 GCC 的 `__attribute__((a, b))` 不同,Viper 只用**单层**括号。
private _parseViperAttributePrefix(): ExpressionNode | undefined {
if (this._parseOptions.dialect !== 'viper' || !this._isAtViperAttributePrefix(0)) {
return undefined;
}
const attributeToken = this._peekToken();
this._getNextToken(); // 消费 `__attribute__`
this._getNextToken(); // 消费 '('
const argListResult = this._parseArgList();
if (!this._consumeTokenIfType(TokenType.CloseParenthesis)) {
this._addSyntaxError(LocMessage.expectedCloseParen(), this._peekToken());
this._consumeTokensUntilType([TokenType.NewLine]);
}
const markers: ExpressionNode[] = [];
argListResult.args.forEach((arg) => {
if (arg.d.argCategory === ArgCategory.Simple) {
markers.push(this._makeViperAttrMarker(arg.d.valueExpr));
}
});
// 修饰器后必须跟类型(同 const / volatile 的硬关键字约束)。
const nextToken = this._peekToken();
if (!this._isViperTypeAtomStartToken(nextToken) && !this._isAtViperAttributePrefix(0)) {
this._addSyntaxError(
LocMessage.viperTypeDescriptorRequiresType().format({ name: '__attribute__' }),
attributeToken
);
return ErrorNode.create(attributeToken, ErrorExpressionCategory.MissingExpression);
}
// 递归解析被修饰的主体(支持 `__attribute__(...) const int`、
// `const __attribute__(...) int`、多个属性前缀等任意组合)。
const innerType = this._parseViperTypeAnnotationCore();
// 合成 '|' 链('|' token 零长度,源码中不存在):marker1 | marker2 | ... | inner
let modifierNode: ExpressionNode | undefined;
for (const marker of markers) {
if (!modifierNode) {
modifierNode = marker;
continue;
}
modifierNode = this._createBinaryOperationNode(
modifierNode,
marker,
this._createViperSyntheticBarToken(attributeToken),
OperatorType.BitwiseOr
);
}
if (!modifierNode) {
return innerType;
}
return this._createBinaryOperationNode(
modifierNode,
innerType,
this._createViperSyntheticBarToken(attributeToken),
OperatorType.BitwiseOr
);
}
// Viper 扩展:合成零长度的 '|' 算子 token(源码中不存在,仅承载位置信息)。
private _createViperSyntheticBarToken(anchorToken: Token): OperatorToken {
return OperatorToken.create(
anchorToken.start + anchorToken.length,
0,
OperatorType.BitwiseOr,
/* comments */ undefined
);
}
// Viper 扩展:当前(可带 offset)是否为注解位置的 `__attribute__(` 前缀。
// `__attribute__` 是普通标识符(非关键字),后必须紧邻 '('。
private _isAtViperAttributePrefix(offset: number): boolean {
const nameToken = this._peekToken(offset);
if (nameToken.type !== TokenType.Identifier) {
return false;
}
if (this._fileContents!.slice(nameToken.start, nameToken.start + nameToken.length) !== '__attribute__') {
return false;
}
return this._peekToken(offset + 1).type === TokenType.OpenParenthesis;
}
// Viper 扩展:把 `__attribute__(...)` 的实参脱糖为 `t.attr.<name>` 标记。
// - `always_inline`(Name) → `t.attr.always_inline`
// - `section(".x")`(Call,callee 是 Name)→ `t.attr.section(".x")`
// - 已是 `t.attr.X` / `c.Attribute(...)` 形态 → 原样保留(允许混写)
// 与 TPC/TPV 的 includes/ast/parser.vp `_MakeTAttrMarker` 逐条对应;
// 成员名复用实参自身的 token(真实位置),故属性名上的跳转/悬停仍指向 t.vpi。
private _makeViperAttrMarker(argExpr: ExpressionNode): ExpressionNode {
const attrRoot = MemberAccessNode.create(
NameNode.create(IdentifierToken.create(argExpr.start, 0, 't', /* comments */ undefined)),
NameNode.create(IdentifierToken.create(argExpr.start, 0, 'attr', /* comments */ undefined))
);
if (argExpr.nodeType === ParseNodeType.Name) {
return MemberAccessNode.create(attrRoot, NameNode.create(argExpr.d.token));
}
if (argExpr.nodeType === ParseNodeType.Call && argExpr.d.leftExpr.nodeType === ParseNodeType.Name) {
const inner = MemberAccessNode.create(attrRoot, NameNode.create(argExpr.d.leftExpr.d.token));
return CallNode.create(inner, argExpr.d.args, argExpr.d.trailingComma);
}
// 已是属性标记形态(t.attr.X / c.Attribute(...)):原样保留。
return argExpr;
}
// Viper 扩展:把 `@__attribute__(...)` 装饰器脱糖为
// `@c.Attribute(t.attr.a, t.attr.b)`——与 TPC/TPV 的
// includes/ast/parser.vp `_DesugarMagicAttribute` 逐字段同形,因此 class 与 def
// 共用同一形态;其它装饰器原样返回。
private _desugarViperMagicAttribute(expr: ExpressionNode): ExpressionNode {
if (this._parseOptions.dialect !== 'viper' || expr.nodeType !== ParseNodeType.Call) {
return expr;
}
if (!this._isViperMagicAttributeCallee(expr.d.leftExpr)) {
return expr;
}
const attributeCallee = MemberAccessNode.create(
NameNode.create(IdentifierToken.create(expr.start, 0, 'c', /* comments */ undefined)),
NameNode.create(IdentifierToken.create(expr.start, 0, 'Attribute', /* comments */ undefined))
);
const newArgs = expr.d.args.map((arg) => {
if (arg.d.argCategory !== ArgCategory.Simple) {
return arg;
}
return ArgumentNode.create(undefined, this._makeViperAttrMarker(arg.d.valueExpr), ArgCategory.Simple);
});
return CallNode.create(attributeCallee, newArgs, expr.d.trailingComma);
}
// Viper 扩展:装饰器被调用者是否为 `__attribute__`(Name 形态,或 `X.__attribute__` 尾段形态)。
private _isViperMagicAttributeCallee(node: ExpressionNode): boolean {
if (node.nodeType === ParseNodeType.Name) {
return node.d.value === '__attribute__';
}
if (node.nodeType === ParseNodeType.MemberAccess) {
return node.d.member.d.value === '__attribute__';
}
return false;
}
// Viper 扩展:判定注解位置 `const` / `volatile` / `__attribute__(` 之后是否跟类型。
private _isViperTypeAtomStartToken(token: Token): boolean {
return (
token.type === TokenType.Identifier ||
token.type === TokenType.OpenBracket ||
(token.type === TokenType.Keyword &&
(token as KeywordToken).keywordType === KeywordType.ViperTypeDescriptor)
);
}
// Viper 扩展:当前是否为值表达式中的 `volatile <调用>` 前缀。
// 仅 `volatile` 有值位置用法(`const` 没有);且下一 token 必须是表达式起点
// (标识符 / '('),否则不算(`volatile = 2`、`volatile: int` 等仍走原错误路径)。
private _isAtViperVolatileValuePrefix(): boolean {
if (this._parseOptions.dialect !== 'viper' || this._isParsingTypeAnnotation) {
return false;
}
const descriptorToken = this._peekToken();
if (
descriptorToken.type !== TokenType.Keyword ||
(descriptorToken as KeywordToken).keywordType !== KeywordType.ViperTypeDescriptor
) {
return false;
}
if (
this._fileContents!.slice(descriptorToken.start, descriptorToken.start + descriptorToken.length) !==
'volatile'
) {
return false;
}
const nextTokenType = this._peekToken(1).type;
return nextTokenType === TokenType.Identifier || nextTokenType === TokenType.OpenParenthesis;
}
// Viper 扩展:消费值位置的 `volatile` 前缀,返回被修饰的调用节点。
// 语义上等价于该调用本身(volatile 仅影响代码生成,不改变类型),故直接返回
// CallNode:`volatile f()` 的类型即 f 的返回类型、`volatile 结构体()` 即结构体实例。
// 非调用形态(如 `volatile x`)报语法错误但仍返回已解析的表达式以维持错误恢复。
private _parseViperVolatileValue(): ExpressionNode {
const volatileToken = this._getNextToken();
const innerExpression = this._parseAtomExpression();
if (innerExpression.nodeType !== ParseNodeType.Call) {
this._addSyntaxError(LocMessage.viperVolatileValueRequiresCall(), volatileToken);
}
return innerExpression;
}
// Viper 扩展:类型描述符前缀 `const T` / `volatile T`。
// 解析层脱糖为等价的联合类型注解 `CConst | T` / `CVolatile | T`,
// 与手写 `a: t.CConst | CInt` 的 AST 同形,从而完全复用既有 TypeSet
// 修饰符语义(类型求值、赋值兼容、运算跳过修饰符、hover、语义高亮均一致),
// 不引入第二套修饰机制。
//
// 硬关键字:const / volatile 是 Viper 保留字(tokenizer 生成
// KeywordType.ViperTypeDescriptor),语法上只允许「类型注解前缀」这一种用法,
// 其余位置(`const = 1`、`x.volatile`、注解中不跟类型等)由 parser 报语法错误。
//
// 脱糖目标 `CConst` / `CVolatile` 是 builtins stub 中的别名
// (见 typeshed-fallback/stdlib/builtins.vpi),故不要求源码 import t。
private _parseViperTypeDescriptorPrefix(): ExpressionNode | undefined {
if (this._parseOptions.dialect !== 'viper') {
return undefined;
}
const descriptorToken = this._peekToken();
if (
descriptorToken.type !== TokenType.Keyword ||
(descriptorToken as KeywordToken).keywordType !== KeywordType.ViperTypeDescriptor
) {
return undefined;
}
const descriptorName = this._fileContents!.slice(
descriptorToken.start,
descriptorToken.start + descriptorToken.length
);
const memberName = descriptorName === 'const' ? 'CConst' : 'CVolatile';
// 硬关键字:紧随其后必须是类型原子的起点(标识符 / '[' / 另一个描述符 /
// `__attribute__(` 属性前缀),否则报语法错误(如 `const = 1`、`a: const`)。
const nextToken = this._peekToken(1);
const isTypeAtomStart =
this._isViperTypeAtomStartToken(nextToken) || this._isAtViperAttributePrefix(1);
if (!isTypeAtomStart) {
this._addSyntaxError(
LocMessage.viperTypeDescriptorRequiresType().format({ name: descriptorName }),
descriptorToken
);
this._getNextToken();
return ErrorNode.create(descriptorToken, ErrorExpressionCategory.MissingExpression);
}
// 消费描述符关键字。
this._getNextToken();
// 递归解析描述符修饰的主体:支持 `const volatile int`、`const [4]int`。
// 期间禁止在表达式层就地消费指针后缀 `*`(置 _isParsingViperTypeDescriptorInner),
// 使 `const int*` 恒为 `(CConst | int)*` —— 指针后缀由 _parseTypeAnnotation
// 统一在最外层应用(指针指向被描述符修饰的类型)。
const wasDescriptorInner = this._isParsingViperTypeDescriptorInner;
this._isParsingViperTypeDescriptorInner = true;
const innerType = this._parseViperTypeAnnotationCore();
this._isParsingViperTypeDescriptorInner = wasDescriptorInner;
// 合成描述符名:零长度、位置落在描述符关键字起点,不产生语义高亮
// (semanticTokenProvider 跳过 length <= 0 的名字),也不参与位置命中。
const modifierNameNode = NameNode.create(
IdentifierToken.create(descriptorToken.start, 0, memberName, /* comments */ undefined)
);
modifierNameNode.d.isViperTypeDescriptor = true;
// 合成 '|' 算子 token:源码中不存在,位置紧随描述符关键字,零长度。
const operatorToken = OperatorToken.create(
descriptorToken.start + descriptorToken.length,
0,
OperatorType.BitwiseOr,
/* comments */ undefined
);
return BinaryOperationNode.create(modifierNameNode, innerType, operatorToken, OperatorType.BitwiseOr);
}
// Viper 扩展:判定当前 '*'/'**' 串是否为类型注解内的 C 指针后缀。
// 判据:星号串('*' 一层 / '**' 两层,可含换行)之后紧跟"无右操作数"位置,
// 即 ']' / ',' / ')' / ':' / '=' / 换行 / ';' / 文件结束。
@@ -5187,6 +5663,56 @@ export class Parser {
return false;
}
// Viper 扩展:判定当前 '*'/'**' 串是否为 `T*(x)` 形式的 C 风格指针强转调用。
// 判据(三条同时成立,与 TPC ExprParser._AtPtrSuffixCall 同源):
// 1. base 是「类型原子」形态(Name / MemberAccess / Index);
// 2. base 与星串**紧贴**(源码字符相邻,无空白/换行);
// 3. 星串内部逐个紧贴,且其**紧贴**一个 '('。
// 为什么 '(' 不并入 _isAtViperPointerTypeSuffix 的「无右操作数位置」:`a * (b)` 是
// 合法乘法('(' 可作乘法右操作数的起点)。故叠加「前驱是类型原子 + 两侧紧贴」两条,
// 使 `a * (b + c)`、`f(x) * (y)`、`(i + 1) * (j)` 仍按乘法解析。
private _isAtViperPointerSuffixCastCall(baseExpr: ExpressionNode): boolean {
if (this._parseOptions.dialect !== 'viper') {
return false;
}
switch (baseExpr.nodeType) {
case ParseNodeType.Name:
case ParseNodeType.MemberAccess:
case ParseNodeType.Index:
break;
default:
return false;
}
// base 与星串是否紧贴:用已消费的最后一个 token 的结束位置做判据。
const prevToken = this._peekToken(-1);
let starEndOffset = prevToken.start + prevToken.length;
let offset = 0;
let starCount = 0;
while (true) {
const token = this._peekToken(offset);
const operatorType =
token.type === TokenType.Operator ? (token as OperatorToken).operatorType : undefined;
if (operatorType !== OperatorType.Multiply && operatorType !== OperatorType.Power) {
break;
}
if (token.start !== starEndOffset) {
// base 与星串之间、或星串内部有空白:非 C 风格强转写法。
return false;
}
starEndOffset = token.start + token.length;
starCount += 1;
offset += 1;
}
if (starCount === 0) {
return false;
}
const nextToken = this._peekToken(offset);
return nextToken.type === TokenType.OpenParenthesis && nextToken.start === starEndOffset;
}
// Viper 扩展:定长数组类型语法 `[SIZE]T`(等价 t.CArray[T, SIZE])与
// 不定长写法 `[]T`(等价 t.CArray[T],与 `t.CArray[T]` 单参数一致)。
// 生效位置:
@@ -646,6 +646,8 @@ enum MagicsKind {
// Viper 扩展:C 风格修饰/声明关键字处理。
// Viper 支持将 C 修饰/声明语义写成语句前缀关键字:
// inline/export/extern def f(...) —— 修饰关键字,必须与 def 搭配
// virtual/novirtual class Foo —— 类级 vtable 声明标记
// virtual/novirtual def f(...) —— 方法级 vtable 声明标记(须在类内)
// fn/fun/func/function f(...) —— def 的别名
// enum/rnum class|struct Foo —— 跳过前缀关键字(后续 class 正常解析)
// enum Foo: —— 等价 `class Foo(t.CEnum):`(枚举)
@@ -656,8 +658,8 @@ enum MagicsKind {
// 返回 'classEnum':把 enum 当作 class 关键字,并标记为 Viper 枚举
// (等价 `class Foo(t.CEnum)`,analyzer 会赋予 CEnum 枚举语义);
// 返回 'def':把 fn/fun/func/function 当作 def 关键字;
// 返回 'viperModifier':inline/export/extern 生成修饰关键字 token,由 parser
// 判断是否与 def 搭配(搭配则跳过并记录,单独使用则报错);
// 返回 'viperModifier':inline/export/extern/virtual/novirtual 生成修饰关键字
// token,由 parser 判断搭配是否合法(单独使用则报错);
// undefined:按普通标识符处理(变量名、成员名、导入名、`state:` 注解等)。
function getViperStatementKeywordAction(
text: string,
@@ -667,8 +669,14 @@ function getViperStatementKeywordAction(
precededByViperModifier: boolean
): 'skip' | 'class' | 'classEnum' | 'def' | 'viperModifier' | undefined {
const nextWord = peekViperKeywordWord(text, identifierEnd);
if (name === 'inline' || name === 'export' || name === 'extern') {
// 修饰关键字仅在语句起始处生效,避免误伤 `self.inline` 等成员名。
if (
name === 'inline' ||
name === 'export' ||
name === 'extern' ||
name === 'virtual' ||
name === 'novirtual'
) {
// 修饰关键字仅在语句起始处生效,避免误伤 `self.inline` / `self.virtual` 等成员名。
if (!isViperAtStatementStart(text, identifierStart)) {
return undefined;
}
@@ -676,7 +684,7 @@ function getViperStatementKeywordAction(
if (isViperNextCharColon(text, identifierEnd)) {
return undefined;
}
// 一律生成修饰关键字 token,由 parser 判断是否与 def 搭配。
// 一律生成修饰关键字 token,由 parser 判断搭配是否合法。
return 'viperModifier';
}
if (name === 'fn' || name === 'fun' || name === 'func' || name === 'function') {
@@ -1504,6 +1512,16 @@ export class Tokenizer {
this._tokens.push(KeywordToken.create(start, length, keywordType, this._getComments()));
} else {
const value = this._internIdentifier(text, start, end, length);
// Viper 扩展:类型描述符 const / volatile 是硬关键字(Viper 方言保留字),
// 出现在任何位置都生成关键字 token,不再作为普通标识符 ——
// 由此 `const = 1` / `x.volatile` 之类误用会在 parser 层直接报语法错误,
// 而正确的用法只有「类型注解前缀」(见 parser._parseViperTypeDescriptorPrefix)。
if (value === 'const' || value === 'volatile') {
this._tokens.push(
KeywordToken.create(start, length, KeywordType.ViperTypeDescriptor, this._getComments())
);
return true;
}
// Viper 扩展:C 风格修饰/声明关键字。
// inline/export/extern 生成修饰关键字 token(parser 判断是否配 def);
// enum/rnum/struct 在语句起始且后跟类名时按 class 关键字处理;
@@ -154,6 +154,8 @@ export const enum KeywordType {
Yield,
// Viper 扩展:inline/export/extern 等 C 风格修饰关键字(必须与 def 搭配)。
ViperModifier,
// Viper 扩展:类型描述符 const / volatile(硬关键字,只能作为类型注解前缀)。
ViperTypeDescriptor,
}
export const softKeywords = [
@@ -0,0 +1,352 @@
/*
* viperAttribute.test.ts
*
* 验证 Viper 的 `__attribute__` 与值位置 `volatile` 前缀(仅 LSP 侧实现,
* TPC/TPV 不处理):
* ① `@__attribute__(section(".x"), always_inline)` 装饰器
* → 脱糖为 `@c.Attribute(t.attr.section(".x"), t.attr.always_inline)`(class/def 通用)
* ② `a: __attribute__(section(".x")) const int = 1` 注解修饰器
* → 脱糖为 `(t.attr.section(".x") | (CConst | int))`,与 const/volatile 同族
* ③ 注解联合里的属性标记 `T | t.attr.<name>` / `T | t.attr.llvm.<name>`
* 求值为 t.CAttr(修饰符),不参与类型检查、展示不泄漏
* ④ 值位置 `volatile f()` / `volatile 结构体()`(标记调用为 volatile)
* ⑤ `__attribute__(` 括号内的属性名补全
* 负例:`volatile = 2` / `const = 1` / `volatile 1`(非调用)仍须报错。
*
* 运行:npx jest src/tests/viperAttribute.test.ts --runInBand
*/
import assert from 'assert';
import * as path from 'path';
import { CancellationToken, MarkupKind, Position } from 'vscode-languageserver';
import { ImportResolver } from '../analyzer/importResolver';
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
import { Program } from '../analyzer/program';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { UriEx } from '../common/uri/uriUtils';
import { FullAccessHost } from '../common/fullAccessHost';
import { CompletionProvider } from '../languageService/completionProvider';
import { HoverProvider } from '../languageService/hoverProvider';
import { ParseNodeType, TypeAnnotationNode } from '../parser/parseNodes';
(global as any).__rootDirectory = path.resolve();
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
function buildProgramForCode(code: string, dirName: string) {
const tempFile = new RealTempFile();
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
const configOptions = new ConfigOptions(projectRoot);
configOptions.internalTestMode = true;
configOptions.initializeTypeCheckingMode('standard');
const execEnv = new ExecutionEnvironment(
'python',
projectRoot,
configOptions.diagnosticRuleSet,
configOptions.defaultPythonVersion,
configOptions.defaultPythonPlatform,
[projectRoot],
/* skipNativeLibraries */ false,
/* defaultDialect */ 'viper'
);
configOptions.executionEnvironments = [execEnv];
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const fs = require('fs');
const testDir = path.join(__dirname, dirName);
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'attribute.vp');
fs.writeFileSync(testFile, code, 'utf8');
const uri = UriEx.file(testFile);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环
}
const parseResults = program.getParseResults(uri);
assert(parseResults, 'parse results not found');
return { program, configOptions, uri, serviceProvider, testDir, parseResults };
}
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any): { category: number; line: number; message: string }[] {
const sourceFile = program.getSourceFile(uri);
assert(sourceFile, 'source file not found');
return (sourceFile.getDiagnostics(configOptions) || []).map((diag: any) => ({
category: diag.category,
line: diag.range ? diag.range.start.line + 1 : -1,
message: diag.message,
}));
}
/** 收集注解变量的展示类型(按变量名索引)。 */
function collectAnnotatedVarTypes(program: Program, parseResults: any): Map<string, string> {
const evaluator = program.evaluator;
assert(evaluator, 'evaluator not available');
const result = new Map<string, string>();
class Collector extends ParseTreeWalker {
override visitTypeAnnotation(node: TypeAnnotationNode) {
const valueExpr: any = node.d.valueExpr;
if (valueExpr.nodeType === ParseNodeType.Name) {
const type = evaluator!.getType(valueExpr);
// 取第一个(顶层声明),避免函数内局部同名覆盖
if (!result.has(valueExpr.d.value)) {
result.set(valueExpr.d.value, type ? program.printType(type) : '(undefined)');
}
}
return true;
}
}
new Collector().walk(parseResults.parserOutput.parseTree);
return result;
}
test('__attribute__ 装饰器 / 修饰器 / 注解联合标记(LSP 侧)', () => {
const code = `import t, c
@__attribute__(section(".text.wo"), always_inline)
def decorated(a: int) -> int:
return a
@__attribute__(packed, aligned(16))
class Packed:
x: t.CInt
AttrConst: __attribute__(section(".PRVERSN")) const int = 1
P: t.CInt | t.attr.llvm.nounwind | t.attr.llvm.nobuiltin = 0
def use_p(p: P) -> int:
return p + 1
V: str | t.attr.weak = "x"
def use_v(v: V) -> int:
return v[0]
`;
const { program, configOptions, uri, serviceProvider, testDir, parseResults } = buildProgramForCode(
code,
'__viper_attribute__'
);
try {
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== __attribute__ 诊断 ===');
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
// ① `__attribute__` / `section` / `always_inline` 等不应报「未定义」
const undefinedDiags = diagnostics.filter((d) => d.message.includes('未定义') || d.message.includes('undefined'));
assert.strictEqual(
undefinedDiags.length,
0,
`__attribute__ 及其属性名不应报未定义:${JSON.stringify(undefinedDiags)}`
);
// ② 属性标记不得触发运算/下标/类型注解调用等诊断
const forbidden = diagnostics.filter(
(d) =>
d.message.includes('运算符') ||
d.message.includes('下标') ||
d.message.includes('调用表达式')
);
assert.strictEqual(forbidden.length, 0, `属性标记应被当作修饰符跳过:${JSON.stringify(forbidden)}`);
// ③ 展示不泄漏 CAttr / nounwind / nobuiltin / weak
const types = collectAnnotatedVarTypes(program, parseResults);
console.log('=== 注解变量类型 ===');
types.forEach((v, k) => console.log(` ${k}: ${v}`));
assert(
types.get('P')!.includes('CInt') && !types.get('P')!.includes('CAttr'),
`P 应展示为基础类型(属性标记被丢弃),实际: ${types.get('P')}`
);
assert(
!types.get('P')!.includes('nounwind') && !types.get('P')!.includes('nobuiltin'),
`P 不应泄漏 llvm 属性标记,实际: ${types.get('P')}`
);
assert(
types.get('AttrConst')!.includes('const') && !types.get('AttrConst')!.includes('CAttr'),
`AttrConst 应展示为 const int,实际: ${types.get('AttrConst')}`
);
assert(
!types.get('V')!.includes('CAttr') && !types.get('V')!.includes('weak'),
`V 不应泄漏属性标记,实际: ${types.get('V')}`
);
} finally {
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(testDir, { recursive: true, force: true });
}
});
test('值位置 volatile 前缀:volatile f() / volatile 结构体()', () => {
const code = `import t
class Settings:
x: int
def f() -> int:
return 1
def use() -> int:
a: int = volatile f()
b: Settings = volatile Settings()
return a
`;
const { program, configOptions, uri, serviceProvider, testDir } = buildProgramForCode(code, '__viper_volatile__');
try {
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== volatile 值前缀诊断 ===');
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
// 只关心 error/warning;category=3 是「未存取」类 hint(未使用的 import/变量)
const realDiagnostics = diagnostics.filter((d) => d.category === 0 || d.category === 1);
assert.strictEqual(
realDiagnostics.length,
0,
`volatile 值前缀不应产生错误/警告:${JSON.stringify(realDiagnostics)}`
);
} finally {
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(testDir, { recursive: true, force: true });
}
});
test('volatile / const 裸用与非调用前缀仍报错', () => {
const code = `const = 1
volatile = 2
def use() -> int:
x = volatile 1
return 0
`;
const { program, configOptions, uri, serviceProvider, testDir } = buildProgramForCode(code, '__viper_volatile_bad__');
try {
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== volatile/const 裸用诊断 ===');
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
const syntaxErrors = diagnostics.filter((d) => d.category === 0);
assert(
syntaxErrors.length >= 2,
`const/volatile 裸用与非调用前缀应报语法错误:${JSON.stringify(diagnostics)}`
);
} finally {
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(testDir, { recursive: true, force: true });
}
});
test('__attribute__(...) 括号内提示属性名', async () => {
// 光标落在 `__attribute__(` 之后(第 2 行第 20 列,即 '(' 与 ')' 之间)。
const code = `import t
Attr: __attribute__() int = 1
`;
const { program, uri, serviceProvider, testDir } = buildProgramForCode(
code,
'__viper_attribute_completion__'
);
try {
const provider = new CompletionProvider(
program as any,
uri,
Position.create(2, 20),
{ format: MarkupKind.PlainText, snippet: false, lazyEdit: false },
CancellationToken.None
);
const completions = await provider.getCompletions();
assert(completions, '补全结果为空');
const labels: string[] = completions.items.map((item) => item.label);
console.log(`=== __attribute__ 补全项 ${labels.length} 项:${labels.slice(0, 20).join(', ')} ===`);
for (const name of ['section', 'always_inline', 'aligned', 'packed', 'weak', 'noinline']) {
assert(labels.indexOf(name) >= 0, `__attribute__ 补全缺少属性 ${name}:${JSON.stringify(labels)}`);
}
// llvm 描述符类不是 __attribute__ 的属性名(属注解联合用法),不应出现在此处
assert(labels.indexOf('nounwind') < 0, `llvm 描述符类不应出现在 __attribute__ 补全里:${JSON.stringify(labels)}`);
} finally {
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(testDir, { recursive: true, force: true });
}
});
test('键入 __a 时补全里能看到 __attribute__', async () => {
// 装饰器位置(第 2 行第 4 列,`@__a` 之后)与注解位置(第 4 行第 10 列,`x: __a` 之后)。
const code = `import t
@__a
def f() -> int:
x: __a = 1
return 0
`;
const { program, uri, serviceProvider, testDir } = buildProgramForCode(code, '__viper_attribute_prefix__');
try {
for (const position of [Position.create(2, 4), Position.create(4, 10)]) {
const provider = new CompletionProvider(
program as any,
uri,
position,
{ format: MarkupKind.PlainText, snippet: false, lazyEdit: false },
CancellationToken.None
);
const completions = await provider.getCompletions();
assert(completions, '补全结果为空');
const labels: string[] = completions.items.map((item) => item.label);
assert(
labels.indexOf('__attribute__') >= 0,
`行 ${position.line} 键入 __a 应提示 __attribute__:${JSON.stringify(labels)}`
);
}
} finally {
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(testDir, { recursive: true, force: true });
}
});
test('hover 在 __attribute__(...) 括号内提示全部属性名', () => {
// 第 2 行第 22 列落在实参 `section` 上。
const code = `import t
Attr: __attribute__(section(".x")) const int = 1
`;
const { program, uri, serviceProvider, testDir } = buildProgramForCode(code, '__viper_attribute_hover__');
try {
const provider = new HoverProvider(program as any, uri, Position.create(2, 22), MarkupKind.Markdown, CancellationToken.None);
const hover = provider.getHover();
assert(hover, 'hover 结果为空');
const text = typeof hover.contents === 'string' ? hover.contents : (hover.contents as any).value;
console.log(`=== hover 内容(前 200 字)===\n${text.slice(0, 200)}`);
for (const name of ['section', 'always_inline', 'aligned', 'packed', 'weak']) {
assert(text.includes(name), `hover 应列出属性 ${name}:${text.slice(0, 300)}`);
}
} finally {
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(testDir, { recursive: true, force: true });
}
});
@@ -442,7 +442,7 @@ def f() -> int:
// 回归:完全不依赖工程 includes/builtins.vp —— C 定宽/最快/最小整型 typedef 已内建到
// typeshed-fallback/stdlib/builtins.vpi。用于修"uint32 报未定义"在找回 builtins.vp
// 失败的工程(如 Trae 中打开的文件集合/工作区根不同)里依然复现的问题。
test('无 includes/builtins.vp 时,内建 builtins.vpi 提供 uint32/sizet/int8', () => {
test('无 includes/builtins.vp 时,内建 builtins.vpi 提供 uint32/sizet/int8/uint/uchar', () => {
const tempFile = new RealTempFile();
const serviceProvider = createServiceProvider(
createFromRealFileSystem(tempFile),
@@ -462,7 +462,7 @@ test('无 includes/builtins.vp 时,内建 builtins.vpi 提供 uint32/sizet/int
def f(a: uint32, b: sizet, c: int8, d: uint64) -> uint32:
return a
def g(e: long, h: longlong, i: short, j: ushort, k: ulong, l: ulonglong) -> long:
def g(e: long, h: longlong, i: short, j: ushort, k: ulong, l: ulonglong, m: uint, n: uchar) -> long:
return e
`,
'utf8'
@@ -515,9 +515,19 @@ def g(e: long, h: longlong, i: short, j: ushort, k: ulong, l: ulonglong) -> long
);
}
// C 基本类型名:long → CLong,longlong → CLongLong,short/ushort/ulong/ulonglong
// C 基本类型名:long → CLong,longlong → CLongLong,short/ushort/ulong/ulonglong,
// uint → CUnsignedInt,uchar → CUnsignedChar
const gTypes = types.get('g') ?? [];
for (const expected of ['CLong', 'CLongLong', 'CShort', 'CUnsignedShort', 'CUnsignedLong', 'CUnsignedLongLong']) {
for (const expected of [
'CLong',
'CLongLong',
'CShort',
'CUnsignedShort',
'CUnsignedLong',
'CUnsignedLongLong',
'CUnsignedInt',
'CUnsignedChar',
]) {
assert(
gTypes.some((t) => t.includes(expected)),
`g() 参数注解应包含 ${expected},实际: ${JSON.stringify(gTypes)}`
@@ -0,0 +1,226 @@
/*
* viperCallable.test.ts
*
* 验证 Viper 的注解位置小写 `callable[...]` 等价 `t.Callable`:
* callable[[A, B], R] / callable[..., R] / callable[[], R] ≡ t.Callable[...]
* 实现:parser 在**类型注解位置**把裸名 `callable` 脱糖为 `Callable`
* (parser._parseAtom,仅 `_isParsingTypeAnnotation` 为真时),
* `Callable` 在 builtins stub 中以 `Callable: t.CTypedef = t.Callable` 声明,
* 故不要求源码 `import t`。**值位置不受影响**:`callable(x)` 仍是内置函数。
*
* 运行:npx jest src/tests/viperCallable.test.ts --runInBand
*/
import assert from 'assert';
import * as path from 'path';
import { ImportResolver } from '../analyzer/importResolver';
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
import { Program } from '../analyzer/program';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { UriEx } from '../common/uri/uriUtils';
import { FullAccessHost } from '../common/fullAccessHost';
import { FunctionNode, ParseNodeType, TypeAnnotationNode } from '../parser/parseNodes';
(global as any).__rootDirectory = path.resolve();
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
function buildProgramForCode(code: string) {
const tempFile = new RealTempFile();
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
const configOptions = new ConfigOptions(projectRoot);
configOptions.internalTestMode = true;
configOptions.initializeTypeCheckingMode('standard');
const execEnv = new ExecutionEnvironment(
'python',
projectRoot,
configOptions.diagnosticRuleSet,
configOptions.defaultPythonVersion,
configOptions.defaultPythonPlatform,
[projectRoot],
/* skipNativeLibraries */ false,
/* defaultDialect */ 'viper'
);
configOptions.executionEnvironments = [execEnv];
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const fs = require('fs');
const testDir = path.join(__dirname, '__viper_callable__');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'callable.vp');
fs.writeFileSync(testFile, code, 'utf8');
const uri = UriEx.file(testFile);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环
}
const parseResults = program.getParseResults(uri);
assert(parseResults, 'parse results not found');
return { program, configOptions, uri, serviceProvider, testDir, parseResults };
}
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any) {
const sourceFile = program.getSourceFile(uri);
assert(sourceFile, 'source file not found');
return (sourceFile.getDiagnostics(configOptions) || []).map((diag: any) => ({
category: diag.category,
line: diag.range ? diag.range.start.line + 1 : -1,
message: diag.message,
}));
}
/** 收集「模块级注解变量 + 函数参数注解 + 函数返回注解」的展示类型。 */
function collectAnnotationTypes(program: Program, parseResults: any): Map<string, string> {
const evaluator = program.evaluator;
assert(evaluator, 'evaluator not available');
const result = new Map<string, string>();
const record = (key: string, node: any) => {
if (!node || result.has(key)) {
return;
}
const type = evaluator!.getType(node);
result.set(key, type ? program.printType(type) : '(undefined)');
};
class Collector extends ParseTreeWalker {
override visitTypeAnnotation(node: TypeAnnotationNode) {
if (node.d.valueExpr.nodeType === ParseNodeType.Name) {
record(node.d.valueExpr.d.value, node.d.annotation);
}
return true;
}
override visitFunction(node: FunctionNode) {
node.d.params.forEach((param) => {
if (param.d.name && param.d.annotation) {
record(param.d.name.d.value, param.d.annotation);
}
});
record(`${node.d.name.d.value}()`, node.d.returnAnnotation);
return true;
}
}
new Collector().walk(parseResults.parserOutput.parseTree);
return result;
}
test('注解位置 callable[[A, B], R] 等价 t.Callable / Callable(无需 import t)', () => {
const code = `A: callable[[int, str], bool] = None
B: Callable[[int], bool] = None
Dotsys: callable[..., bool] = None
Empty: callable[[], bool] = None
def f(cb: callable[[int, str], bool], cb2: callable[..., bool], cb3: callable[[], bool]) -> None:
pass
def g() -> callable[[int], bool]:
return None
`;
const { program, configOptions, uri, serviceProvider, testDir, parseResults } = buildProgramForCode(code);
try {
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== callable 注解诊断 ===');
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
const realDiagnostics = diagnostics.filter((d) => d.category === 0 || d.category === 1);
assert.strictEqual(
realDiagnostics.length,
0,
`callable[...] 注解不应产生错误/警告:${JSON.stringify(realDiagnostics)}`
);
const types = collectAnnotationTypes(program, parseResults);
console.log('=== callable 注解类型 ===');
types.forEach((v, k) => console.log(` ${k}: ${v}`));
// 注解位置的求值结果是「可实例化」形式(type[...]),与 t.Callable[...] 完全一致
assert.strictEqual(
types.get('A'),
'type[(int, str) -> bool]',
`A 应为 type[(int, str) -> bool],实际: ${types.get('A')}`
);
assert.strictEqual(types.get('B'), 'type[(int) -> bool]', `B 应为 type[(int) -> bool],实际: ${types.get('B')}`);
assert.strictEqual(
types.get('Dotsys'),
'type[(...) -> bool]',
`Dotsys 应为 type[(...) -> bool],实际: ${types.get('Dotsys')}`
);
assert.strictEqual(
types.get('Empty'),
'type[() -> bool]',
`Empty 应为 type[() -> bool],实际: ${types.get('Empty')}`
);
assert.strictEqual(
types.get('cb'),
'type[(int, str) -> bool]',
`cb 应为 type[(int, str) -> bool],实际: ${types.get('cb')}`
);
assert.strictEqual(types.get('cb2'), 'type[(...) -> bool]', `cb2 应为 type[(...) -> bool],实际: ${types.get('cb2')}`);
assert.strictEqual(types.get('cb3'), 'type[() -> bool]', `cb3 应为 type[() -> bool],实际: ${types.get('cb3')}`);
assert.strictEqual(
types.get('g()'),
'type[(int) -> bool]',
`g 的返回注解应为 type[(int) -> bool],实际: ${types.get('g()')}`
);
} finally {
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(testDir, { recursive: true, force: true });
}
});
test('值位置 callable(x) 仍是内置函数(不脱糖)', () => {
const code = `def value_pos(x: object) -> None:
if callable(x):
pass
def value_pos2(x: object) -> bool:
y: bool = callable(x)
return y
def value_subscript(x: object) -> None:
z = callable[[int], bool]
`;
const { program, configOptions, uri, serviceProvider, testDir, parseResults } = buildProgramForCode(code);
try {
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== callable 值位置诊断 ===');
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
const realDiagnostics = diagnostics.filter((d) => d.category === 0 || d.category === 1);
assert.strictEqual(
realDiagnostics.length,
0,
`值位置 callable(x) 不应产生错误/警告:${JSON.stringify(realDiagnostics)}`
);
// 值位置的 `callable[[int], bool]` 不得变成可调用类型(说明未在值位置脱糖)
const types = collectAnnotationTypes(program, parseResults);
console.log('=== 值位置注解类型 ===');
types.forEach((v, k) => console.log(` ${k}: ${v}`));
const xType = types.get('x') ?? '';
assert(
!xType.includes('->'),
`值位置的 callable 不应被当作类型脱糖(x 的注解仍应为 object),实际: ${xType}`
);
} finally {
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(testDir, { recursive: true, force: true });
}
});
@@ -0,0 +1,136 @@
/*
* viperCompletionKeyword.test.ts
*
* 验证 Viper 方言专有关键字出现在补全提示里:硬关键字 define / const / volatile,
* 软关键字 typedef / inline / export / extern / virtual / novirtual / enum / rnum /
* union / struct,以及 def 别名 fn / fun / func / function。
* 这些词不在 CPython 关键字表里,若未并入 Keywords 列表则补全时"看不见"。
*
* 运行:npx jest src/tests/viperCompletionKeyword.test.ts --runInBand
*/
import assert from 'assert';
import * as path from 'path';
import { CancellationToken, CompletionItemKind, MarkupKind, Position } from 'vscode-languageserver';
import { ImportResolver } from '../analyzer/importResolver';
import { Program } from '../analyzer/program';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { UriEx } from '../common/uri/uriUtils';
import { FullAccessHost } from '../common/fullAccessHost';
import { CompletionProvider } from '../languageService/completionProvider';
(global as any).__rootDirectory = path.resolve();
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
/** 期望出现在补全里的 Viper 关键字(硬关键字 + 软关键字 + def 别名)。 */
const ViperKeywords: string[] = [
'define',
'const',
'volatile',
'typedef',
'inline',
'export',
'extern',
'virtual',
'novirtual',
'enum',
'rnum',
'union',
'struct',
'fn',
'fun',
'func',
'function',
];
function buildProgramForCode(code: string) {
const tempFile = new RealTempFile();
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
const configOptions = new ConfigOptions(projectRoot);
configOptions.internalTestMode = true;
configOptions.initializeTypeCheckingMode('standard');
const execEnv = new ExecutionEnvironment(
'python',
projectRoot,
configOptions.diagnosticRuleSet,
configOptions.defaultPythonVersion,
configOptions.defaultPythonPlatform,
[projectRoot],
/* skipNativeLibraries */ false,
/* defaultDialect */ 'viper'
);
configOptions.executionEnvironments = [execEnv];
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const fs = require('fs');
const testDir = path.join(__dirname, '__viper_completion_keyword__');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'completion_keyword.vp');
fs.writeFileSync(testFile, code, 'utf8');
const uri = UriEx.file(testFile);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环
}
return { program, configOptions, uri, serviceProvider, testDir };
}
/** 与内置符号同名的关键字:补全里会先命中该符号(如 `class function` 来自
* builtins stub),按符号种类提示而非 Keyword,故只校验"能被提示到"。 */
const SymbolCollidingKeywords = new Set(['function']);
test('Viper 补全提示包含 Viper 专有关键字', async () => {
// 第 2 行(索引 1)是函数体内空语句槽;补全位置取该行缩进之后。
const code = `def f() -> int:
return 0
`;
const { program, uri, serviceProvider, testDir } = buildProgramForCode(code);
try {
const provider = new CompletionProvider(
program,
uri,
Position.create(1, 4),
{ format: MarkupKind.PlainText, snippet: false, lazyEdit: false },
CancellationToken.None
);
const completions = await provider.getCompletions();
assert(completions, '补全结果为空');
const labels: string[] = completions.items.map((item) => item.label);
console.log(
`=== 补全项 ${labels.length} 项,Viper 关键字命中 ${
ViperKeywords.filter((keyword) => labels.indexOf(keyword) >= 0).length
}/${ViperKeywords.length} ===`
);
for (const keyword of ViperKeywords) {
const index = labels.indexOf(keyword);
assert(index >= 0, `补全里缺少 Viper 关键字 ${keyword}:${JSON.stringify(labels)}`);
if (!SymbolCollidingKeywords.has(keyword)) {
assert.strictEqual(
completions.items[index].kind,
CompletionItemKind.Keyword,
`${keyword} 的 kind 应为 Keyword`
);
}
}
} finally {
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(testDir, { recursive: true, force: true });
}
});
@@ -722,7 +722,93 @@ def test_builtins_asm_llvmir():
});
test('Viper len 接受任意值并返回 t.SizeT', () => {
// Viper 扩展:C 风格内置 asm / LLVM IR 助记符 —— `__asm__` 等价 c.Asm,
// `asminp`/`asmout` 等价 c.AsmInp/c.AsmOut,`__llvmir__` 等价 c.LLVMIR,
// `irinp`/`irout` 等价 c.LInp/c.LOut,`ASM_DESCR` 免 `t.` 前缀;它们都是隐式
// builtins,源码无需 `import c` / `import t` 即可直接使用(与 rawptr/uint32 同机制)。
test('Viper 内置 __asm__ / asminp / asmout / __llvmir__ / irinp / irout / ASM_DESCR(免 c./t. 前缀)', () => {
const tempFile = new RealTempFile();
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
const configOptions = new ConfigOptions(projectRoot);
configOptions.internalTestMode = true;
configOptions.initializeTypeCheckingMode('standard');
const execEnv = new ExecutionEnvironment(
'python',
projectRoot,
configOptions.diagnosticRuleSet,
configOptions.defaultPythonVersion,
configOptions.defaultPythonPlatform,
[projectRoot],
/* skipNativeLibraries */ false,
/* defaultDialect */ 'viper'
);
configOptions.executionEnvironments = [execEnv];
// 刻意不写任何 import:新写法全靠隐式 builtins(与用户给的 outb 示例一致)。
const code = `def outb(port: uint16, value: uint8):
__asm__(f"""mov edx, {asminp(uint32(port), ASM_DESCR.REG_ANY)}
mov eax, {asminp(uint32(value), ASM_DESCR.REG_ANY)}
out dx, al""",
op = [ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RAX])
def inb(port: uint16) -> uint8:
r: uint8 = 0
__asm__("in al, dx", out=[asmout(r, ASM_DESCR.OUTPUT_REG)], op=[ASM_DESCR.CLOBBER_RAX])
return r
def with_inp(port: uint16):
__asm__(f"mov edx, {asminp(port, ASM_DESCR.REG_ANY)}", inp=[asminp(port, ASM_DESCR.REG_ANY)])
def ir_ops(a: int, b: int) -> int:
# __llvmir__:内联 IR 模板串 + 结果类型(第二个位置参数)
r: int = __llvmir__(f"add i32 {irinp(a)}, {irinp(b)}", int)
# 输出操作数 + 破坏列表
__llvmir__(f"{irout(a)} = add i32 {irinp(a)}, {irinp(b)}", int,
out=[irout(a)], op=[ASM_DESCR.CLOBBER_MEMORY])
return r
`;
const testDir = path.join(__dirname, '__viper_asm_intrinsics__');
const fs = require('fs');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'asm_intrinsics.vp');
fs.writeFileSync(testFile, code, 'utf8');
try {
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const uri = UriEx.file(testFile);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环
}
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== asm_intrinsics.vp diagnostics ===');
diagnostics.forEach((d) => console.log(` ${d}`));
const errors = diagnostics.filter(
(d) =>
d.startsWith('[category=0]') ||
(d.startsWith('[category=1]') && (d.includes('未定义') || d.includes('无法解析')))
);
assert.strictEqual(
errors.length,
0,
`__asm__/asminp/asmout/ASM_DESCR 不应报错或未定义:${JSON.stringify(diagnostics)}`
);
} finally {
fs.rmSync(testDir, { recursive: true, force: true });
}
serviceProvider.dispose();
});
test('Viper len 接受任意值并返回 t.SizeT', () => {
const tempFile = new RealTempFile();
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
@@ -0,0 +1,163 @@
/*
* viperGlobalRedeclare.test.ts
*
* 验证 Viper 的「重复声明」规则(与 TPV AssignGenMixin 同口径):
* 模块级已有同名**运行期变量**时,函数内用**类型注解**声明同名变量无条件失败
* (TypeError,判据不含类型是否一致):
* ① 无 global/nonlocal → 会把全局遮蔽成局部 → 重复声明
* ② 有 global/nonlocal → 注解与既有全局声明重复 → 重复声明
* 合法写法:函数内 `global x` + 无注解赋值 `x = v`;或换不重名的局部名。
* 不检查:模块级自身的重复注解、类体成员、CDefine 宏常量、CTypedef 别名、同名函数。
*
* 运行:npx jest src/tests/viperGlobalRedeclare.test.ts --runInBand
*/
import assert from 'assert';
import * as path from 'path';
import { ImportResolver } from '../analyzer/importResolver';
import { Program } from '../analyzer/program';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { UriEx } from '../common/uri/uriUtils';
import { FullAccessHost } from '../common/fullAccessHost';
// 允许 vipershed-fallback(t.vpi/c.vpi 存根)被定位。
(global as any).__rootDirectory = path.resolve();
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
function buildProgramForCode(code: string) {
const tempFile = new RealTempFile();
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
const configOptions = new ConfigOptions(projectRoot);
configOptions.internalTestMode = true;
configOptions.initializeTypeCheckingMode('standard');
const execEnv = new ExecutionEnvironment(
'python',
projectRoot,
configOptions.diagnosticRuleSet,
configOptions.defaultPythonVersion,
configOptions.defaultPythonPlatform,
[projectRoot],
/* skipNativeLibraries */ false,
/* defaultDialect */ 'viper'
);
configOptions.executionEnvironments = [execEnv];
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const fs = require('fs');
const testDir = path.join(__dirname, '__global_redeclare__');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'global_redeclare.vp');
fs.writeFileSync(testFile, code, 'utf8');
const uri = UriEx.file(testFile);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环
}
const sourceFile = program.getSourceFile(uri);
assert(sourceFile, 'source file not found');
return { program, configOptions, uri, serviceProvider, sourceFile };
}
function getDiagnostics(sourceFile: any, configOptions: ConfigOptions): { category: number; line: number; message: string }[] {
return (sourceFile.getDiagnostics(configOptions) || []).map((diag: any) => ({
category: diag.category,
line: diag.range ? diag.range.start.line + 1 : -1,
message: diag.message,
}));
}
test('Viper 重复声明:函数内同名注解声明无条件失败(有/无 global 均报)', () => {
const code = `import t
Counter: int = 0
Macro: t.CDefine = 16
Alias: t.CTypedef = uint32
def helper() -> int:
return 0
def without_global() -> int:
Counter: int = 1
return 0
def with_global() -> int:
global Counter
Counter: int = 1
return 0
def bare_annotation() -> int:
Counter: int
return 0
def ok_plain_assign() -> int:
global Counter
Counter = 1
return 0
def ok_local() -> int:
Other: int = 1
return 0
def ok_macro() -> int:
Macro: int = 1
return 0
def ok_alias() -> int:
Alias: int = 1
return 0
def ok_func_name() -> int:
helper: int = 1
return 0
def ok_read() -> int:
return Counter
class Holder:
Counter: int = 2
`;
const { program, configOptions, uri, serviceProvider, sourceFile } = buildProgramForCode(code);
const diagnostics = getDiagnostics(sourceFile, configOptions);
console.log('=== 重复声明诊断 ===');
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
// 三处重复声明:L11(无 global)、L16(有 global)、L20(裸注解)
const redeclarations = diagnostics.filter((d) => d.message.includes('Counter') && d.message.includes('global'));
assert.strictEqual(
redeclarations.length,
3,
`应报 3 处重复声明(L11/L16/L20): ${JSON.stringify(redeclarations)}`
);
assert.deepStrictEqual(
redeclarations.map((d) => d.line).sort((a, b) => a - b),
[11, 16, 20],
`重复声明应落在 L11/L16/L20: ${JSON.stringify(redeclarations)}`
);
for (const diag of redeclarations) {
assert.strictEqual(diag.category, 0, `重复声明应为 error(category=0): ${JSON.stringify(diag)}`);
}
// 合法的两种写法与例外(CDefine 宏、CTypedef 别名、同名函数、类体成员、仅读取)不得报错
const unexpectedErrors = diagnostics.filter((d) => d.category === 0 || d.category === 1);
assert.strictEqual(
unexpectedErrors.length,
redeclarations.length,
`除 3 处重复声明外不应有其它错误/警告: ${JSON.stringify(unexpectedErrors)}`
);
program.dispose();
serviceProvider.dispose();
});
@@ -0,0 +1,140 @@
/*
* viperInlineLoop.test.ts
*
* 验证 Viper 的 `inline while cond:` / `inline for x in y:` 写法(内联循环标记)。
*
* 语义取舍:inline 只是代码生成期的内联提示,类型层无额外语义;
* 解析器消费修饰关键字后按普通 while / for 语句解析,不报语法错误。
* 语法高亮由 TextMate grammar(storage.modifier.viper)负责,不涉及语义 token。
*
* 运行:npx jest src/tests/viperInlineLoop.test.ts --runInBand
*/
import assert from 'assert';
import * as path from 'path';
import { ImportResolver } from '../analyzer/importResolver';
import { Program } from '../analyzer/program';
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { UriEx } from '../common/uri/uriUtils';
import { FullAccessHost } from '../common/fullAccessHost';
import { convertOffsetToPosition } from '../common/positionUtils';
import { ForNode, WhileNode } from '../parser/parseNodes';
(global as any).__rootDirectory = path.resolve();
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
function buildProgram(source: string) {
const tempFile = new RealTempFile();
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
const fs = require('fs');
const testRoot = path.join(__dirname, '__inline_loop__');
fs.rmSync(testRoot, { recursive: true, force: true });
fs.mkdirSync(testRoot, { recursive: true });
const filePath = path.join(testRoot, 'main.vp');
fs.writeFileSync(filePath, source, 'utf8');
const projectRoot = UriEx.file(testRoot);
const configOptions = new ConfigOptions(projectRoot);
configOptions.internalTestMode = true;
configOptions.initializeTypeCheckingMode('standard');
configOptions.executionEnvironments = [
new ExecutionEnvironment(
'python',
projectRoot,
configOptions.diagnosticRuleSet,
configOptions.defaultPythonVersion,
configOptions.defaultPythonPlatform,
[UriEx.file(TRANSPYC_INCLUDES)],
/* skipNativeLibraries */ false,
/* defaultDialect */ 'viper'
),
];
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const uri = UriEx.file(filePath);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环
}
const parseResults = program.getSourceFile(uri)!.getParseResults();
return { program, configOptions, parseResults, uri, serviceProvider };
}
test('inline while / inline for 可解析且不报语法错误', () => {
const source = `def f() -> int:
i: int = 0
inline while i < 3:
i += 1
inline for j in range(3):
i += j
return i
`;
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(source);
// 诊断:不得出现 Error / Warning 类别
const diagnostics = (program.getSourceFile(uri)?.getDiagnostics(configOptions) || []).map(
(d) => `[category=${d.category}] ${d.message}`
);
const errors = diagnostics.filter((d) => d.startsWith('[category=0]') || d.startsWith('[category=1]'));
assert.strictEqual(errors.length, 0, `inline 循环不应产生错误/警告,实际: ${JSON.stringify(diagnostics)}`);
// 形状:inline while → WhileNode;inline for → ForNode(修饰关键字被消费,不残留为表达式)
const statements: { line: number; kind: 'while' | 'for' }[] = [];
class LoopCollector extends ParseTreeWalker {
override visitWhile(node: WhileNode) {
statements.push({
line: convertOffsetToPosition(node.start, parseResults!.parserOutput.lines).line + 1,
kind: 'while',
});
return true;
}
override visitFor(node: ForNode) {
statements.push({
line: convertOffsetToPosition(node.start, parseResults!.parserOutput.lines).line + 1,
kind: 'for',
});
return true;
}
}
new LoopCollector().walk(parseResults!.parserOutput.parseTree);
// 行号指向 while / for 关键字自身(inline 在上一列),L3 / L5
const whileNode = statements.find((s) => s.kind === 'while');
assert(whileNode, `inline while 未解析为 WhileNode,实际: ${JSON.stringify(statements)}`);
const forNode = statements.find((s) => s.kind === 'for');
assert(forNode, `inline for 未解析为 ForNode,实际: ${JSON.stringify(statements)}`);
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__inline_loop__'), { recursive: true, force: true });
});
test('inline 用于 while / for 之外仍报「必须与 def 搭配使用」', () => {
const source = `def f() -> int:
i: int = 0
inline if i:
pass
return i
`;
const { program, configOptions, uri, serviceProvider } = buildProgram(source);
const diagnostics = (program.getSourceFile(uri)?.getDiagnostics(configOptions) || []).map(
(d) => `[category=${d.category}] ${d.message}`
);
assert(
diagnostics.some((d) => d.includes('inline') && d.includes('def')),
`inline if 应仍报修饰关键字错误,实际: ${JSON.stringify(diagnostics)}`
);
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__inline_loop__'), { recursive: true, force: true });
});
@@ -0,0 +1,417 @@
/*
* viperTypeDescriptor.test.ts
*
* 验证 Viper 类型描述符 `const T` / `volatile T`(`a: const int` / `a: volatile int`)。
*
* 实现取舍:const / volatile 是**硬关键字**(tokenizer 生成 ViperTypeDescriptor),
* 语法上只允许「类型注解前缀」一种用法,其余位置报语法错误。解析层脱糖为等价的
* 联合类型注解 `CConst | T` / `CVolatile | T`,完全复用既有 TypeSet 修饰符语义:
* - 注解保留修饰符(CConst / CVolatile),无诊断
* - `const T*` ≡ `(CConst | T)*` → `CConst | CPtr[T]`(指针后缀绑定到修饰后的类型,
* 不因 `*` 之后是行尾还是下一行内容而在两条解析路径间摇摆)
* - 脱糖目标 CConst / CVolatile 是 builtins stub 别名,不要求源码 import t
* - 硬关键字边界:`const = 1`、`a: const`(不跟类型)报语法错误
*
* 运行:npx jest src/tests/viperTypeDescriptor.test.ts --runInBand
*/
import assert from 'assert';
import * as path from 'path';
import { ImportResolver } from '../analyzer/importResolver';
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
import { Program } from '../analyzer/program';
import { EvalFlags, TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { isClass, isUnion } from '../analyzer/types';
import { convertToInstance } from '../analyzer/typeUtils';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { FullAccessHost } from '../common/fullAccessHost';
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { UriEx } from '../common/uri/uriUtils';
import { FunctionNode, ParseNodeType } from '../parser/parseNodes';
(global as any).__rootDirectory = path.resolve();
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
// 按「注解求值」的方式求类型(EvalFlags.TypeExpression + convertToInstance),
// 与 typeEvaluator.getTypeOfAnnotation 的处理一致,避免把 `X | Y` 当成值表达式
// (此时 printType 只会打印 specialForm 名 "UnionType")。
function evaluateAnnotation(evaluator: TypeEvaluator, annotation: any): any {
const raw = evaluator.getTypeOfExpression(annotation, EvalFlags.TypeExpression).type;
return raw ? convertToInstance(raw) : undefined;
}
// 结构化描述类型:联合 → 子类型名列表;单一 → 类名。不依赖 printType 格式。
function describeType(type: any): string {
if (!type) {
return '(undefined)';
}
if (isUnion(type)) {
return type.priv.subtypes.map((sub: any) => describeType(sub)).join(' | ');
}
if (isClass(type)) {
const typeArgs = type.priv.typeArgs;
if (typeArgs && typeArgs.length > 0) {
return `${type.shared.name}[${typeArgs.map((a: any) => describeType(a)).join(', ')}]`;
}
return type.shared.name;
}
return String(type.category);
}
function buildProgram(source: string) {
const tempFile = new RealTempFile();
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
const fs = require('fs');
const testRoot = path.join(__dirname, '__type_descriptor__');
fs.rmSync(testRoot, { recursive: true, force: true });
fs.mkdirSync(testRoot, { recursive: true });
const filePath = path.join(testRoot, 'main.vp');
fs.writeFileSync(filePath, source, 'utf8');
const projectRoot = UriEx.file(testRoot);
const configOptions = new ConfigOptions(projectRoot);
configOptions.internalTestMode = true;
configOptions.initializeTypeCheckingMode('standard');
const execEnv = new ExecutionEnvironment(
'python',
projectRoot,
configOptions.diagnosticRuleSet,
configOptions.defaultPythonVersion,
configOptions.defaultPythonPlatform,
[UriEx.file(TRANSPYC_INCLUDES)],
/* skipNativeLibraries */ false,
/* defaultDialect */ 'viper'
);
configOptions.executionEnvironments = [execEnv];
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const uri = UriEx.file(filePath);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环
}
const sourceFile = program.getSourceFile(uri);
const parseResults = sourceFile!.getParseResults();
return { program, configOptions, parseResults, uri, serviceProvider };
}
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any): string[] {
return (program.getSourceFile(uri)?.getDiagnostics(configOptions) || []).map(
(d) => `[category=${d.category}] ${d.message}`
);
}
// 收集函数内「变量注解」的解析类型(结构化描述,不依赖 printType 格式)。
function collectVarAnnotationTypes(program: Program, parseResults: any): Map<string, string> {
const evaluator = program.evaluator as TypeEvaluator | undefined;
assert(evaluator);
const ev = evaluator;
const result = new Map<string, string>();
class VarCollector extends ParseTreeWalker {
override visitAssignment(node: any) {
const left = node.d.leftExpr;
if (left?.nodeType === ParseNodeType.TypeAnnotation && left.d.annotation) {
const name = left.d.valueExpr?.d?.value;
if (name) {
result.set(name, describeType(evaluateAnnotation(ev, left.d.annotation)));
}
}
return true;
}
}
new VarCollector().walk(parseResults.parserOutput.parseTree);
return result;
}
// 收集函数内「变量注解」的展示形态(printType,即 hover / 诊断里给用户看的文本)。
function collectVarAnnotationDisplays(program: Program, parseResults: any): Map<string, string> {
const evaluator = program.evaluator as TypeEvaluator | undefined;
assert(evaluator);
const ev = evaluator;
const result = new Map<string, string>();
class DisplayCollector extends ParseTreeWalker {
override visitAssignment(node: any) {
const left = node.d.leftExpr;
if (left?.nodeType === ParseNodeType.TypeAnnotation && left.d.annotation) {
const name = left.d.valueExpr?.d?.value;
if (name) {
const type = evaluateAnnotation(ev, left.d.annotation);
result.set(name, type ? program.printType(type, { expandTypeAlias: true }) : '(undefined)');
}
}
return true;
}
}
new DisplayCollector().walk(parseResults.parserOutput.parseTree);
return result;
}
// 收集函数参数 / 返回注解的解析类型。
function collectFunctionAnnotationTypes(program: Program, parseResults: any): Map<string, string[]> {
const evaluator = program.evaluator as TypeEvaluator | undefined;
assert(evaluator);
const ev = evaluator;
const result = new Map<string, string[]>();
class FuncCollector extends ParseTreeWalker {
override visitFunction(node: FunctionNode) {
const funcName = node.d.name.d.value;
const list: string[] = [];
for (const param of node.d.params) {
const ann = (param as any).d?.annotation;
if (ann) {
list.push(describeType(evaluateAnnotation(ev, ann)));
}
}
if (node.d.returnAnnotation) {
list.push('->' + describeType(evaluateAnnotation(ev, node.d.returnAnnotation)));
}
result.set(funcName, list);
return true;
}
}
new FuncCollector().walk(parseResults.parserOutput.parseTree);
return result;
}
test('const int / volatile int 注解:保留修饰符且无诊断', () => {
const source = `import t
def f() -> int:
a: const int = 5
b: volatile int = 6
c: t.CConst | int = 5
d: t.CVolatile | int = 6
return a + b + c + d
`;
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(source);
const diagnostics = getDiagnostics(program, configOptions, uri);
const errors = diagnostics.filter((d) => d.startsWith('[category=0]') || d.startsWith('[category=1]'));
assert.strictEqual(errors.length, 0, `const/volatile 注解不应有错误,实际: ${JSON.stringify(diagnostics)}`);
const types = collectVarAnnotationTypes(program, parseResults!);
const aType = types.get('a') ?? '';
const bType = types.get('b') ?? '';
const cType = types.get('c') ?? '';
const dType = types.get('d') ?? '';
assert(
aType === cType,
`\`const int\` 应与手写 \`t.CConst | int\` 完全等价,实际: ${aType} vs ${cType}`
);
assert(bType === dType, `\`volatile int\` 应与手写 \`t.CVolatile | int\` 完全等价,实际: ${bType} vs ${dType}`);
assert(aType.includes('CConst'), `a: const int 应保留 CConst 修饰符,实际: ${aType}`);
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
});
test('const/volatile 支持指针、联合与参数/返回值位置', () => {
const source = `import t
def f(p: const t.CPtr, p2: t.CConst | t.CPtr, p3: const int*, p4: t.CConst | int*) -> volatile int:
q: const int | t.CPtr = p
r: volatile int* = p3
return 0
`;
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(source);
const diagnostics = getDiagnostics(program, configOptions, uri);
const errors = diagnostics.filter((d) => d.startsWith('[category=0]') || d.startsWith('[category=1]'));
assert.strictEqual(errors.length, 0, `描述符组合写法不应有错误,实际: ${JSON.stringify(diagnostics)}`);
const funcTypes = collectFunctionAnnotationTypes(program, parseResults!);
const fTypes = funcTypes.get('f') ?? [];
assert.strictEqual(
fTypes[0],
fTypes[1],
`p: const t.CPtr 应与手写 t.CConst | t.CPtr 完全等价,实际: ${fTypes[0]} vs ${fTypes[1]}`
);
assert(
fTypes[0] && fTypes[0].includes('CConst') && fTypes[0].includes('CPtr'),
`p: const t.CPtr 应为 CConst + CPtr,实际: ${fTypes[0]}`
);
// 指针后缀写法与手写等价(该组合在既有实现中即为 Unknown,此处只保证两者一致,
// 不引入新的分叉;是否让 `const T*` 折叠为 CPtr[const T] 见后续讨论)。
assert.strictEqual(
fTypes[2],
fTypes[3],
`p: const int* 应与手写 t.CConst | int* 完全等价,实际: ${fTypes[2]} vs ${fTypes[3]}`
);
assert(
fTypes.some((t) => t.startsWith('->') && t.includes('CVolatile')),
`-> volatile int 应保留 CVolatile,实际: ${JSON.stringify(fTypes)}`
);
const varTypes = collectVarAnnotationTypes(program, parseResults!);
assert(
(varTypes.get('q') ?? '').includes('CConst') && (varTypes.get('q') ?? '').includes('CPtr'),
`q: const int | t.CPtr 应保留 CConst + CPtr,实际: ${varTypes.get('q')}`
);
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
});
test('类型描述符不依赖源码 import t(合成 t 静默退化)', () => {
const source = `a: const int = 1
b: volatile int = 2
`;
const { program, configOptions, uri, serviceProvider } = buildProgram(source);
const diagnostics = getDiagnostics(program, configOptions, uri);
const undefinedDiags = diagnostics.filter((d) => d.includes('未定义') || d.includes('undefined'));
assert.strictEqual(
undefinedDiags.length,
0,
`描述符不应要求 import t(合成 t 须静默退化),实际: ${JSON.stringify(diagnostics)}`
);
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
});
test('展示形态:修饰符作为前缀限定符(const rawptr[int]),不暴露联合成员名', () => {
const source = `import t
def f() -> int:
a: const int = 5
b: volatile int = 6
c: const int* = None
d: const char* = None
e: const volatile int = 7
return a + b + e
`;
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(source);
const diagnostics = getDiagnostics(program, configOptions, uri);
const errors = diagnostics.filter((d) => d.startsWith('[category=0]') || d.startsWith('[category=1]'));
assert.strictEqual(errors.length, 0, `描述符注解不应有错误,实际: ${JSON.stringify(diagnostics)}`);
const displays = collectVarAnnotationDisplays(program, parseResults!);
assert.strictEqual(displays.get('a'), 'const int', `a 展示形态应为 const int,实际: ${displays.get('a')}`);
assert.strictEqual(displays.get('b'), 'volatile int', `b 展示形态应为 volatile int,实际: ${displays.get('b')}`);
assert.strictEqual(
displays.get('c'),
'const rawptr[int]',
`c 展示形态应为 const rawptr[int],实际: ${displays.get('c')}`
);
assert.strictEqual(
displays.get('d'),
'const rawptr[char]',
`d 展示形态应为 const rawptr[char],实际: ${displays.get('d')}`
);
assert.strictEqual(
displays.get('e'),
'const volatile int',
`e 展示形态应为 const volatile int,实际: ${displays.get('e')}`
);
// 内部表示名(CConst / CVolatile)不得泄漏到展示文本里
for (const [name, text] of displays) {
assert(
!text.includes('CConst') && !text.includes('CVolatile'),
`${name} 展示形态不应暴露内部修饰符名,实际: ${text}`
);
}
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
});
test('const / volatile 是硬关键字:只能作类型注解前缀,其余用法报语法错误', () => {
const source = `const = 1
volatile = 2
`;
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgram(source);
const diagnostics = getDiagnostics(program, configOptions, uri);
// 硬关键字:不再是普通标识符,赋值语句必然解析失败。
const syntaxErrors = diagnostics.filter((d) => d.startsWith('[category=0]'));
assert(
syntaxErrors.length > 0,
`const/volatile 作普通标识符应报语法错误(硬关键字),实际: ${JSON.stringify(diagnostics)}`
);
// 形状校验:不应再出现左值为 NameNode 'const' / 'volatile' 的普通赋值。
const assigned = new Set<string>();
class ShapeCollector extends ParseTreeWalker {
override visitAssignment(node: any) {
const left = node.d.leftExpr;
if (left?.nodeType === ParseNodeType.Name) {
assigned.add(left.d.value);
}
return true;
}
}
new ShapeCollector().walk(parseResults!.parserOutput.parseTree);
assert(!assigned.has('const'), `\`const = 1\` 不应被解析为普通赋值(硬关键字)`);
assert(!assigned.has('volatile'), `\`volatile = 2\` 不应被解析为普通赋值(硬关键字)`);
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
});
test('类型描述符后不跟类型时报语法错误', () => {
const source = `import t
def f() -> int:
a: const = 1
return 0
`;
const { program, configOptions, uri, serviceProvider } = buildProgram(source);
const diagnostics = getDiagnostics(program, configOptions, uri);
assert(
diagnostics.some((d) => d.includes('类型描述符') && d.includes('const')),
`\`a: const\` 应报「类型描述符后必须跟类型」,实际: ${JSON.stringify(diagnostics)}`
);
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
});
test('T* / const T* 不依赖源码 import t', () => {
const source = `a: int*
b: const int*
c: int**
d: const int**
`;
const { program, configOptions, uri, serviceProvider } = buildProgram(source);
const diagnostics = getDiagnostics(program, configOptions, uri);
const undefinedDiags = diagnostics.filter((d) => d.includes('未定义') || d.includes('undefined'));
assert(
!undefinedDiags.some((d) => d.includes('未定义“t”')),
`T* / const T* 不应要求 import t(rawptr 内建回退),实际: ${JSON.stringify(diagnostics)}`
);
const errors = diagnostics.filter((d) => d.startsWith('[category=0]'));
assert.strictEqual(
errors.length,
0,
`T* / const T* 不应产生错误(未 import t),实际: ${JSON.stringify(diagnostics)}`
);
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__type_descriptor__'), { recursive: true, force: true });
});
@@ -46,7 +46,9 @@ import { OperatorType } from '../parser/tokenizerTypes';
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
function buildProgramForCode(code: string) {
// extraModuleFiles:额外写入同目录的模块文件(如 `bootinfo.vp`),并把该目录加入
// 额外搜索路径,用于覆盖「import 同目录模块」的场景。不传时行为与原先完全一致。
function buildProgramForCode(code: string, extraModuleFiles?: Record<string, string>) {
const tempFile = new RealTempFile();
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
@@ -54,13 +56,27 @@ function buildProgramForCode(code: string) {
configOptions.internalTestMode = true;
configOptions.initializeTypeCheckingMode('standard');
const fs = require('fs');
const testDir = path.join(__dirname, '__unary_ptr__');
fs.mkdirSync(testDir, { recursive: true });
if (extraModuleFiles) {
for (const [name, content] of Object.entries(extraModuleFiles)) {
fs.writeFileSync(path.join(testDir, name), content, 'utf8');
}
}
const extraPaths = [projectRoot];
if (extraModuleFiles) {
extraPaths.push(UriEx.file(testDir));
}
const execEnv = new ExecutionEnvironment(
'python',
projectRoot,
configOptions.diagnosticRuleSet,
configOptions.defaultPythonVersion,
configOptions.defaultPythonPlatform,
[projectRoot],
extraPaths,
/* skipNativeLibraries */ false,
/* defaultDialect */ 'viper'
);
@@ -69,9 +85,6 @@ function buildProgramForCode(code: string) {
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const fs = require('fs');
const testDir = path.join(__dirname, '__unary_ptr__');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'unary_ptr.vp');
fs.writeFileSync(testFile, code, 'utf8');
@@ -190,10 +203,10 @@ v: t.CInt = sp.a
def test_interop(x: t.CInt):
q: t.CInt* = &x
v: t.CInt = *q
vq: t.CInt = *q
def test_double_deref(pp: t.CInt**):
v: t.CInt = *(*pp)
vpp: t.CInt = *(*pp)
def make_ptr(x: t.CInt) -> t.CInt*:
return &x
@@ -612,6 +625,185 @@ def f(ptr_raw: t.CUInt64T, offset: uint64) -> int:
serviceProvider.dispose();
});
// Viper:`T*(x)` 中 T 为「模块名同名类」时的强转。
// 背景(用户报的误报):ViperOS VKernel/Kernel/main.vp `BootInfo = bootinfo*(saved_rdi)`,
// 其中 `import bootinfo`(模块)与 bootinfo.vp 的 `class bootinfo` 同名。TPC 的 struct
// 注册表按短名全局查找,故裸名 bootinfo 取到该结构体类;此前 LSP 把裸名解析成模块,
// 报「类型 Module("bootinfo") 和 ulong 不支持运算符 *」。
// 锁定:模块内存在同名类时按强转折叠为 CPtr[bootinfo](与限定名 bootinfo.bootinfo* 同型);
// 星串两侧带空白的 `plainmod * (v)`(非 C 风格强转写法)仍是真实乘法并报运算符错误。
test('Viper T*(x) 强转:T 为「模块名同名类」(import bootinfo + class bootinfo)', () => {
const code = `import t
from stdint import *
import bootinfo
import plainmod
def _start() -> int:
saved_rdi: ulong
p = bootinfo*(saved_rdi)
q = bootinfo.bootinfo*(saved_rdi)
r = bootinfo.memory_map_entry*(saved_rdi)
return 0
def _bad() -> int:
v: ulong
w = plainmod * (v)
return 0
`;
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code, {
'bootinfo.vp':
'class memory_map_entry:\n Base: int = 0\n\n\nclass bootinfo:\n MemmapAddr: int = 0\n',
'plainmod.vp': 'X: int = 0\n\ndef helper() -> int:\n return 0\n',
});
const evaluator = program.evaluator as TypeEvaluator | undefined;
assert(evaluator, 'evaluator not available');
const ev = evaluator;
const rhsTypes: { line: number; type: string }[] = [];
class RhsCollector extends ParseTreeWalker {
override visitAssignment(node: AssignmentNode) {
const line = convertOffsetToPosition(node.d.rightExpr.start, parseResults.parserOutput.lines).line + 1;
const type = ev.getType(node.d.rightExpr);
rhsTypes.push({ line, type: type ? program.printType(type) : '(undefined)' });
return true;
}
}
new RhsCollector().walk(parseResults.parserOutput.parseTree);
console.log('=== 模块同名类强转右值类型 ===');
rhsTypes.forEach((r) => console.log(` L${r.line}: ${r.type}`));
// 裸模块名(L8)与限定名(L9)同为 CPtr[bootinfo]
for (const line of [8, 9]) {
const entry = rhsTypes.find((r) => r.line === line);
assert(entry, `强转右值类型未找到 (L${line})`);
assert(
entry.type.includes('rawptr') && entry.type.includes('bootinfo'),
`L${line} 应为 CPtr[bootinfo](rawptr[bootinfo]),实际: ${entry.type}`
);
}
const bareModule = rhsTypes.find((r) => r.line === 8);
const qualified = rhsTypes.find((r) => r.line === 9);
assert.strictEqual(bareModule?.type, qualified?.type, '裸模块名与限定名强转结果类型应一致');
// L10:模块内其它类(memory_map_entry)同样可强转
const entryType = rhsTypes.find((r) => r.line === 10);
assert(
entryType && entryType.type.includes('rawptr') && entryType.type.includes('memory_map_entry'),
`L10 应为 CPtr[memory_map_entry],实际: ${entryType?.type}`
);
// 诊断:唯一允许的错误是 _bad 里 `plainmod * (v)`(星串两侧有空白 → 仍是真实乘法)
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== 诊断 ===');
diagnostics.forEach((d) => console.log(` ${d}`));
const operatorErrors = diagnostics.filter((d) => d.includes('不支持运算符'));
assert.strictEqual(
operatorErrors.length,
1,
`应且仅应报 1 处运算符错误(plainmod * (v) 为真实乘法): ${JSON.stringify(operatorErrors)}`
);
assert(
operatorErrors[0].includes('[line=15]'),
`运算符错误应落在 plainmod * (v) 所在行(L15): ${JSON.stringify(operatorErrors)}`
);
program.dispose();
serviceProvider.dispose();
});
// Viper:`T*(x)` 解析为**调用**(与 TPC ExprParser._AtPtrSuffixCall 同形),
// 故强转结果可继续参与 trailer:`T*(x)[i] = v` 是合法赋值目标。
// 背景(用户报的误报):ViperOS VKernel/Kernel/drivers/core/cpu/cpu.vp:232
// `uint32*(info.vendor_id)[0] = res.ebx`
// 此前 LSP 把 `*` 当二元乘法(右操作数 `(info.vendor_id)[0]`),整个乘法表达式
// 落在赋值左侧 → 报「表达式不能是赋值目标」。
// 判据与 TPC 一致:base 是类型原子 + 星串两侧**紧贴**(无空白);带空白的
// `a * (b)` / `(a) * (b)` 仍是乘法。
test('Viper T*(x)[i] = v:强转结果作为赋值目标(cpu.vp vendor_id 写法)', () => {
const code = `import t
from stdint import *
class cpu_info:
vendor_id: [13]char
def get_info(info: cpu_info*, value: uint32) -> int:
uint32*(info.vendor_id)[0] = value
uint32*(info.vendor_id)[1] = value
first: uint32 = uint32*(info.vendor_id)[0]
return 0
def spaced(a: t.CInt, b: t.CInt) -> int:
m: t.CInt = a * (b)
n: t.CInt = (a) * (b)
return 0
`;
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code);
const evaluator = program.evaluator as TypeEvaluator | undefined;
assert(evaluator, 'evaluator not available');
const ev = evaluator;
// 收集「赋值目标 = 强转调用下标」的形状与强转类型
const castTargets: { line: number; funcIsPtrSuffix: boolean; castType: string; targetType: string }[] = [];
class CastTargetCollector extends ParseTreeWalker {
override visitAssignment(node: AssignmentNode) {
const target = node.d.leftExpr;
if (target.nodeType === ParseNodeType.Index) {
const leftExpr = target.d.leftExpr;
if (leftExpr.nodeType === ParseNodeType.Call) {
const castType = ev.getType(leftExpr);
const targetType = ev.getType(target);
castTargets.push({
line: convertOffsetToPosition(node.start, parseResults.parserOutput.lines).line + 1,
funcIsPtrSuffix: !!(
leftExpr.d.leftExpr.nodeType === ParseNodeType.UnaryOperation &&
(leftExpr.d.leftExpr as UnaryOperationNode).d.isViperPointerTypeSuffix
),
castType: castType ? program.printType(castType) : '(undefined)',
targetType: targetType ? program.printType(targetType) : '(undefined)',
});
}
}
return true;
}
}
new CastTargetCollector().walk(parseResults.parserOutput.parseTree);
console.log('=== T*(x)[i] = v 形状 ===');
castTargets.forEach((c) =>
console.log(` L${c.line} ptrSuffixFunc=${c.funcIsPtrSuffix} cast=${c.castType} target=${c.targetType}`)
);
// L8/L9:赋值目标为「强转调用 + 下标」,callee 是指针类型后缀
for (const line of [8, 9]) {
const entry = castTargets.find((c) => c.line === line);
assert(entry, `L${line} 赋值目标不是「强转调用下标」形状`);
assert(entry.funcIsPtrSuffix, `L${line} 的 callee 应为指针类型后缀节点`);
assert(
entry.castType.includes('rawptr') && entry.castType.includes('uint32'),
`L${line} 强转类型应为 CPtr[uint32],实际: ${entry.castType}`
);
assert(entry.targetType.includes('uint32'), `L${line} 赋值目标类型应为 uint32,实际: ${entry.targetType}`);
}
// 诊断:`uint32*(info.vendor_id)[0] = value` 不得报「表达式不能是赋值目标」,
// 也不得有其它 category=0/1;带空白的乘法仍走真实乘法(此处类型相符,无报错)
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== 诊断 ===');
diagnostics.forEach((d) => console.log(` ${d}`));
const hardErrors = diagnostics.filter((d) => d.includes('category=0') || d.includes('category=1'));
assert.strictEqual(
hardErrors.length,
0,
`T*(x)[i] = v 不应产生错误/警告: ${JSON.stringify(hardErrors)}`
);
program.dispose();
serviceProvider.dispose();
});
// Viper:`(T)x` 强转(括号内普通类型名 + 裸值),语义 = 调用形式 `T(x)` 的强转结果。
// 与已支持的 `(T)(x)` 调用式强转对照;真实乘法 `(a)*b` / `(a) * (b)` 不受影响。
test('Viper (T)x 强转:括号内普通类型名 + 裸值', () => {
@@ -844,3 +1036,92 @@ def test_str_arith(fmt: str) -> int:
program.dispose();
serviceProvider.dispose();
});
// Viper:双级指针强转 `(T**)x` / `(T**)(x)`(C 风格二级指针),以及 `T**` 注解/下标。
// 背景:`T**` 的 AST 是嵌套一元 '*'(*(*T)),值上下文求值时外层 '*' 的操作数已是
// 内层求出的 CPtr[T] 实例(非可实例化类),若按「解引用」处理会把括号内类型退化成 T,
// 报"T 的对象不可调用"并丢失二级指针语义(用户真实写法:
// `slot_l: ast.AST** = (ast.AST**)(uint64(data_l) + li_l * elem_sz_l)`)。
// 修复后 parser 标记的指针类型后缀节点(isViperPointerTypeSuffix)在值上下文一律
// 按类型表达式求值,故 `(T**)x` 与 `(T**)(x)` 均得到 CPtr[CPtr[T]]。
test('Viper 双级指针强转 (T**)x / (T**)(x)', () => {
const code = `import t
from stdint import *
class Node:
kind: int
def f(data_l: t.CPtr, li_l: sizet, elem_sz_l: sizet, elem_dst_l: Node*) -> Node*:
slot_l: Node** = (Node**)(uint64(data_l) + li_l * elem_sz_l)
slot_l[0] = elem_dst_l
c1: Node** = (Node**)data_l
c2: Node** = (Node**)(data_l)
c3 = (Node**)data_l
single: Node* = (Node*)data_l
return elem_dst_l
`;
const { program, configOptions, parseResults, uri, serviceProvider } = buildProgramForCode(code);
const evaluator = program.evaluator as TypeEvaluator | undefined;
assert(evaluator, 'evaluator not available');
const ev = evaluator;
const rhsTypes: { line: number; type: string }[] = [];
const indexTypes: { line: number; type: string }[] = [];
class TypeCollector extends ParseTreeWalker {
override visitAssignment(node: AssignmentNode) {
const line = convertOffsetToPosition(node.d.rightExpr.start, parseResults.parserOutput.lines).line + 1;
const type = ev.getType(node.d.rightExpr);
rhsTypes.push({ line, type: type ? program.printType(type) : '(undefined)' });
return true;
}
override visitIndex(node: IndexNode) {
const type = ev.getType(node as any);
indexTypes.push({
line: convertOffsetToPosition(node.start, parseResults.parserOutput.lines).line + 1,
type: type ? program.printType(type) : '(undefined)',
});
return true;
}
}
new TypeCollector().walk(parseResults.parserOutput.parseTree);
console.log('=== 双级指针强转右值类型 ===');
rhsTypes.forEach((r) => console.log(` L${r.line}: ${r.type}`));
indexTypes.forEach((i) => console.log(` L${i.line} [0]: ${i.type}`));
// L8:完整写法 → rawptr[rawptr[Node]]
const slotRhs = rhsTypes.find((r) => r.line === 8);
assert(slotRhs, `slot_l 右值未找到 (L8)`);
assert.strictEqual(slotRhs.type, 'rawptr[rawptr[Node]]', `L8 应为二级指针,实际: ${slotRhs.type}`);
// L9:`slot_l[0]` → rawptr[Node](一级指针元素)
const slotElem = indexTypes.find((i) => i.line === 9);
assert(slotElem, `slot_l[0] 未找到 (L9)`);
assert.strictEqual(slotElem.type, 'rawptr[Node]', `L9 应为一级指针元素,实际: ${slotElem.type}`);
// L10/L11/L12:三种二级强转写法 → rawptr[rawptr[Node]]
for (const line of [10, 11, 12]) {
const entry = rhsTypes.find((r) => r.line === line);
assert(entry, `二级强转右值未找到 (L${line})`);
assert.strictEqual(entry.type, 'rawptr[rawptr[Node]]', `L${line} 应为二级指针,实际: ${entry.type}`);
}
// L13:一级强转对照 → rawptr[Node]
const singleRhs = rhsTypes.find((r) => r.line === 13);
assert(singleRhs, `一级强转右值未找到 (L13)`);
assert.strictEqual(singleRhs.type, 'rawptr[Node]', `L13 应为一级指针,实际: ${singleRhs.type}`);
// 不得有 category=0(错误)/ category=1(警告)诊断
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== 诊断 ===');
diagnostics.forEach((d) => console.log(` ${d}`));
const errorsAndWarnings = diagnostics.filter((d) => d.includes('category=0') || d.includes('category=1'));
assert(
errorsAndWarnings.length === 0,
`双级指针强转不应产生错误/警告: ${JSON.stringify(errorsAndWarnings)}`
);
program.dispose();
serviceProvider.dispose();
});
@@ -111,3 +111,76 @@ test('BuddyTest 联合类型变量访问 realloc/alloc 成员不报 UnionType/Un
program.dispose();
serviceProvider.dispose();
});
// Viper:`X | t.CPtr` 折叠出的指针级数。
// 背景(用户报的误报):`HANDLE = VOIDPTR = t.CPtr`(void*),CreatePipe 的
// `hReadPipe: HANDLE | t.CPtr` 语义上是 HANDLE*(void**)。但两侧在类型层同形
// (都是 type[CPtr[Unknown]]),原先被并成一级指针 → `&stdout_read`(void**)
// 传参被误报"跨级指针隐式转换不允许"。
// 本用例锁定:左操作数已是指针时按"包一层指针"折叠;同时真实跨级错误必须照报。
test('Viper `X | t.CPtr`:左操作数已是指针时折成二级指针(HANDLE | t.CPtr = void**)', () => {
const code = `import t
from stdint import *
import w32.win32base
import w32.win32file
extern def TakeHandlePtr(h: w32.win32base.HANDLE | t.CPtr) -> BOOL
extern def TakeStructPtr(s: w32.win32base.SECURITY_ATTRIBUTES | t.CPtr) -> BOOL
extern def TakeVoidPtr(p: t.CVoid | t.CPtr) -> BOOL
extern def TakeStrPtr(s: str | t.CPtr) -> BOOL
def types_ok(handle: w32.win32base.HANDLE, sa: w32.win32base.SECURITY_ATTRIBUTES, text: str) -> int:
# HANDLE | t.CPtr = void**:&handle(void**)应接受
TakeHandlePtr(&handle)
# 结构体 | t.CPtr = 结构体*:&sa 应接受
TakeStructPtr(&sa)
# CVoid | t.CPtr = void*:&sa(一级)应接受
TakeVoidPtr(&sa)
# str | t.CPtr = i8**:&text(i8**)应接受
TakeStrPtr(&text)
return 0
def level_mismatch_still_reported(handle: w32.win32base.HANDLE) -> int:
# void** 传 void*:真实跨级错误必须照报
TakeVoidPtr(&handle)
return 0
def create_pipe(capture_output: BOOL, sa: w32.win32base.SECURITY_ATTRIBUTES) -> int:
stdout_read: w32.win32base.HANDLE = None
stdout_write: w32.win32base.HANDLE = None
if capture_output:
pipe_result: BOOL = w32.win32file.CreatePipe(&stdout_read, &stdout_write, &sa, 0)
return 0
`;
const fs = require('fs');
const testDir = path.join(__dirname, '__viper_union_ptr_level__');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'ptr_level.vp');
fs.writeFileSync(testFile, code, 'utf8');
try {
const { program, serviceProvider, uri, configOptions } = buildProgram(testFile);
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== ptr_level.vp diagnostics ===');
diagnostics.forEach((d) => console.log(` ${d.range} [category=${d.category}] ${d.message}`));
const errors = diagnostics.filter((d) => d.category === 0);
const levelErrors = errors.filter((d) => d.message.includes('跨级指针'));
const callErrors = errors.filter((d) => d.message.includes('CreatePipe') || d.message.includes('TakeHandlePtr')
|| d.message.includes('TakeStructPtr') || d.message.includes('TakeStrPtr'));
// 唯一允许的错误:level_mismatch_still_reported 里 `TakeVoidPtr(&handle)` 的跨级错误。
assert.strictEqual(
levelErrors.length,
1,
`应且仅应报 1 处跨级指针错误(void** → void*):${JSON.stringify(errors)}`
);
assert.strictEqual(callErrors.length, 0, `不应有参数类型误报:${JSON.stringify(callErrors)}`);
program.dispose();
serviceProvider.dispose();
} finally {
fs.rmSync(testDir, { recursive: true, force: true });
}
});
@@ -0,0 +1,274 @@
/*
* viperVirtualModifier.test.ts
*
* 验证 Viper 的 vtable 声明修饰关键字:
* virtual class X —— 类级:启用多态 vtable(等价 @t.CVTable class X)
* novirtual class X —— 类级:非多态,字段展平嵌入(等价 @t.NoVTable class X)
* virtual def f(...) —— 方法级:必须在 class / virtual class 内(不能在 novirtual class 内)
* novirtual def f(..) —— 方法级:必须在 class / virtual class / novirtual class 内
*
* 语义取舍:这些关键字是给编译器的代码生成提示,类型层无额外语义,
* 解析器只记录在节点上(ClassNode.d.viperClassModifier / FunctionNode.d.viperModifier),
* 并校验方法级修饰的出现位置;语法高亮由 TextMate grammar 负责。
*
* 运行:npx jest src/tests/viperVirtualModifier.test.ts --runInBand
*/
import assert from 'assert';
import path from 'path';
import { ImportResolver } from '../analyzer/importResolver';
import { Program } from '../analyzer/program';
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { UriEx } from '../common/uri/uriUtils';
import { FullAccessHost } from '../common/fullAccessHost';
import { ClassNode, FunctionNode } from '../parser/parseNodes';
(global as any).__rootDirectory = path.resolve();
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
function buildProgram(source: string) {
const tempFile = new RealTempFile();
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
const fs = require('fs');
const testRoot = path.join(__dirname, '__virtual_modifier__');
fs.rmSync(testRoot, { recursive: true, force: true });
fs.mkdirSync(testRoot, { recursive: true });
const filePath = path.join(testRoot, 'main.vp');
fs.writeFileSync(filePath, source, 'utf8');
const projectRoot = UriEx.file(testRoot);
const configOptions = new ConfigOptions(projectRoot);
configOptions.internalTestMode = true;
configOptions.initializeTypeCheckingMode('standard');
configOptions.executionEnvironments = [
new ExecutionEnvironment(
'python',
projectRoot,
configOptions.diagnosticRuleSet,
configOptions.defaultPythonVersion,
configOptions.defaultPythonPlatform,
[UriEx.file(TRANSPYC_INCLUDES)],
/* skipNativeLibraries */ false,
/* defaultDialect */ 'viper'
),
];
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const uri = UriEx.file(filePath);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环
}
const parseResults = program.getSourceFile(uri)!.getParseResults();
const diagnostics = (program.getSourceFile(uri)?.getDiagnostics(configOptions) || []).map(
(d) => `[category=${d.category}] ${d.message}`
);
return { program, configOptions, parseResults, uri, serviceProvider, diagnostics };
}
function collectClassModifiers(parseResults: any): Map<string, string | undefined> {
const result = new Map<string, string | undefined>();
class Collector extends ParseTreeWalker {
override visitClass(node: ClassNode) {
result.set(node.d.name.d.value, node.d.viperClassModifier);
return true;
}
}
new Collector().walk(parseResults.parserOutput.parseTree);
return result;
}
function collectMethodModifiers(parseResults: any): Map<string, string | undefined> {
const result = new Map<string, string | undefined>();
class Collector extends ParseTreeWalker {
override visitFunction(node: FunctionNode) {
result.set(node.d.name.d.value, node.d.viperModifier);
return true;
}
}
new Collector().walk(parseResults.parserOutput.parseTree);
return result;
}
function errorsOf(diagnostics: string[]): string[] {
return diagnostics.filter((d) => d.startsWith('[category=0]') || d.startsWith('[category=1]'));
}
test('virtual class / novirtual class 记录修饰形态且不报错', () => {
const source = `import t
virtual class A():
val: t.CInt
def get(self) -> t.CInt:
return self.val
novirtual class B():
val: t.CInt
class C():
val: t.CInt
`;
const { program, serviceProvider, parseResults, diagnostics } = buildProgram(source);
const errors = errorsOf(diagnostics);
assert.strictEqual(errors.length, 0, `vtable 类声明不应报错,实际: ${JSON.stringify(diagnostics)}`);
const classModifiers = collectClassModifiers(parseResults!);
assert.strictEqual(classModifiers.get('A'), 'virtual', `A 应为 virtual class,实际: ${classModifiers.get('A')}`);
assert.strictEqual(classModifiers.get('B'), 'novirtual', `B 应为 novirtual class,实际: ${classModifiers.get('B')}`);
assert.strictEqual(classModifiers.get('C'), undefined, `C 是普通 class,不应有修饰,实际: ${classModifiers.get('C')}`);
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__virtual_modifier__'), { recursive: true, force: true });
});
test('virtual def / novirtual def 在允许的类形态内不报错', () => {
const source = `import t
class Plain():
virtual def a(self) -> int:
return 1
novirtual def b(self) -> int:
return 2
virtual class V():
virtual def c(self) -> int:
return 3
novirtual def d(self) -> int:
return 4
novirtual class N():
novirtual def e(self) -> int:
return 5
`;
const { program, serviceProvider, parseResults, diagnostics } = buildProgram(source);
const errors = errorsOf(diagnostics);
assert.strictEqual(errors.length, 0, `合法位置的 vtable 方法不应报错,实际: ${JSON.stringify(diagnostics)}`);
const methodModifiers = collectMethodModifiers(parseResults!);
for (const [name, expected] of [
['a', 'virtual'],
['b', 'novirtual'],
['c', 'virtual'],
['d', 'novirtual'],
['e', 'novirtual'],
] as const) {
assert.strictEqual(
methodModifiers.get(name),
expected,
`${name} 应为 ${expected} 方法,实际: ${methodModifiers.get(name)}`
);
}
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__virtual_modifier__'), { recursive: true, force: true });
});
test('virtual def 不能位于 novirtual class 内,也不能位于类外', () => {
const source = `import t
novirtual class N():
virtual def bad(self) -> int:
return 1
virtual def topLevel() -> int:
return 2
def outer() -> int:
virtual def nested() -> int:
return 3
return 0
`;
const { program, serviceProvider, diagnostics } = buildProgram(source);
assert(
diagnostics.some((d) => d.includes('不能位于 novirtual class 内')),
`novirtual class 内的 virtual def 应报错,实际: ${JSON.stringify(diagnostics)}`
);
assert(
diagnostics.filter((d) => d.includes('"virtual def" 必须位于 class 或 virtual class 内')).length >= 2,
`类外的 virtual def(模块级 / 函数体内)应各报一次,实际: ${JSON.stringify(diagnostics)}`
);
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__virtual_modifier__'), { recursive: true, force: true });
});
test('novirtual def 位于类外时报错', () => {
const source = `import t
novirtual def topLevel() -> int:
return 1
`;
const { program, serviceProvider, diagnostics } = buildProgram(source);
assert(
diagnostics.some((d) => d.includes('"novirtual def" 必须位于 class、virtual class 或 novirtual class 内')),
`类外的 novirtual def 应报错,实际: ${JSON.stringify(diagnostics)}`
);
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__virtual_modifier__'), { recursive: true, force: true });
});
test('virtual / novirtual 不搭配 class、def 时报错', () => {
const source = `import t
virtual val: t.CInt
novirtual def f(x: t.CInt) -> t.CInt:
return x
`;
const { program, serviceProvider, diagnostics } = buildProgram(source);
assert(
diagnostics.some((d) => d.includes('必须与 class 或 def 搭配使用')),
`virtual 不搭配 class/def 应报错,实际: ${JSON.stringify(diagnostics)}`
);
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__virtual_modifier__'), { recursive: true, force: true });
});
test('装饰器后的 virtual class 同样成立,且类成员可正常访问', () => {
const source = `import t
@t.Object
virtual class Widget():
val: t.CInt
def __init__(self, v: t.CInt):
self.val = v
def get(self) -> t.CInt:
return self.val
export def main() -> int:
w = Widget(7)
return w.get()
`;
const { program, serviceProvider, parseResults, diagnostics } = buildProgram(source);
const errors = errorsOf(diagnostics);
assert.strictEqual(errors.length, 0, `装饰器 + virtual class 不应报错,实际: ${JSON.stringify(diagnostics)}`);
const classModifiers = collectClassModifiers(parseResults!);
assert.strictEqual(classModifiers.get('Widget'), 'virtual', `Widget 应为 virtual class,实际: ${classModifiers.get('Widget')}`);
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(path.join(__dirname, '__virtual_modifier__'), { recursive: true, force: true });
});
@@ -31,11 +31,17 @@ from _typeshed import (
SupportsRichComparisonT,
SupportsWrite,
)
from collections.abc import Awaitable, Callable, Iterable, Iterator, MutableSet, Reversible, Set as AbstractSet, Sized
from collections.abc import Awaitable, Iterable, Iterator, MutableSet, Reversible, Set as AbstractSet, Sized
from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper
from os import PathLike
from types import CellType, CodeType, EllipsisType, GenericAlias, NotImplementedType, TracebackType
# Viper 扩展:ASM_DESCR 免 `t.` 前缀直接可用 —— 内联汇编的约束 / 破坏描述符常量类
# (等价 t.ASM_DESCR,见本文件末尾 __asm__ / asminp / asmout 一节)。
# 写成 `X as X` 的冗余别名形式:stub 文件里 `from m import X` 默认不对外再导出,
# 只有冗余别名才构成显式 re-export(PEP 484 对 .pyi 的规则)。
from t import ASM_DESCR as ASM_DESCR
# mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping}
# are imported from collections.abc in builtins.pyi
from typing import ( # noqa: Y022,UP035
@@ -2734,6 +2740,49 @@ def LOut(expr: Any, constraint: Any = None) -> Any:
"""LLVM IR 输出操作数标记(等价 c.LOut)。"""
...
# --- Viper 扩展:C 风格内置 asm 助记符(免 c./t. 前缀) ---
# 与 C 的写法对齐,源码里可直接写 `__asm__(...)` / `asminp(...)` / `asmout(...)` /
# `ASM_DESCR.XXX`,不必 `import c` 或加 `t.` 前缀:
# - `__asm__` 等价 c.Asm(声明式内联汇编,参数为模板串 + inp/out/op 关键字);
# - `asminp` / `asmout` 等价 c.AsmInp / c.AsmOut(操作数标记,constraint 取
# ASM_DESCR 上的约束常量);
# - `ASM_DESCR` 见本文件顶部 `from t import ASM_DESCR`。
# 原有 Asm / AsmInp / AsmOut(以及 c.* / t.ASM_DESCR)写法保持可用。
def __asm__(asm: str, *args: Any, inp: Any = None, out: Any = None, op: Any = None) -> Any:
"""内联汇编(等价 c.Asm)。inp 为输入操作数描述符列表,out 为输出操作数
描述符列表,op 为破坏描述符列表。"""
...
def asminp(expr: Any, constraint: Any = None) -> Any:
"""汇编输入操作数标记(等价 c.AsmInp)。constraint 为寄存器约束
(如 ASM_DESCR.REG_ANY)。"""
...
def asmout(expr: Any, constraint: Any = None) -> Any:
"""汇编输出操作数标记(等价 c.AsmOut)。constraint 为寄存器约束
(如 ASM_DESCR.OUTPUT_REG)。"""
...
# --- Viper 扩展:LLVM IR 内置助记符(免 c. 前缀) ---
# 与 C 风格 asm 助记符同一套机制,只是针对内联 LLVM IR:
# - `__llvmir__` 等价 c.LLVMIR(模板串 + 可选结果类型等位置参数 + inp/out/op);
# - `irinp` / `irout` 等价 c.LInp / c.LOut(操作数标记)。
# 原有 LLVMIR / LInp / LOut(以及 c.*)写法保持可用。
def __llvmir__(ir: str, *args: Any, inp: Any = None, out: Any = None, op: Any = None) -> Any:
"""内联 LLVM IR(等价 c.LLVMIR)。ir 为 IR 模板串,其后可跟结果类型等位置参数;
inp/out/op 为操作数描述符列表。"""
...
def irinp(expr: Any, constraint: Any = None) -> Any:
"""LLVM IR 输入操作数标记(等价 c.LInp)。"""
...
def irout(expr: Any, constraint: Any = None) -> Any:
"""LLVM IR 输出操作数标记(等价 c.LOut)。"""
...
# --- Viper 扩展:编译器内置裸指针类型 rawptr ---
# rawptr 是编译器内置类型(TPC/TPV 双端,语法保留字,不可重定义):
# rawptr[T] 固定表示"裸指针,指向原始类型 T"(等价 t.CPtr[T]),
@@ -2743,6 +2792,23 @@ def LOut(expr: Any, constraint: Any = None) -> Any:
# 路径并忽略本声明(见 TransPyC TypeAnnotationResolver / SymbolExtractor)。
rawptr: t.CTypedef = t.CPtr
# --- Viper 扩展:类型描述符 const / volatile 的脱糖目标 ---
# `const T` / `volatile T` 由 parser 脱糖为 `CConst | T` / `CVolatile | T`
# (见 parser._parseViperTypeDescriptorPrefix)。这两个名字声明在 builtins stub 中,
# 使脱糖结果对任意 Viper 工程均可解析 —— 与 T* 指针注解一致,不要求源码
# 显式 `import t`(t.CConst / t.CVolatile 需要 import t 才可见)。
# 值语义即 t.CConst / t.CVolatile 本身(TypeSet 修饰符,不改变数据类型)。
CConst: t.CTypedef = t.CConst
CVolatile: t.CTypedef = t.CVolatile
# --- Viper 扩展:注解位置的小写 callable 等价 t.Callable ---
# `callable[[A, B], R]` / `callable[..., R]` / `callable[[], R]` 由 parser 在
# **注解位置**脱糖为同名大写的 `Callable[...]`(见 parser._parseAtom),
# 值位置的 `callable(x)` 仍是内置函数(本别名只在类型位置生效)。
# `Callable` 在此声明,使脱糖结果对任意 Viper 工程均可解析 —— 与 rawptr /
# CConst 一致,不要求源码显式 `import t`(t.Callable 需要 import t 才可见)。
Callable: t.CTypedef = t.Callable
# --- Viper 扩展:C 定宽/最快/最小整型 typedef(镜像 TransPyC includes/builtins.vp) ---
# 这些 typedef 由编译器内置的 includes/builtins.vp 提供。LSP 侧原先只靠
# program.ts 的 _mergeViperBuiltinsVpSymbols 在"被分析文件所在目录及其祖先目录的
@@ -2756,6 +2822,8 @@ rawptr: t.CTypedef = t.CPtr
# 语言内置名称,无需 import)。C 的 "long long" / "unsigned long long" 含空格、
# 不能作标识符,Viper 写作 longlong / ulonglong。
short: t.CTypedef = t.CShort
uint: t.CTypedef = t.CUnsignedInt
uchar: t.CTypedef = t.CUnsignedChar
long: t.CTypedef = t.CLong
longlong: t.CTypedef = t.CLongLong
ushort: t.CTypedef = t.CUnsignedShort
@@ -400,6 +400,17 @@ class CVolatile(CType):
pass
class CAttr(CType):
"""`__attribute__` 属性标记(编译期元数据,不改变数据类型)。
与 CConst / CVolatile 同族的修饰符:注解里写 `T | t.attr.section(".x")`
(编译器按 AST 扫描提取属性,见 t.attr)或 `__attribute__(section(".x")) T`
时,LSP 把属性标记求值为本类的实例——它只作修饰,不参与类型检查:
运算/下标/赋值都按被修饰的基础类型处理,展示时不作为联合成员暴露。
"""
pass
class CStatic(CType):
"""static 存储类。"""
pass
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "vipeye",
"version": "1.1.425",
"version": "1.1.426",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vipeye",
"version": "1.1.425",
"version": "1.1.426",
"license": "MIT",
"bin": {
"vipeye": "index.js",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "vipeye",
"displayName": "Vipeye",
"description": "Type checker for the Viper language",
"version": "1.1.425",
"version": "1.1.426",
"license": "MIT",
"author": {
"name": "TermiNexus"
+28
View File
@@ -1,5 +1,33 @@
# Change Log
## 1.1.426
- **Type descriptors**: `const` / `volatile` may prefix any type annotation — `a: const int`, `b: volatile int`, `def f() -> volatile int`, `p: const t.CPtr`, `q: const int*`, in parameters, return types, class members and type aliases. Both are **hard keywords** now (reserved words): the only legal use is a type-annotation prefix, anything else (`const = 1`, `a: const` with no type) is reported as a syntax error with a dedicated message. They desugar in the parser into the equivalent type-set annotation `CConst | T` / `CVolatile | T`, so they weigh exactly the same as the hand-written form in assignment compatibility, operator handling and diagnostics — no second modifier mechanism. The desugared `CConst` / `CVolatile` names live in the built-in `builtins.vpi` stub, so no `import t` is required
- `const T*` is now `(CConst | T)*` → `CConst | CPtr[T]`, i.e. a pointer to the described type, matching the `CConst | CChar | CPtr` (const char *) spelling used by TransPyC. The pointer suffix is always applied at the outermost level, so the result no longer depends on whether the `*` is followed by an end-of-line or by more source (a newline used to make the star attach at a different level, leaving the annotation as `Unknown`)
- `T*` / `&x` no longer require `import t`: when the file doesn't import the `t` module, `CPtr` falls back to the compiler built-in `rawptr` from `builtins.vpi`. Previously such files got `type[int] is not supported for operator "*"` and an `Unknown` type
- `|` combinations that mix a type descriptor with an **instance**-form type (e.g. `t.CConst | int*`, whose right operand evaluates to `CPtr[int]`) are now treated as a type-set union instead of falling through to `__or__` and degrading to `Unknown`
- Type descriptors are rendered as a **prefix qualifier** instead of a union member, so the internal representation never leaks into hovers or diagnostics: `a: const int*` shows `const rawptr[int]` (not `CConst | rawptr[int]`), `a: const int` shows `const int`, and `a: const volatile int` shows `const volatile int`. Storage-class descriptors get the same treatment (`static` / `register` / `auto` / `inline` / `extern`)
- Type descriptors no longer appear as the pointee of a pointer: `(CConst | int)*` evaluates to `CConst | CPtr[int]` rather than the "union of pointers" `CPtr[CConst] | CPtr[int]`
- `t.CVolatile` is handled everywhere `t.CConst` already was (type-set subscript, iteration, member access, string-literal assignability, `Struct | CPtr` folding), so `volatile` behaves symmetrically with `const`
- New **`inline while cond:` / `inline for x in y:`**: the `inline` modifier is accepted on loop statements (it is a code-generation hint, so no additional type semantics — the loop is parsed as a plain `while` / `for`), and `inline` is highlighted as a modifier. Using `inline` anywhere else still reports "must be used with 'def'"
- `const` / `volatile` are highlighted as type modifiers
- New built-in C basic type names `uint` (`t.CUnsignedInt`) and `uchar` (`t.CUnsignedChar`), declared in the built-in `builtins.vpi` stub. Any Viper project resolves them without configuring `includes` — `uint` already exists in TransPyC's `includes/builtins.vp` but was missing from the stub, so it was reported as undefined in setups where that file cannot be located; `uchar` is new in both places
- New **`virtual` / `novirtual` declaration modifiers** for vtable control, equivalent to the `@t.CVTable` / `@t.NoVTable` decorators: `virtual class X` (polymorphic vtable) and `novirtual class X` (flattened, no vtable) are recorded on the class node, and `virtual def f(...)` / `novirtual def f(...)` mark individual methods. Placement is enforced: `virtual def` must be inside a `class` or `virtual class` (rejected inside a `novirtual class`, at module level, or nested in a function body), and `novirtual def` must be inside a class of any of the three forms. Using either modifier without `class` / `def` reports a dedicated error, they work after decorators too, and both are highlighted as type modifiers
- Two-level pointer casts work now: `(T**)x` / `(T**)(x)` evaluate to `CPtr[CPtr[T]]` (e.g. `slot_l: ast.AST** = (ast.AST**)(uint64(data_l) + li_l * elem_sz_l)`, whose element access `slot_l[0]` yields `ast.AST*`). The `T**` AST is a nested unary `*` pair whose outer operand is already the `CPtr[T]` instance produced by the inner one; in a value context that was read as a **dereference**, collapsing the cast target to `T` (reported as "object of type 'T' is not callable") and losing the second pointer level. Pointer-type suffixes marked by the parser are now always evaluated as type expressions, so `(T*)x` / `T*(x)` are unaffected
- Bare C-style inline-asm and LLVM-IR builtins, so the C spelling can be used verbatim without any import or module prefix: **`__asm__(...)`** (same as `c.Asm` / `Asm`), **`asminp(...)`** / **`asmout(...)`** (same as `c.AsmInp` / `c.AsmOut`), **`__llvmir__(...)`** (same as `c.LLVMIR` / `LLVMIR`) and **`irinp(...)`** / **`irout(...)`** (same as `c.LInp` / `c.LOut`), plus **`ASM_DESCR`** (same as `t.ASM_DESCR`, so `ASM_DESCR.REG_ANY` / `ASM_DESCR.CLOBBER_RDX` resolve). All of these are declared in the built-in `builtins.vpi` stub except `ASM_DESCR`, which is re-exported there as `from t import ASM_DESCR as ASM_DESCR` (a stub re-export must use the redundant-alias form). The existing `Asm` / `AsmInp` / `AsmOut` / `LLVMIR` / `LInp` / `LOut` names and the `c.*` / `t.ASM_DESCR` spellings keep working
- `X | t.CPtr` now folds to a pointer-to-`X` even when `X` is itself a pointer: `HANDLE | t.CPtr` (with `HANDLE = VOIDPTR = t.CPtr`, i.e. void*) is `void**`, not `void*`. Both operands used to evaluate to the very same type (`type[CPtr[Unknown]]`), so the union collapsed into a single-level pointer and `&handle` (void**) was rejected — e.g. `w32.win32file.CreatePipe(&stdout_read, &stdout_write, &sa, 0)`, whose parameters are declared `HANDLE | t.CPtr`, reported "跨级指针隐式转换不允许" (a false positive: `LPHANDLE` really is `HANDLE*`). `createUnionType` now decides this syntactically: when the right operand is the bare `CPtr` / `rawptr` class and the left operand already is a pointer type, the left operand is wrapped (`CPtr[X]`). Struct / void / str operands keep their previous behavior (`Scheduler | t.CPtr` → `CPtr[Scheduler]`, `CVoid | t.CPtr` → void*, `str | t.CPtr` → i8**), and genuine level mismatches (void** → void*) are still reported
- Viper's own keywords are now offered by completion: the hard keywords **`define`** (macro function, same as `def`), **`const`** / **`volatile`** (type-descriptor prefixes) and the soft keywords **`typedef`**, **`inline`** / **`export`** / **`extern`** / **`virtual`** / **`novirtual`**, **`enum`** / **`rnum`** / **`union`** / **`struct`**, plus the `def` aliases **`fn`** / **`fun`** / **`func`** / **`function`**. They are absent from CPython's keyword table, so they used to be invisible in the suggestion list even though the parser accepts them (the TextMate grammar already highlighted them). `completionProvider`'s `Keywords` namespace now merges a Viper list into both the full and the expression-only keyword sets
- New **"Count Project LOC"** command (`viper.countLines`): right-click a `.vpj` in the Explorer → the project's `source_dir` (usually `App`) is scanned recursively and every **known language** is counted. A **`Viper LOC` Webview panel** opens *before* the scan and shows a **determinate progress bar** (files collected first, then processed `N / M` with the current file), and once done renders the report: KPI cards (files, total / code / comment / blank lines, **comment rate**, **average line length**, characters), a **stacked bar chart** per language (code / comment / blank segments, scaled to the largest language), a **language filter** (checkboxes that re-compute the KPIs, chart and table), and a detail table with a totals row. The same report is also written in plain text to the `Viper LOC` output channel for copying. Known languages follow the extensions this extension registers (`.vp`, `.vpi`, `.vpnci`, `.vpj`) and, as requested, `.def` files are supported too (module-definition `;` line comments, C-style `//` line comments and slash-star block comments are all recognized; block comments that span lines are counted as comments). The project's `build_dir`, hidden directories, `node_modules`, `.git` and `__pycache__` are skipped, and symlinks are not followed
- `T*(x)` casts now accept a bare name that resolves to a **module** when that module defines a class with the same name, as in `import bootinfo` plus `bootinfo.vp`'s `class bootinfo` — the spelling ViperOS uses for `BootInfo = bootinfo*(saved_rdi)` in `VKernel/Kernel/main.vp`. TransPyC's struct registry is keyed by short name, so the plain name picks up the class there; Vipeye evaluated it as the module instead, the cast fell through to arithmetic and reported `类型"Module("bootinfo")"和"ulong"不支持运算符"*"`. It now folds to `CPtr[bootinfo]`, identical to the qualified `bootinfo.bootinfo*(saved_rdi)` / `bootinfo.memory_map_entry*(saved_rdi)` spellings. A module without a same-named class is not turned into a cast, so the genuine operator error is still reported
- `T*(x)` is now parsed as a **call** (`T*(x)[i]` / `T*(x).f` therefore work), matching TransPyC's `_AtPtrSuffixCall`: when a type atom (`Name` / `member access` / `subscript`) is followed by a run of `*`/`**` and then `(` with **no whitespace anywhere in between**, the run is consumed as a pointer-type suffix and the `(` becomes the call trailer — the same tree `(T*)x` already produces. This fixes `uint32*(info.vendor_id)[0] = res.ebx` in ViperOS `drivers/core/cpu/cpu.vp`, which was read as `uint32 * (info.vendor_id[0])` and reported "表达式不能是赋值目标" (the whole multiplication sat on the left of `=`); the cast now yields `CPtr[uint32]`, the subscript yields `uint32`, and the assignment is accepted. Real multiplications keep multiplying: `a * (b)`, `(a) * (b)`, `(a)*b`, `n*(m - 1)` and `2 * 3` are unaffected (whitespace or a non-type-atom base), and `(T*)[i]` stays a subscript
- **Duplicate declaration** of a module-level variable inside a function is now an **error (TypeError)**, matching TransPyV's `AssignGenMixin` check: when the module already declares a runtime variable `x` (`x: T = ...`), a **type-annotated** declaration of the same name inside a function fails *unconditionally* — both without a `global` statement (it would shadow the global as a local) and **with** one (the annotation duplicates the existing global declaration). Type equality is deliberately not considered. The only legal spellings stay: `global x` + an **un-annotated** `x = v` assignment, or a differently-named local. Scope is function-only — module-level re-annotation, class members, `CDefine` macro constants (no global storage), `CTypedef` aliases and same-named functions/classes are not flagged; the diagnostic is only produced when the name really is a module-level variable. Verified against the real tree: `TransPyV/App`, `VET/App` and `includes` (147 files) have **zero** violations, while the TPC-compiled ViperOS kernel has 14 in `VKernel/Kernel/paging/paging.vp` (L99–L190 re-annotate `pdp` / `pd` / `pt` right after `global pdp, pd, pt`), which TransPyV rejects as well
- **`__attribute__`** is now supported in both spellings Viper uses (LSP side only; TransPyC/TransPyV are unchanged). As a **decorator**, `@__attribute__(section(".text.wo"), always_inline)` desugars into `@c.Attribute(t.attr.section(".text.wo"), t.attr.always_inline)` — the same tree TransPyC's `_DesugarMagicAttribute` produces, so it works for `class` and `def` alike and no second mechanism is introduced. As a **modifier**, `a: __attribute__(section(".x")) const int = 1` desugars into the type-set annotation `t.attr.section(".x") | (CConst | int)`, i.e. the same family as `const` / `volatile` (it must be followed by a type, one layer of parentheses only — not GCC's `__attribute__((...))`), and it composes freely with `const` / `volatile` in either order. Attribute names in the parentheses are offered by completion, listed from the `attr` class declared in the `t` stub
- Attribute markers in a type annotation (`T | t.attr.section(".x")`, `T | t.attr.llvm.nounwind`, and the markers the parser synthesizes from `__attribute__(...)`) are now evaluated as the modifier `t.CAttr` instead of leaking into the annotation as a union member. Previously `t.attr.llvm.nounwind` stayed the class `nounwind` — itself a union member, so `P: t.CInt | t.attr.llvm.nounwind | t.attr.llvm.nobuiltin` displayed as `CInt | nounwind | nobuiltin`, `p + 1` reported "运算符不支持" and `V[0]` reported "类型"nounwind"上未定义"__getitem__""; and `t.attr.section(".x")` (a *call*) was rejected with "类型表达式中不允许使用调用表达式". `CAttr` joins the existing C-modifier family, so operators, subscripts, assignment compatibility and rendering all skip it by the same rules as `CConst` / `CVolatile`, and the property names (`section`, `always_inline`, `nounwind`, …) resolve against the `t` stub for jump-to-definition and hover
- New **value-position `volatile` prefix**: `a: int = volatile f()` and `b: Settings = volatile Settings()` mark a call (function call or struct construction) as volatile for code generation — the expression keeps the call's own type, TransPyC / TransPyV are not involved. Only a call may follow it (one is reported otherwise), and the bare uses `volatile = 2` / `const = 1` still report a syntax error
- `__attribute__` is highlighted as a type modifier (same scope as `const` / `volatile`): the TextMate grammar matches it when followed by `(`, in both the decorator and the modifier spelling
- `__attribute__` is also offered by completion (typing `__a` lists it) and **hovering inside `__attribute__(...)`** shows the full list of available attribute names (`section` / `aligned` / `always_inline` / `weak` / …), taken from the `attr` class of the `t` stub. Both share one helper (`viperAttributeUtils`) for locating the parentheses and enumerating `t.attr`, so the completion list and the hover list cannot drift apart
- Lowercase **`callable[[A, B], R]`** is now supported in a type annotation and means exactly `t.Callable[[A, B], R]` — all three spellings (`callable[...]`, `Callable[...]`, `t.Callable[...]`) and all the usual argument forms (`callable[[int, str], bool]`, `callable[..., bool]`, `callable[[], bool]`, as a parameter, return or variable annotation) evaluate to the same callable type. The parser rewrites the bare name `callable` to `Callable` **only in type-annotation position**, so the built-in function is untouched: `callable(x)` / `if callable(x):` still behave as before. `Callable` is declared in the built-in `builtins.vpi` stub (`Callable: t.CTypedef = t.Callable`, next to `rawptr` / `CConst`), so no `import t` is required. Previously `callable[[int], bool]` silently degraded to the type of the `callable` *function* (the Viper-dialect rule that ignores explicit type arguments on a callable), and bare `Callable` was reported as undefined
## 1.1.425
- New `typedef T1 = T2` statement, fully equivalent to `T1: t.CTypedef = T2`. It desugars in the parser into the same annotated-assignment tree (synthetic zero-length `t.CTypedef`), so binding, type evaluation, hover and semantic highlighting all behave identically — and it works without the file having to `import t`. `typedef` is a soft keyword, so `typedef = 1` / `typedef(x)` still parse as before. `typedef` is also highlighted as a declaration keyword now
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "vscode-vipeye",
"version": "1.1.425",
"version": "1.1.426",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vscode-vipeye",
"version": "1.1.425",
"version": "1.1.426",
"license": "MIT",
"dependencies": {
"mermaid": "^11.12.0",
+16 -2
View File
@@ -1,8 +1,8 @@
{
"name": "vipeye",
"name": "vscode-vipeye",
"displayName": "Vipeye",
"description": "VS Code language support for Viper (LSP client)",
"version": "1.1.425",
"version": "1.1.426",
"private": true,
"license": "MIT",
"author": {
@@ -330,6 +330,11 @@
"command": "viper.openVpjAsJson",
"title": "Open .vpj as JSON",
"category": "Viper"
},
{
"command": "viper.countLines",
"title": "Count Project LOC",
"category": "Viper"
}
],
"menus": {
@@ -390,6 +395,10 @@
{
"command": "viper.openVpjAsJson",
"when": "resourceExtname == .vpj"
},
{
"command": "viper.countLines",
"when": "resourceExtname == .vpj"
}
],
"editor/title/context": [
@@ -404,6 +413,11 @@
"command": "viper.openVpjAsJson",
"when": "resourceExtname == .vpj",
"group": "1_open"
},
{
"command": "viper.countLines",
"when": "resourceExtname == .vpj",
"group": "1_open@2"
}
],
"view/title": [
@@ -47,7 +47,19 @@
},
{
"name": "storage.modifier.viper",
"match": "\\b(inline|export|extern)\\b(?=\\s+(?:def|fn|fun|func|function)\\b|\\s+[A-Za-z_][A-Za-z0-9_]*|[ \\t]*$)"
"match": "\\b(inline|export|extern)\\b(?=\\s+(?:def|fn|fun|func|function|while|for)\\b|\\s+[A-Za-z_][A-Za-z0-9_]*|[ \\t]*$)"
},
{
"name": "storage.modifier.viper",
"match": "\\b(virtual|novirtual)\\b(?=\\s+(?:class|def|fn|fun|func|function)\\b)"
},
{
"name": "storage.modifier.viper",
"match": "\\b(const|volatile)\\b(?=\\s+[A-Za-z_][A-Za-z0-9_]*|\\s*\\[)"
},
{
"name": "storage.modifier.viper",
"match": "\\b__attribute__(?=\\s*\\()"
},
{
"name": "storage.type.class.viper",