基本对齐了现阶段 Viper 的所有必要功能,但还缺少反向测试
Validation / Typecheck (push) Has been cancelled
Validation / Style (push) Has been cancelled
Validation / Test macos-latest (push) Has been cancelled
Validation / Test ubuntu-latest (push) Has been cancelled
Validation / Test windows-latest (push) Has been cancelled
Validation / Build (push) Has been cancelled
Validation / Required (push) Has been cancelled

This commit is contained in:
TermiNexus committed 2026-08-05 18:15:53 +08:00
1 parent b47f43d5f2
commit be9ff90d11
14 files changed
+956 -38

No files matched your search

View File
File renamed without changes.
View File
File renamed without changes.
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "vipeye-internal",
"displayName": "vipeye",
"description": "Type checker for the Viper language",
"version": "1.1.411",
"version": "1.1.412",
"license": "MIT",
"private": true,
"files": [
@@ -9,6 +9,7 @@
*/
import { appendArray } from '../common/collectionUtils';
import { IsViperDialect } from '../common/dialect';
import { ArgCategory, CallNode, DecoratorNode, FunctionNode, ParamCategory, ParseNodeType } from '../parser/parseNodes';
import { getDeclaration, getFileInfo } from './analyzerNodeInfo';
import {
@@ -412,6 +413,22 @@ function getTypeOfDecorator(evaluator: TypeEvaluator, node: DecoratorNode, funct
const decoratorTypeResult = evaluator.getTypeOfExpression(node.d.expr, flags);
// Viper 扩展:t.CVTable / t.Object / t.NoVTable 是类型透传装饰器
// (签名 `(cls: Type[T]) -> Type[T]`)。在 Viper 方言下,泛型 `Type[T]`
// 的参数绑定在 stub 求值中可能退化为裸 `Type`,导致被装饰类被解析为
// `Type` 而非 `type[Class]`,进而丢失全部成员(如 __enter__/__exit__)。
// 这些装饰器语义上就是"返回原类",因此直接透传输入类型。
if (IsViperDialect(getFileInfo(node).executionEnvironment.dialect)) {
const decExpr = node.d.expr as any;
const decName =
decExpr?.nodeType === 'Call'
? decExpr?.d?.leftExpr?.d?.member?.d?.value ?? decExpr?.d?.leftExpr?.d?.value
: decExpr?.d?.member?.d?.value ?? decExpr?.d?.value;
if (decName === 'CVTable' || decName === 'Object' || decName === 'NoVTable') {
return functionOrClassType;
}
}
// Special-case the combination of a classmethod decorator applied
// to a property. This is allowed in Python 3.9, but it's not reflected
// in the builtins.pyi stub for classmethod.
@@ -31,6 +31,7 @@ import {
import { CodeFlowReferenceExpressionNode } from './codeFlowTypes';
import { addConstraintsForExpectedType } from './constraintSolver';
import { ConstraintTracker } from './constraintTracker';
import { DeclarationType } from './declaration';
import { getTypeVarScopesForNode, isMatchingExpression } from './parseTreeUtils';
import { getTypedDictMembersForClass } from './typedDicts';
import { EvalFlags, TypeEvaluator, TypeResult } from './typeEvaluatorTypes';
@@ -54,6 +55,9 @@ import {
isAnyOrUnknown,
isClass,
isClassInstance,
isCPtrType,
isCTypeSubclassType,
isCVoidType,
isInstantiableClass,
isNever,
isSameWithoutLiteralValue,
@@ -664,6 +668,42 @@ function getPositionalMatchArgNames(evaluator: TypeEvaluator, type: ClassType):
}
}
// Viper 扩展:REnum 变体类的 match 位置参数。
// Viper 的 REnum(t.REnum)嵌套类变体(如 class LLVMType 内的 class Ptr)
// 没有 __match_args__,但 match 模式 `LLVMType.Ptr(pointee)` 允许按声明顺序
// 解构其类型注解字段。Pyright 默认对无 __match_args__ 的类只允许 0 个位置参数,
// 会把 `LLVMType.Ptr(pointee)` 误报为"位置模式太多"。
// 此处返回类中所有类型注解字段名作为位置参数名。
// 判断依据:类自身是 CType 子类(CPtr/CVoid),或声明在 CType 子类内部
// (REnum 的嵌套类变体)。
const isNestedInCType =
(() => {
const decl = type.shared.declaration;
const classNode = decl?.type === DeclarationType.Class ? decl.node : undefined;
if (!classNode) {
return false;
}
// 嵌套类的直接父节点是外层类的 Suite(nodeType=50),
// Suite 的父节点才是外层 ClassNode(nodeType=10)。
const parentNode = classNode.parent;
const grandparentNode =
parentNode?.nodeType === ParseNodeType.Suite ? parentNode.parent : undefined;
return grandparentNode?.nodeType === ParseNodeType.Class;
})();
if (isCTypeSubclassType(type) || isCPtrType(type) || isCVoidType(type) || isNestedInCType) {
const fieldNames: string[] = [];
const fields = type.shared.fields;
if (fields) {
for (const [name, symbol] of fields) {
const decls = symbol.getDeclarations();
if (decls.some((decl) => decl.type === DeclarationType.Variable)) {
fieldNames.push(name);
}
}
}
return fieldNames;
}
return [];
}
@@ -2655,7 +2655,6 @@ export function createTypeEvaluator(
if (!boundMethodResult || boundMethodResult.typeErrors) {
return undefined;
}
if (isFunctionOrOverloaded(boundMethodResult.type)) {
return boundMethodResult.type;
}
@@ -5343,10 +5342,19 @@ export function createTypeEvaluator(
if (name === 'reveal_type' || name === 'reveal_locals') {
type = AnyType.create();
} else {
// Viper 扩展:前向引用支持。
// Viper 是 AOT 语言,结构体/函数不在乎定义顺序(类似 C 语言的前向声明)。
// 当符号未找到时(可能在更后方定义),不报 undefinedVariable 错误,
// 而是返回 UnknownType,允许后续定义的符号被引用。
// Viper 扩展:前向引用支持(收紧版)。
// Viper 是 AOT 语言,函数/类的定义顺序无关(类似 C 语言的前向声明),
// 但绑定器在分析开始前已预扫描全部声明,真实的前向引用目标
// (如 viperlib.vp 中 sprintf 引用后方的 vsnprintf)会出现在符号表中
// (symbolWithScope 非空),不会进入此分支。
// 走到这里的名字(symbolWithScope 为空)在整个文件+可见作用域中
// 均无任何声明,属于真正的未定义符号(如拼写错误 count1),
// 此时应报 undefined variable,而不是静默返回 Unknown。
addDiagnostic(
DiagnosticRule.reportUndefinedVariable,
LocMessage.symbolIsUndefined().format({ name }),
node
);
type = UnknownType.create();
}
}
@@ -8615,7 +8623,7 @@ export function createTypeEvaluator(
// 返回特殊化为 Unknown 的 CPtr[Unknown] 实例,使任何 CPtr[X] 特殊化指针
// 都能通过泛型匹配赋值(Unknown 匹配一切类型参数)。
// 查找失败返回 undefined。
function getViperCPtrType(node: ExpressionNode): Type | undefined {
function getViperCPtrType(node: ParseNode): Type | undefined {
const tSymbolWithScope = lookUpSymbolRecursive(node, 't', /* honorCodeFlow */ true);
if (tSymbolWithScope) {
const tEffectiveType = getEffectiveTypeOfSymbolForUsage(
@@ -8640,6 +8648,28 @@ export function createTypeEvaluator(
return undefined;
}
// Viper 扩展:判断类型是否为 REnum 变体类。
// Viper 的 REnum(t.REnum)类内部定义嵌套类变体(如 class LLVMType 内的
// class Int/Ptr/Func/Struct 等)。这些嵌套类不继承 CType,但语义上是
// REnum 的 tagged-union 变体:match 模式 `LLVMType.Ptr(pointee)` 按字段
// 顺序解构,构造调用 `LLVMType.Int(bits)` 按字段顺序传参。
// 判断依据:类声明嵌套在另一个类(外层 REnum)的 suite 中。
function isViperREnumVariantType(type: Type): boolean {
if (!isClass(type)) {
return false;
}
const decl = type.shared.declaration;
const classNode = decl?.type === DeclarationType.Class ? decl.node : undefined;
if (!classNode) {
return false;
}
// 嵌套类的直接父节点是外层类的 Suite(nodeType=50),
// Suite 的父节点才是外层 ClassNode(nodeType=10)。
const parentNode = classNode.parent;
const grandparentNode = parentNode?.nodeType === ParseNodeType.Suite ? parentNode.parent : undefined;
return grandparentNode?.nodeType === ParseNodeType.Class;
}
// Viper 扩展:构造带 t.CChar 类型别名信息的 int 类型(表示 C 的 char 类型)。
// Viper 中 str/bytes 是 i8* 指针,下标访问/迭代/字符运算返回 t.CChar(int 别名)。
// 优先从 t 模块查找 CChar 类型别名以保留显示信息;找不到时回退为
@@ -9285,6 +9315,34 @@ export function createTypeEvaluator(
return false;
}
// Viper 扩展:判断调用节点是否为 stdarg.va_arg 调用。
// va_arg 通过 `from stdarg import va_arg` 引入,baseTypeResult 为 FunctionType,
// 其 shared.name 为 'va_arg'。TPV 将 va_arg 视为 builtin,第二参数是类型名。
function isViperVaArgCall(node: CallNode, baseTypeResult: TypeResult): boolean {
return (
isFunction(baseTypeResult.type) &&
baseTypeResult.type.shared.name === 'va_arg' &&
baseTypeResult.type.shared.moduleName === 'stdarg'
);
}
// Viper 扩展:判断调用节点是否为 c 模块的 Deref/Load/DerefAs/Set 调用。
// 这些函数在 c.vpi 存根中声明为 int 参数,但 Viper 语义接受任意指针表达式。
function isViperCDerefCall(node: CallNode, baseTypeResult: TypeResult): boolean {
const leftExpr = node.d.leftExpr;
if (leftExpr.nodeType !== ParseNodeType.MemberAccess) {
return false;
}
const memberName = leftExpr.d.member.d.value;
if (memberName !== 'Deref' && memberName !== 'Load' && memberName !== 'DerefAs' && memberName !== 'Set') {
return false;
}
return (
isFunction(baseTypeResult.type) &&
baseTypeResult.type.shared.moduleName === 'c'
);
}
// Viper 扩展:检查类型注解 AST 节点是否包含 CPtr 引用。
// 用于判断循环引用是否被指针打破(如 next: 'Node' | t.CPtr)。
// 递归遍历类型注解表达式,查找名称为 CPtr/CNeedPtr/CAutoPtr/CArrayPtr 的标识符。
@@ -9618,6 +9676,18 @@ export function createTypeEvaluator(
} else {
typeResult.type = instanceType;
}
} else if (
// Viper 扩展:拦截 REnum 变体类构造调用。
// Viper 的 REnum 变体(如 LLVMType.Int(bits)、LLVMType.Struct(fields, fcount, name))
// 没有 __init__,但构造调用按字段声明顺序接收参数(tagged-union 语义)。
// Pyright 默认对无 __init__ 的类只允许 0 个位置参数,会把
// `LLVMType.Int(1)` 误报为"应为 0 个位置参数"。
// 此处跳过参数校验,直接返回变体类的实例类型。
IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) &&
isInstantiableClass(baseTypeResult.type) &&
isViperREnumVariantType(baseTypeResult.type)
) {
typeResult.type = ClassType.cloneAsInstance(baseTypeResult.type);
} else if (
// Viper 扩展:拦截 __sizeof__ 调用。
// Viper 中 __sizeof__ 语义是 C 语言的 sizeof(Type) -> int,
@@ -9630,6 +9700,48 @@ export function createTypeEvaluator(
// Viper 的 __sizeof__ 是类方法(sizeof(Type)),返回类型大小(字节数)。
// 跳过 object.__sizeof__ 的实例方法参数检查,直接返回 int。
typeResult.type = getBuiltInObject(node, 'int');
} else if (
// Viper 扩展:拦截 stdarg.va_arg 调用。
// TPV 将 va_arg 视为 builtin(见 HandlesExprBuiltin._HandleVaArgLlvm),
// 第二参数是类型名(如 t.CInt64T / t.CPtr / t.CInt64T | t.CPtr),
// 而非 CPtr 值。若按 stdarg.vp 的声明 `va_arg(args: t.CPtr, type: t.CPtr)`
// 做普通参数校验,会把类型名当作 CPtr 值检查而误报。
// 返回类型 = 第二参数类型形式对应的实例类型;无第二参数时返回 Unknown
// (由赋值上下文注解推断,如 `ptr_i: t.CInt = va_arg(ap)`)。
IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) &&
isViperVaArgCall(node, baseTypeResult)
) {
if (node.d.args.length >= 2) {
const typeExpr = node.d.args[1].d.valueExpr;
const typeFormResult = getTypeOfExpression(typeExpr, EvalFlags.TypeExpression);
const typeFormType = stripTypeForm(typeFormResult.type);
typeResult.type = isInstantiableClass(typeFormType)
? convertToInstance(typeFormType)
: typeFormType;
} else {
typeResult.type = UnknownType.create();
}
} else if (
// Viper 扩展:拦截 c.Deref / c.Load / c.DerefAs / c.Set 调用。
// TPV 将 Deref 等视为 builtin(见 lib/core/Handles/HandlesSpecialCall.py),
// 指针参数接受任意指针表达式(str/bytes/t.CPtr 等,i8*)。
// c.vpi 存根中 `Deref(ptr: int)` 的 int 参数声明与 Viper 语义不符
// (Viper 中 str 是 i8*,str→int 不允许隐式转换),
// 按普通校验会误报 "str 不可分配给 int"。
// 此处跳过参数检查,返回:Deref → 类型参数 T 或 Unknown;其余 → Unknown。
IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) &&
isViperCDerefCall(node, baseTypeResult)
) {
if (node.d.leftExpr.nodeType === ParseNodeType.MemberAccess &&
node.d.leftExpr.d.member.d.value === 'Deref' &&
node.d.args.length >= 1) {
// c.Deref(ptr) → 解引用结果。返回类型为 c.Deref 的 T 类型参数
// 对应的实例类型(从 c.vpi 的 `def Deref(ptr: int) -> T` 推导)。
// 简化:返回 Unknown,由赋值上下文推断(如 `v: int = c.Deref(p)`)。
typeResult.type = UnknownType.create();
} else {
typeResult.type = UnknownType.create();
}
} else {
const callResult = validateCallArgs(
node,
@@ -20534,8 +20646,28 @@ export function createTypeEvaluator(
!isOptionalType(type) &&
!AnalyzerNodeInfo.getFileInfo(param).diagnosticRuleSet.strictParameterNoneValue
) {
// Viper 扩展:None 在 Viper 中对应 C 的 NULL(空指针),而不是
// Python 语义下的 Optional 标记。当参数注解本身就是指针类类型
// (str/bytes 是 i8*、CPtr、CVoid、函数指针、纯 C 修饰符),
// `= None` 只是表示默认值为空指针,无需展开为 `X | None`。
// 若展开,`app_deps: str = None` 会变成 `str | None`,
// 导致函数体内 app_deps[0] 这类下标访问误报 reportOptionalSubscript。
// 仅对值类型(int/bool/float 等)保留 PEP 484 展开语义。
const isViperPtrLike = (() => {
const variants = isUnion(type) ? type.priv.subtypes : [type];
return variants.some(
(variant) =>
isCPtrType(variant) ||
isCVoidType(variant) ||
isPureCModifierType(variant) ||
isFunction(variant) ||
(isClassInstance(variant) && ClassType.isBuiltIn(variant, ['str', 'bytes']))
);
})();
if (!isViperPtrLike) {
return combineTypes([type, getNoneType()]);
}
}
return type;
}
@@ -20701,6 +20833,19 @@ export function createTypeEvaluator(
return ClassType.cloneForPacked(type);
}
// Viper 扩展:`*args` 在函数体内绑定为 `va_list`(即 `CPtr[Unknown]`,
// 对应 LLVM 的 i8*),而非 Python 语义下的 tuple。TPV 在函数入口对
// `*args` 变量执行 va_start 并注册为裸指针(见 HandlesFunctions.py:
// `Gen._direct_values[vararg_name] = va_list_ptr`),因此函数体内
// 直接传递 `args` 给另一个 variadic 函数(如 vsnprintf 的 `ap` 参数)
// 是合法的指针透传,不应报 tuple 类型不兼容。
if (IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect)) {
const cptrType = getViperCPtrType(node);
if (cptrType) {
return cptrType;
}
}
return makeTupleObject(evaluatorInterface, [{ type, isUnbounded: !isTypeVarTuple(type) }]);
}
@@ -21276,11 +21421,20 @@ export function createTypeEvaluator(
}
if (isClass(subtype)) {
// Viper 扩展:`__exit__` 无需异常参数(Viper 无异常机制),
// 签名仅为 `def __exit__(self):`。若按 CPython 惯例传
// (exc_type, exc_val, exc_tb) 三个参数会报参数过多,
// 导致 magic method 调用失败并误报 "not usable with"。
const anyArg: TypeResult = { type: AnyType.create() };
const exitArgList: TypeResult[] = IsViperDialect(
AnalyzerNodeInfo.getFileInfo(node.d.expr).executionEnvironment.dialect
)
? []
: [anyArg, anyArg, anyArg];
const exitTypeResult = getTypeOfMagicMethodCall(
subtype,
exitMethodName,
[anyArg, anyArg, anyArg],
exitArgList,
node.d.expr,
/* inferenceContext */ undefined,
exitDiag
@@ -23104,7 +23258,23 @@ export function createTypeEvaluator(
}
if (usageScopeNode === declScopeNode) {
if (!isFlowPathBetweenNodes(declNode, node)) {
// Viper 扩展:类/函数声明的引用不受代码流可达性限制。
// Viper 是 AOT 语言,支持前向引用(类似 C 语言的前向声明)。
// 例如 `class ParamNode(GSListNode[ParamNode])` 中基类类型参数
// 引用自身(ParamNode 声明节点在引用节点之后,代码流不可达),
// 以及函数体引用后方定义的函数/类。这些都应视为合法引用,
// 跳过代码流过滤,否则会误报未定义。
const declIsClassOrFunc =
decl.type === DeclarationType.Class ||
decl.type === DeclarationType.Function ||
decl.type === DeclarationType.TypeAlias;
if (
!isFlowPathBetweenNodes(declNode, node) &&
!(
IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) &&
declIsClassOrFunc
)
) {
// If there was no control flow path from the usage back
// to the source, see if the usage node is reachable by
// any path.
@@ -24102,13 +24272,47 @@ export function createTypeEvaluator(
}
}
return applyLoaderActionsToModuleType(
moduleType,
// Viper 扩展:合并同一符号的所有多段 import 声明的 implicitImports。
// 例如 `import os.path` + `import os._win32` + `import os._posix` 时,
// os 符号有多个 Alias 声明,每个声明只记录各自的子模块。Python/Viper
// 的模块加载器会把所有子模块都挂载到父模块命名空间,因此这里把所有
// 声明的 implicitImports 合并后再应用 loader actions,避免 `os._win32`
// 被误报为"不是模块 os 的已知属性"。
const baseLoader =
resolvedDecl.symbolName && resolvedDecl.submoduleFallback
? resolvedDecl.submoduleFallback
: resolvedDecl,
importLookup
);
: resolvedDecl;
const mergedImplicitImports = new Map(baseLoader.implicitImports ?? []);
symbol.getDeclarations().forEach((siblingDecl) => {
if (siblingDecl.type !== DeclarationType.Alias) {
return;
}
const siblingResolved = resolveAliasDeclaration(siblingDecl, /* resolveLocalNames */ true, {
allowExternallyHiddenAccess: AnalyzerNodeInfo.getFileInfo(siblingDecl.node).isStubFile,
});
if (!siblingResolved || siblingResolved.type !== DeclarationType.Alias) {
return;
}
const siblingLoader =
siblingResolved.symbolName && siblingResolved.submoduleFallback
? siblingResolved.submoduleFallback
: siblingResolved;
siblingLoader.implicitImports?.forEach((action, name) => {
if (!mergedImplicitImports.has(name)) {
mergedImplicitImports.set(name, action);
}
});
});
let mergedLoaderActions = baseLoader;
if (mergedImplicitImports.size > (baseLoader.implicitImports?.size ?? 0)) {
mergedLoaderActions = {
...baseLoader,
implicitImports: mergedImplicitImports,
};
}
return applyLoaderActionsToModuleType(moduleType, mergedLoaderActions, importLookup);
}
const declaredType = getTypeForDeclaration(resolvedDecl);
@@ -25999,6 +26203,59 @@ export function createTypeEvaluator(
if (destIsStrBytes) {
return true;
}
// Viper 扩展:数组名退化为指向首元素的指针(C 语义 array-to-pointer decay)。
// 栈上数组 t.CArray[T, N] 作为实参传递时退化为 T*(CPtr[T]),如
// `transform(block: CPtr[CUInt8T])` 接收 `buf: CArray[CUInt8T, CDefine]`。
// 目标可以是 CPtr[T] 或 CVoid(void* 通用指针)。
if (isClassInstance(destType) && (isCPtrType(destType) || isCVoidType(destType))) {
const destTypeArgs = destType.priv.typeArgs;
if (isCVoidType(destType)) {
return true;
}
const srcTypeArgs = (srcType as ClassType).priv.typeArgs;
const srcElemType = srcTypeArgs && srcTypeArgs.length > 0 ? srcTypeArgs[0] : undefined;
const destPointee =
destTypeArgs && destTypeArgs.length > 0 ? destTypeArgs[0] : undefined;
if (srcElemType && destPointee) {
// 数组元素类型与指针目标类型兼容即可(同类或可隐式转换)。
return assignType(
destPointee,
srcElemType,
diag,
constraints,
flags | AssignTypeFlags.SkipRecursiveTypeCheck,
recursionCount + 1
);
}
return srcElemType !== undefined;
}
}
// Viper 扩展:指针 → CArray 形参调整(C 语义 parameter adjustment,与
// array-to-pointer decay 互逆)。C 中函数参数声明为数组 T[N] 时自动调整为
// 指针 T*,因此实参指针(CPtr[T] / void*)可以传给"数组"形参。例如
// `transform(block: t.CArray[t.CUInt8T, N])` 接收 `data: t.CUInt8T | t.CPtr`。
if (isCArrayType(destType) && isClassInstance(srcType) && (isCPtrType(srcType) || isCVoidType(srcType))) {
if (isCVoidType(srcType)) {
return true;
}
const srcTypeArgs = srcType.priv.typeArgs;
const srcPointee = srcTypeArgs && srcTypeArgs.length > 0 ? srcTypeArgs[0] : undefined;
const destTypeArgs = (destType as ClassType).priv.typeArgs;
const destElemType = destTypeArgs && destTypeArgs.length > 0 ? destTypeArgs[0] : undefined;
if (srcPointee && destElemType) {
// 指针目标类型与数组元素类型兼容即可。
return assignType(
destElemType,
srcPointee,
diag,
constraints,
flags | AssignTypeFlags.SkipRecursiveTypeCheck,
recursionCount + 1
);
}
return srcPointee !== undefined;
}
// Viper 扩展:函数名退化为函数指针(C 语义)。
@@ -26191,11 +26448,15 @@ export function createTypeEvaluator(
}
if (pointeeAsClass) {
const inheritanceChain: InheritanceChain = [];
const isDerivedFrom = ClassType.isDerivedFrom(
srcType,
pointeeAsClass,
inheritanceChain
);
// 统一比较形式:baseClasses 里存的是 instantiable 基类,
// 与 destPointee 统一转成 instantiable 形式再比较,避免
// isSameGenericClass 因 instance/instantiable 差异误判。
const srcForCompare = convertToInstantiable(srcType);
const pointeeForCompare = convertToInstantiable(pointeeAsClass);
const isDerivedFrom =
isInstantiableClass(srcForCompare) &&
isInstantiableClass(pointeeForCompare) &&
ClassType.isDerivedFrom(srcForCompare, pointeeForCompare, inheritanceChain);
if (isDerivedFrom) {
return true;
}
@@ -635,6 +635,7 @@ export function getOffDiagnosticRuleSet(dialect?: LanguageDialect): DiagnosticRu
diagSettings.reportDeprecated = 'none';
diagSettings.reportMissingTypeStubs = 'none';
diagSettings.reportMissingModuleSource = 'none';
diagSettings.reportMissingImports = 'warning';
diagSettings.reportTypeCommentUsage = 'none';
diagSettings.reportIncompleteStub = 'none';
diagSettings.reportInvalidStubStatement = 'none';
@@ -742,7 +743,7 @@ export function getBasicDiagnosticRuleSet(dialect?: LanguageDialect): Diagnostic
reportImplicitStringConcatenation: 'none',
reportUnboundVariable: 'error',
reportUnhashable: 'error',
reportUndefinedVariable: 'error',
reportUndefinedVariable: 'warning',
reportInvalidStubStatement: 'none',
reportIncompleteStub: 'none',
reportUnsupportedDunderAll: 'warning',
@@ -867,7 +868,7 @@ export function getStandardDiagnosticRuleSet(dialect?: LanguageDialect): Diagnos
reportImplicitStringConcatenation: 'none',
reportUnboundVariable: 'error',
reportUnhashable: 'error',
reportUndefinedVariable: 'error',
reportUndefinedVariable: 'warning',
reportInvalidStubStatement: 'none',
reportIncompleteStub: 'none',
reportUnsupportedDunderAll: 'warning',
@@ -885,6 +886,7 @@ export function getStandardDiagnosticRuleSet(dialect?: LanguageDialect): Diagnos
diagSettings.reportDeprecated = 'none';
diagSettings.reportMissingTypeStubs = 'none';
diagSettings.reportMissingModuleSource = 'none';
diagSettings.reportMissingImports = 'warning';
diagSettings.reportTypeCommentUsage = 'none';
diagSettings.reportIncompleteStub = 'none';
diagSettings.reportInvalidStubStatement = 'none';
@@ -995,7 +997,7 @@ export function getStrictDiagnosticRuleSet(dialect?: LanguageDialect): Diagnosti
reportImplicitStringConcatenation: 'none',
reportUnboundVariable: 'error',
reportUnhashable: 'error',
reportUndefinedVariable: 'error',
reportUndefinedVariable: 'warning',
reportInvalidStubStatement: 'error',
reportIncompleteStub: 'error',
reportUnsupportedDunderAll: 'error',
@@ -1013,6 +1015,7 @@ export function getStrictDiagnosticRuleSet(dialect?: LanguageDialect): Diagnosti
diagSettings.reportDeprecated = 'none';
diagSettings.reportMissingTypeStubs = 'none';
diagSettings.reportMissingModuleSource = 'none';
diagSettings.reportMissingImports = 'warning';
diagSettings.reportTypeCommentUsage = 'none';
diagSettings.reportIncompleteStub = 'none';
diagSettings.reportInvalidStubStatement = 'none';
@@ -14,10 +14,11 @@
* - 基础类型分类:class/function/parameter/namespace
*/
import * as AnalyzerNodeInfo from '../analyzer/analyzerNodeInfo';
import { Declaration, DeclarationType, VariableDeclaration } from '../analyzer/declaration';
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
import { isWithinTypeAnnotation } from '../analyzer/parseTreeUtils';
import { TypeCategory } from '../analyzer/types';
import { TypeCategory, isCDefineType } from '../analyzer/types';
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { LogTracker } from '../common/logTracker';
import { ProgramView } from '../common/extensibility';
@@ -468,9 +469,14 @@ class SemanticTokenWalker extends ParseTreeWalker {
return true;
}
/** 访问 ModuleNameNode:import 语句中的模块名路径部分 → namespace。
/** 访问 ModuleNameNode:import 语句中的模块名路径部分。
* 解析成功的模块 → namespace(绿色);未解析的模块(如不存在的
* `from .visitor import ...`)→ undefined(白色,与诊断一致)。
* 返回 false 阻止遍历 nameParts(避免重复 token,因 visitName 也会处理) */
override visitModuleName(node: ModuleNameNode): boolean {
const importInfo = AnalyzerNodeInfo.getImportInfo(node);
// 模块整体未解析:每个 namePart 都视为未定义(白色)
const isUnresolved = !!importInfo && !importInfo.isImportFound && !importInfo.isNativeLib;
for (const part of node.d.nameParts) {
const position = convertOffsetToPosition(part.start, this._lines);
if (position && part.length > 0) {
@@ -478,7 +484,9 @@ class SemanticTokenWalker extends ParseTreeWalker {
line: position.line,
startChar: position.character,
length: part.length,
tokenType: SemanticTokenTypes.namespace,
tokenType: isUnresolved
? SemanticTokenTypes.undefined
: SemanticTokenTypes.namespace,
tokenModifiers: 0,
});
}
@@ -634,8 +642,43 @@ class SemanticTokenWalker extends ParseTreeWalker {
}
break;
case DeclarationType.Alias:
// import 的模块别名
// import 的模块别名。按别名实际指向的类型分类:
// `from .base import AST` 中 AST 实际是类 → class;
// `import os.path` 中 os 是模块 → namespace。
// 避免"无论实际上是什么都显示成绿色(namespace)"。
if (type) {
if (type.category === TypeCategory.Unknown ||
type.category === TypeCategory.Any ||
type.category === TypeCategory.Unbound) {
// 未解析的导入(如 `from .visitor import ASTVisitor`,
// visitor 模块不存在):标 undefined(白色),与诊断一致。
tokenType = SemanticTokenTypes.undefined;
} else if (isCDefineType(type)) {
// t.CDefine 常量(如 FLAG_IS_ASYNC: t.CDefine = 1):
// 编译时元指令,按常量显示(variable + readonly)。
tokenType = SemanticTokenTypes.variable;
tokenModifiers |= SemanticTokenModifiers.readonly;
} else if (type.category === TypeCategory.Class) {
tokenType = BUILTIN_CLASS_NAMES.has(nameText)
? SemanticTokenTypes.class
: (BUILTIN_TYPE_NAMES.has(nameText)
? SemanticTokenTypes.builtin
: SemanticTokenTypes.class);
} else if (
type.category === TypeCategory.Function ||
type.category === TypeCategory.Overloaded
) {
tokenType = SemanticTokenTypes.function;
} else if (type.category === TypeCategory.Module) {
tokenType = SemanticTokenTypes.namespace;
} else if (type.category === TypeCategory.TypeVar) {
tokenType = SemanticTokenTypes.type;
} else {
tokenType = SemanticTokenTypes.variable;
}
} else {
tokenType = SemanticTokenTypes.namespace;
}
tokenModifiers |= defMod;
break;
case DeclarationType.TypeAlias:
@@ -0,0 +1,113 @@
/*
* viperUnionMemberAccess.test.ts
*
* 验证 Viper 扩展:联合类型 `MemBuddy | CPtr` 的变量访问成员方法(如 realloc)
* 不应报 "Cannot access attribute for class UnionType"。
*
* 场景:TransPyC/Test/BuddyTest/App/mbuddytest.vp
* bd3: memhub.MemBuddy | CPtr = memhub.MemBuddy(arena3, 4096)
* rp2: bytes = bd3.realloc(rp, 128)
*
* 同时覆盖 `with memhub.MemBuddy(...) as wbd:` 上下文管理器场景:
* - @t.CVTable 装饰的 MemManager 类必须解析为 type[MemManager] 而非 Type
* - __enter__ 返回 'MemManager' | t.CPtr 折叠为 CPtr[MemManager]
* - wbd.alloc / bd3.realloc 成员访问不报 Unknown
*
* 运行:npx jest src/tests/viperUnionMemberAccess.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 BUDDY_TEST = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\Test\\BuddyTest\\App\\mbuddytest.vp';
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
function buildProgram(targetPath: 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 uri = UriEx.file(targetPath);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环直到分析完成
}
return { program, serviceProvider, uri, configOptions };
}
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any): any[] {
const sourceFile = program.getSourceFile(uri);
assert(sourceFile, 'source file not found');
return (sourceFile.getDiagnostics(configOptions) || []).map((diag) => ({
category: diag.category,
message: diag.message,
range: diag.range ? `L${diag.range.start.line + 1}` : '(no range)',
}));
}
test('BuddyTest 联合类型变量访问 realloc/alloc 成员不报 UnionType/Unknown 错误', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(BUDDY_TEST);
const diagnostics = getDiagnostics(program, configOptions, uri);
// 断言:联合类型 `MemBuddy | CPtr` 的成员访问(realloc/alloc/__enter__/__exit__)无错误。
const unionErrors = diagnostics.filter(
(d) =>
d.message.includes('UnionType') ||
d.message.includes('realloc') ||
d.message.includes('alloc') ||
d.message.includes('__enter__') ||
d.message.includes('__exit__') ||
d.message.includes('不能与 "with" 一起使用')
);
assert.strictEqual(
unionErrors.length,
0,
`unexpected union/member access errors: ${JSON.stringify(unionErrors)}`
);
// 排除合理的既有告警(未使用的 import/变量),不应有 category=0/1 的其它错误。
// 唯一允许的已知源码问题:mbuddytest.vp L209 引用了 MemBuddy 不存在的属性 "mem"
// (应为 base,属于 TransPyC 测试源码自身的问题,LSP 报错是正确的)。
const hardErrors = diagnostics.filter((d) => d.category === 0 || d.category === 1);
const knownSourceIssue = hardErrors.filter((d) => d.range === 'L209' && d.message.includes('mem'));
assert.strictEqual(
hardErrors.length,
knownSourceIssue.length,
`unexpected hard errors: ${JSON.stringify(hardErrors)}`
);
program.dispose();
serviceProvider.dispose();
});
@@ -0,0 +1,399 @@
/*
* viperVariadicArgs.test.ts
*
* 验证 Viper 扩展:`*args` 在函数体内应绑定为 `va_list`(指针),而非 Python 语义的 tuple。
*
* 场景:TransPyC/includes/viperlib.vp
* def sprintf(buf: t.CChar | t.CPtr, rule: str, *args) -> t.CVoid | t.CExport:
* if buf is None or rule is None: return
* vsnprintf(buf, 0x7FFFFFFF, rule, args) # 不应报 tuple 不能赋值给 CPtr[CChar]
*
* 同时覆盖 vsprintf / snprintf 内部将 `args` 透传给 vsnprintf 的场景。
*
* 运行:npx jest src/tests/viperVariadicArgs.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 VIPERLIB_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\viperlib.vp';
const MEMHUB_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\memhub.vp';
const REQUESTS_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\requests.vp';
const ARGPARSE_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\argparse.vp';
const TYPES_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\llvmlite\\__types.vp';
const OS_INIT_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\os\\__init__.vp';
const SHA256_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\hashlib\\__sha256.vp';
const SHA512_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\hashlib\\__sha512.vp';
const AST_INIT_PATH = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes\\ast\\__init__.vp';
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
function buildProgram(targetPath: 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 uri = UriEx.file(targetPath);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环直到分析完成
}
return { program, serviceProvider, uri, configOptions };
}
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any): any[] {
const sourceFile = program.getSourceFile(uri);
assert(sourceFile, 'source file not found');
return (sourceFile.getDiagnostics(configOptions) || []).map((diag) => ({
category: diag.category,
message: diag.message,
range: diag.range ? `L${diag.range.start.line + 1}` : '(no range)',
}));
}
test('viperlib.vp 中 *args 透传 vsnprintf 不报 tuple 类型错误', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(VIPERLIB_PATH);
const diagnostics = getDiagnostics(program, configOptions, uri);
// 断言:sprintf/vsprintf/snprintf 内部的 `args` 透传(vsnprintf 的 ap 参数)不应报
// tuple 类型不能赋值给 CPtr 的错误。
const variadicErrors = diagnostics.filter(
(d) =>
d.message.includes('tuple') ||
d.message.includes('vsnprintf') ||
d.message.includes('cannot be assigned to parameter "ap"') ||
d.message.includes('参数 "ap"')
);
assert.strictEqual(
variadicErrors.length,
0,
`unexpected variadic arg errors: ${JSON.stringify(variadicErrors)}`
);
// va_arg / c.Deref 的误报(stdarg.vp 声明与 Viper 语义不符)也应消除。
const derefVaArgErrors = diagnostics.filter(
(d) => d.message.includes('va_arg') || d.message.includes('Deref')
);
assert.strictEqual(
derefVaArgErrors.length,
0,
`unexpected va_arg/Deref errors: ${JSON.stringify(derefVaArgErrors)}`
);
// 不应有任何 category=0/1 的错误。
// 唯一允许的已知源码问题:viperlib.vp L13 `return count1` 引用不存在的变量
// (应为 `return count`,属于 TransPyC 源码自身的拼写错误,LSP 报错正确)。
const hardErrors = diagnostics.filter((d) => d.category === 0 || d.category === 1);
const knownSourceIssue = hardErrors.filter(
(d) => d.range === 'L13' && (d.message.includes('count1') || d.message.includes('未定义'))
);
assert.strictEqual(
hardErrors.length,
knownSourceIssue.length,
`unexpected hard errors: ${JSON.stringify(hardErrors)}`
);
program.dispose();
serviceProvider.dispose();
});
test('memhub.vp 中 viperio.Buf.__sizeof__() 不报 Argument missing for parameter "self"', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(MEMHUB_PATH);
const diagnostics = getDiagnostics(program, configOptions, uri);
// __sizeof__ 是 Viper 的类方法(sizeof(Type) -> int),不需要 self 参数。
// 命中场景:memhub.vp L109 `self.alloc(viperio.Buf.__sizeof__())`。
const sizeOfErrors = diagnostics.filter(
(d) => d.message.includes('__sizeof__') || d.message.includes('parameter "self"')
);
assert.strictEqual(
sizeOfErrors.length,
0,
`unexpected __sizeof__ errors: ${JSON.stringify(sizeOfErrors)}`
);
// 不应有任何 category=0/1 的错误。
const hardErrors = diagnostics.filter((d) => d.category === 0 || d.category === 1);
assert.strictEqual(hardErrors.length, 0, `unexpected hard errors: ${JSON.stringify(hardErrors)}`);
program.dispose();
serviceProvider.dispose();
});
test('argparse.vp 中 str = None 参数默认值不报类型不兼容(回归)', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(ARGPARSE_PATH);
const diagnostics = getDiagnostics(program, configOptions, uri);
// 命中场景:argparse.vp L155-158 `short: str = None` / `help: str = None`。
// Viper 下 None 是空指针,str 是 i8*,`str = None` 合法且不应展开为 str | None。
const strNoneErrors = diagnostics.filter(
(d) => d.message.includes('str') && (d.message.includes('None') || d.message.includes('default'))
);
assert.strictEqual(
strNoneErrors.length,
0,
`unexpected str=None errors: ${JSON.stringify(strNoneErrors)}`
);
// 不应有任何 category=0/1 的错误。
const hardErrors = diagnostics.filter((d) => d.category === 0 || d.category === 1);
assert.strictEqual(hardErrors.length, 0, `unexpected hard errors: ${JSON.stringify(hardErrors)}`);
program.dispose();
serviceProvider.dispose();
});
test('__types.vp 嵌套类成员 Pointee: LLVMType | t.CPtr 自我引用不被标为 Unknown', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(TYPES_PATH);
const diagnostics = getDiagnostics(program, configOptions, uri);
// 命中场景:llvmlite/__types.vp 的 class LLVMType(t.REnum) 中,
// class Ptr 的成员 Pointee: LLVMType | t.CPtr 引用外层类 LLVMType。
// 嵌套类成员注解引用外层类名(自我指定)应解析为类类型而非 Unknown。
const unknownErrors = diagnostics.filter(
(d) => d.message.includes('Unknown') && d.message.includes('LLVMType')
);
assert.strictEqual(
unknownErrors.length,
0,
`unexpected Unknown errors for nested self-ref: ${JSON.stringify(unknownErrors)}`
);
// 不应有 TypePrint 参数不匹配(Struct | Unknown)错误。
const typePrintErrors = diagnostics.filter(
(d) => d.message.includes('TypePrint') && d.message.includes('Struct')
);
assert.strictEqual(
typePrintErrors.length,
0,
`unexpected TypePrint errors: ${JSON.stringify(typePrintErrors)}`
);
// REnum 变体构造(LLVMType.Int(1) / LLVMType.Struct(f, c, n))不报位置参数错误。
const variantCtorErrors = diagnostics.filter(
(d) => d.message.includes('位置参数') && d.message.includes('应为 0')
);
assert.strictEqual(
variantCtorErrors.length,
0,
`unexpected REnum variant ctor errors: ${JSON.stringify(variantCtorErrors)}`
);
// 泛型自引用基类(class ParamNode(GSListNode[ParamNode]))不报未定义。
const paramNodeErrors = diagnostics.filter((d) => d.message.includes('ParamNode'));
assert.strictEqual(
paramNodeErrors.length,
0,
`unexpected ParamNode errors: ${JSON.stringify(paramNodeErrors)}`
);
program.dispose();
serviceProvider.dispose();
});
test('os/__init__.vp 中 os._win32 子模块访问不报未知属性', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(OS_INIT_PATH);
const diagnostics = getDiagnostics(program, configOptions, uri);
// 命中场景:os/__init__.vp L6 `import os._win32` 后,L25 `os._win32.exists(path)`。
// Viper 中 `import os._win32` 应把子模块 _win32 注册为 os 模块的属性。
const win32Errors = diagnostics.filter(
(d) => d.message.includes('_win32') && (d.message.includes('known attribute') || d.message.includes('属性'))
);
assert.strictEqual(
win32Errors.length,
0,
`unexpected os._win32 errors: ${JSON.stringify(win32Errors)}`
);
program.dispose();
serviceProvider.dispose();
});
test('requests.vp 中 str = None 默认值不展开为 str | None,无 Optional 误报', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(REQUESTS_PATH);
const diagnostics = getDiagnostics(program, configOptions, uri);
// 命中场景:requests.vp L258 `def post(url: str, data: str = None, ...)`。
// Viper 下 str 是 i8*(可空指针),None 是空指针,不应展开为 str | None,
// 否则 data[0] 等下标访问会误报 reportOptionalSubscript。
const optionalErrors = diagnostics.filter(
(d) => d.message.includes('OptionalSubscript') || d.message.includes('is not subscriptable')
);
assert.strictEqual(
optionalErrors.length,
0,
`unexpected optional subscript errors: ${JSON.stringify(optionalErrors)}`
);
// 不应有任何 category=0/1 的错误。
const hardErrors = diagnostics.filter((d) => d.category === 0 || d.category === 1);
assert.strictEqual(hardErrors.length, 0, `unexpected hard errors: ${JSON.stringify(hardErrors)}`);
program.dispose();
serviceProvider.dispose();
});
test('__sha256.vp 中 CArray 传给 CPtr 参数(数组名退化)不报类型错误', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(SHA256_PATH);
const diagnostics = getDiagnostics(program, configOptions, uri);
// 命中场景:__sha256.vp L95/100/111 `self.transform(self.buf)`,
// self.buf 是 `t.CArray[t.CUInt8T, SHA256_BLOCK_LEN]`,transform 的
// block 参数是 `t.CUInt8T | t.CPtr`(折叠为 CPtr[CUInt8T])。
// C 语义下数组名退化为指向首元素的指针,应允许 CArray → CPtr 传参。
const cptrErrors = diagnostics.filter(
(d) =>
d.message.includes('cannot be assigned to parameter "block"') ||
d.message.includes('参数 "block"') ||
(d.message.includes('CArray') && d.message.includes('CPtr'))
);
assert.strictEqual(cptrErrors.length, 0, `unexpected CArray->CPtr errors: ${JSON.stringify(cptrErrors)}`);
program.dispose();
serviceProvider.dispose();
});
test('__sha512.vp 中 CPtr 传给 CArray 参数(形参调整)不报类型错误', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(SHA512_PATH);
const diagnostics = getDiagnostics(program, configOptions, uri);
// 命中场景:__sha512.vp L119 `self.transform(data)`,
// data 是 `t.CUInt8T | t.CPtr`(折叠为 CPtr[CUInt8T]),transform 的
// block 参数是 `t.CArray[t.CUInt8T, SHA512_BLOCK_LEN]`。
// C 语义下函数参数声明为数组时自动调整为指针(parameter adjustment),
// 应允许 CPtr → CArray 形参传参。
const cptrErrors = diagnostics.filter(
(d) =>
d.message.includes('cannot be assigned to parameter "block"') ||
d.message.includes('参数 "block"') ||
(d.message.includes('CArray') && d.message.includes('CPtr'))
);
assert.strictEqual(cptrErrors.length, 0, `unexpected CPtr->CArray errors: ${JSON.stringify(cptrErrors)}`);
program.dispose();
serviceProvider.dispose();
});
test('ast/__init__.vp 中派生类实例传给基类指针(Expression→CPtr[AST])不报类型错误', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(AST_INIT_PATH);
const diagnostics = getDiagnostics(program, configOptions, uri);
// 命中场景:ast/__init__.vp L105 `return Expression(pool, body)`,
// Expression 继承自 AST,函数返回类型是 `AST | t.CPtr`(折叠为 CPtr[AST])。
// C 语义下派生类指针可隐式 upcast 为基类指针(CPtr[Expression] → CPtr[AST])。
const exprErrors = diagnostics.filter(
(d) => d.message.includes('Expression') && (d.message.includes('返回类型') || d.message.includes('assignable'))
);
assert.strictEqual(exprErrors.length, 0, `unexpected Expression->CPtr[AST] errors: ${JSON.stringify(exprErrors)}`);
program.dispose();
serviceProvider.dispose();
});
/**
* 语义 token 辅助:解码 SemanticTokens 相对编码,返回 line 级 token 映射。
* 返回 Map<line, Array<{start, text, tokenType, tokenModifiers}>>
*/
function getSemanticTokens(program: Program, uri: any): Map<number, any[]> {
const { SemanticTokenProvider } = require('../languageService/semanticTokenProvider');
const { LogTracker } = require('../common/logTracker');
const provider = new SemanticTokenProvider(new LogTracker());
const result = provider.provideFull(program, uri);
assert(result, 'no semantic tokens produced');
const sourceFile = program.getSourceFile(uri);
const lines = sourceFile!.getParseResults()!.text.split('\n');
const map = new Map<number, any[]>();
let cursorLine = 0;
let cursorChar = 0;
for (let i = 0; i < result.data.length; i += 5) {
const deltaLine = result.data[i];
const deltaStart = result.data[i + 1];
const length = result.data[i + 2];
const tokenType = result.data[i + 3];
const tokenMod = result.data[i + 4];
cursorLine += deltaLine;
cursorChar = deltaLine === 0 ? cursorChar + deltaStart : deltaStart;
const lineText = lines[cursorLine] || '';
const text = lineText.substring(cursorChar, cursorChar + length);
const entry = { start: cursorChar, text, tokenType, tokenModifiers: tokenMod };
if (!map.has(cursorLine)) {
map.set(cursorLine, []);
}
map.get(cursorLine)!.push(entry);
}
return map;
}
test('ast/__init__.vp 中不存在的 from .visitor import ASTVisitor 标黄且标白(undefined)', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(AST_INIT_PATH);
// 1. 诊断:不存在的模块 `.visitor` 应标黄(warning,cat=1=Warning)
const diagnostics = getDiagnostics(program, configOptions, uri);
const visitorDiag = diagnostics.find(
(d) => d.range === 'L51' && d.message.includes('visitor')
);
assert(visitorDiag, `expected unresolved '.visitor' diagnostic, got: ${JSON.stringify(diagnostics)}`);
assert.strictEqual(visitorDiag.category, 1, `expected warning(1) for .visitor, got cat=${visitorDiag.category}`);
// 2. 语义 token:`visitor` 和 `ASTVisitor` 都应为 undefined(白色,type=11)
const tokensByLine = getSemanticTokens(program, uri);
const line51 = tokensByLine.get(50); // L51 → 行索引 50
assert(line51, 'expected tokens on line 51');
const visitorToken = line51.find((t) => t.text === 'visitor');
assert(visitorToken, 'expected "visitor" token on line 51');
assert.strictEqual(visitorToken.tokenType, 11, `"visitor" should be undefined(11), got ${visitorToken.tokenType}`);
const astVisitorToken = line51.find((t) => t.text === 'ASTVisitor');
assert(astVisitorToken, 'expected "ASTVisitor" token on line 51');
assert.strictEqual(
astVisitorToken.tokenType,
11,
`"ASTVisitor" should be undefined(11), got ${astVisitorToken.tokenType}`
);
program.dispose();
serviceProvider.dispose();
});
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "vipeye",
"displayName": "Vipeye",
"description": "Type checker for the Viper language",
"version": "1.1.411",
"version": "1.1.412",
"license": "MIT",
"author": {
"name": "TermiNexus"
+10
View File
@@ -1,5 +1,15 @@
# Change Log
## 1.1.412
- 语义高亮:不存在的模块名与未解析的导入符号(如 `from .visitor import ASTVisitor`)标为 undefined(白色)
- 诊断:Viper 方言下 `reportMissingImports` 降级为 warning(黄色波浪线),与 `reportAttributeAccessIssue` 一致
- 语义高亮:import 别名按实际类型着色(类/函数/模块/常量)
- 语义高亮:`t.CDefine` 常量显示为常量色(variable + readonly)
- 类型检查:CArray → CPtr 数组名退化、CPtr → CArray 形参调整
- 类型检查:派生类实例 → 基类指针 upcast(`CPtr[Expression]` → `CPtr[AST]`)
- 类型检查:`os._win32` 等子模块合并解析,多子模块访问不再误报未知属性
## 0.1.0
- Initial release of Vipeye (Viper language support for VS Code)
+36 -6
View File
@@ -1,13 +1,43 @@
# Vipeye
Vipeye is a VS Code extension that provides language server features for the Viper programming language.
Vipeye 是面向 Viper 语言的 VS Code 扩展,提供完整的语言服务支持。
## Features
Viper 是一门基于 CPython 语法的 AOT 静态类型语言(编译器为 TransPyC / TPV),与 Python 的语义完全不同。Vipeye 在 Pyright 基础上深度改造而来,现已全面切换为 Viper 语义。
- Syntax highlighting for Viper (.vp, .vpi) files
- Project file (.vpj) support
- Type checking and diagnostics
- Code completion and hover information
## 功能特性
- **语法高亮**:`.vp` / `.vpi` 文件的完整 Viper 语法高亮(关键字、类型、装饰器 `@`、f-string、常量、未定义符号等语义化区分)
- **语义高亮**:基于 LSP 3.16 SemanticTokens 协议,区分已定义/未定义符号、类/函数/变量/模块/参数/常量
- **类型检查与诊断**:遵循 Viper 语义的类型推断与静态检查
- 指针语义:`CPtr` / `CArray`(数组名退化、形参调整、派生类指针 upcast)
- 类型别名与 C 类型系统:`t.CInt` / `t.CTypedef` / `t.CDefine` / `t.CEnum` 等
- Viper 特有的 `T | t.CPtr` 指针折叠表示
- 全局变量访问规则(读/写/global 声明判定)
- **项目支持**:`.vpj` 项目配置文件、`viperconfig.json` 配置
- **代码补全与悬停信息**
- **导航**:定义跳转、查找引用、重命名
## 项目架构
- `vscode-vipeye`:LSP 客户端扩展(本包)
- `vipeye`:Viper 语言命令行类型检查器(pyright 命令行工具改造)
- `vipeye-internal`:核心分析引擎(类型推断、语义高亮、诊断)
- `vipeye-typeserver`:类型服务进程
- Viper 核心扩展 `vscode-viper-core`:管理 `.vpj` 项目配置与工具链
## 配置
在 `.vscode/settings.json` 或 `viperconfig.json` 中配置:
```json
{
"viper.analysis.typeCheckingMode": "standard",
"viper.analysis.extraPaths": ["d:/path/to/includes"],
"viper.analysis.diagnosticSeverityOverrides": {
"reportMissingImports": "warning"
}
}
```
## License
+7 -5
View File
@@ -2,7 +2,7 @@
"name": "vscode-vipeye",
"displayName": "Vipeye",
"description": "VS Code language support for Viper (LSP client)",
"version": "1.1.411",
"version": "1.1.412",
"private": true,
"license": "MIT",
"author": {
@@ -76,6 +76,10 @@
"variable": [
"variable"
],
"variable.readonly": [
"variable.other.constant",
"constant"
],
"function": [
"entity.name.function.viper",
"entity.name.function"
@@ -111,9 +115,7 @@
"variable.other.property",
"variable.other.member"
],
"undefined": [
"variable"
]
"undefined": []
}
}
],
@@ -1377,7 +1379,7 @@
"boolean"
],
"description": "Diagnostics for undefined variables.",
"default": "error",
"default": "warning",
"enum": [
"none",
"information",