feat(vipeye-internal): Viper len 优先调用 __len__,结构体缺 __len__ 报错

- len 对用户自定义结构体优先解析 __len__ 返回类型,无 __len__ 报错
- t.CType 原生类型(C 标量/指针/str/bytes)豁免,返回 t.SizeT
- 新增本地化消息 viperLenNoLenMethod
- viperGenericTypeVarCall 10/10 测试通过
This commit is contained in:
TermiNexus committed 2026-08-17 10:32:19 +08:00
1 parent bbd8b1022e
commit 41bc302916
5 files changed
+172

No files matched your search

@@ -9280,6 +9280,50 @@ export function createTypeEvaluator(
return false;
}
// Viper 扩展:判断调用节点是否为内置 len 调用。
// Viper 语义下 len 接受任意值(不限定 Sized 协议),返回 t.SizeT(size_t),
// 与 typeshed builtins.vpi 的 Python len 声明(obj: Sized, / -> int)语义不同。
// 当同一函数名存在多个"版本"定义时,以本特判(Viper 方言实现)优先。
function isViperLenCall(baseTypeResult: TypeResult): boolean {
return (
isFunction(baseTypeResult.type) &&
baseTypeResult.type.shared.name === 'len' &&
baseTypeResult.type.shared.moduleName === 'builtins'
);
}
// Viper 扩展:从 t 模块获取 SizeT(size_t)的实例类型。
// t.vpi 中类型名为 CSizeT;优先查找 'SizeT',失败时回退 'CSizeT'。
// 查找失败返回 undefined。
function getViperSizeTType(node: ParseNode): Type | undefined {
const tSymbolWithScope = lookUpSymbolRecursive(node, 't', /* honorCodeFlow */ true);
if (!tSymbolWithScope) {
return undefined;
}
const tEffectiveType = getEffectiveTypeOfSymbolForUsage(
tSymbolWithScope.symbol,
/* usageNode */ undefined,
/* useLastDecl */ true
).type;
if (!tEffectiveType || !isModule(tEffectiveType)) {
return undefined;
}
const sizeTSymbol =
ModuleType.getField(tEffectiveType, 'SizeT') ?? ModuleType.getField(tEffectiveType, 'CSizeT');
if (!sizeTSymbol) {
return undefined;
}
const sizeTType = getEffectiveTypeOfSymbolForUsage(
sizeTSymbol,
/* usageNode */ undefined,
/* useLastDecl */ true
).type;
if (sizeTType && isInstantiableClass(sizeTType)) {
return convertToInstance(sizeTType);
}
return undefined;
}
// Viper 扩展:判断调用节点是否为 stdarg.va_arg 调用。
// va_arg 通过 `from stdarg import va_arg` 引入,baseTypeResult 为 FunctionType,
// 其 shared.name 为 'va_arg'。TPV 将 va_arg 视为 builtin,第二参数是类型名。
@@ -9761,6 +9805,40 @@ export function createTypeEvaluator(
} else {
typeResult.type = UnknownType.create();
}
} else if (
// Viper 扩展:拦截内置 len 调用。
// Viper 语义下 len 优先调用 __len__ 魔法函数:
// - 用户自定义结构体类实例:必须有 __len__ 方法,否则报错;
// 返回 __len__ 的返回类型。
// - t.CType 原生类型(C 标量/指针/str/bytes 等,任意值以 t.CType 表示):
// 豁免检查,直接返回 t.SizeT(size_t)。
// typeshed builtins.vpi 的 Python len 声明 `len(obj: Sized, /) -> int`
// 与 Viper 语义不符,版本重复时以本特判(Viper 方言实现)优先。
IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect) &&
isViperLenCall(baseTypeResult)
) {
const argExpr = node.d.args.length >= 1 ? node.d.args[0].d.valueExpr : undefined;
const argType = argExpr ? getTypeOfExpression(argExpr).type : UnknownType.create();
if (isClassInstance(argType) && isViperUserStructValue(argType)) {
// 用户自定义结构体:要求实现 __len__。
const lenMethodResult = argExpr ? getTypeOfBoundMember(argExpr, argType, '__len__') : undefined;
if (lenMethodResult && isFunction(lenMethodResult.type)) {
const lenRetType = lenMethodResult.type.shared.declaredReturnType;
typeResult.type =
lenRetType && isInstantiableClass(lenRetType)
? convertToInstance(lenRetType)
: lenRetType ?? UnknownType.create();
} else {
addDiagnostic(
DiagnosticRule.reportGeneralTypeIssues,
LocMessage.viperLenNoLenMethod().format({ name: argType.shared.name }),
node
);
typeResult.type = UnknownType.create();
}
} else {
typeResult.type = getViperSizeTType(node) ?? getBuiltInObject(node, 'int');
}
} else {
const callResult = validateCallArgs(
node,
@@ -1182,6 +1182,8 @@ export namespace Localizer {
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperModifierRequiresDef'));
export const viperExternBodyInvalid = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperExternBodyInvalid'));
export const viperLenNoLenMethod = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperLenNoLenMethod'));
export const unhashableDictKey = () => getRawString('Diagnostic.unhashableDictKey');
export const unhashableSetEntry = () => getRawString('Diagnostic.unhashableSetEntry');
export const unionForwardReferenceNotAllowed = () => getRawString('Diagnostic.unionForwardReferenceNotAllowed');
@@ -1620,6 +1620,7 @@
"unexpectedUnindent": "Unindent not expected",
"viperModifierRequiresDef": "The modifier \"{name}\" must be used with 'def'",
"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",
"unhashableSetEntry": {
"message": "Set entry must be hashable",
@@ -618,6 +618,7 @@
"unexpectedUnindent": "不应取消缩进",
"viperModifierRequiresDef": "修饰关键字 \"{name}\" 必须与 def 搭配使用",
"viperExternBodyInvalid": "extern 函数 \"{name}\" 的函数体必须为 pass 或 None",
"viperLenNoLenMethod": "类型 \"{name}\" 没有 __len__ 方法,不能用于 len()",
"unhashableDictKey": "字典密钥必须可哈希",
"unhashableSetEntry": "Set 条目必须是可哈希的",
"uninitializedAbstractVariables": "抽象基类中定义的变量未在 final 类 \"{classType}\" 中初始化",
@@ -716,3 +716,93 @@ def test_builtins_asm_llvmir():
}
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);
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 code = `import t
class Vec3:
def __init__(self, x: t.CInt, y: t.CInt, z: t.CInt):
self.x = x
self.y = y
self.z = z
def __len__(self) -> t.CInt:
return 3
class NoLen:
a: t.CInt = 0
def test_len_any():
n: t.CInt = 5
f: t.CFloat = 3.5
p: t.CPtr = None
s: str = "hello"
v = Vec3(1, 2, 3)
nl = NoLen()
# len 对 t.CType 原生类型(int/float/指针/str):返回 t.SizeT,不报错
a: t.CSizeT = len(n)
b: t.CSizeT = len(f)
c: t.CSizeT = len(p)
d: t.CSizeT = len(s)
# len 优先调用 __len__:有 __len__ 的结构体返回其返回类型
e: t.CSizeT = len(v)
# 无 __len__ 的结构体:报错
bad = len(nl)
`;
const testDir = path.join(__dirname, '__viper_len_test__');
const fs = require('fs');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'len_any.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('=== len_any.vp diagnostics ===');
diagnostics.forEach((d) => console.log(` ${d}`));
// 期望恰好 1 个错误级诊断:无 __len__ 的结构体 NoLen 使用 len() 报错;
// t.CType 原生类型(int/float/指针/str)豁免;有 __len__ 的 Vec3 返回其返回类型。
const errors = diagnostics.filter((d) => d.startsWith('[category=0]'));
assert.strictEqual(
errors.length,
1,
`expected exactly 1 error (NoLen without __len__), got: ${JSON.stringify(diagnostics)}`
);
assert.ok(
errors[0].includes('__len__'),
`expected __len__ error message: ${JSON.stringify(errors)}`
);
program.dispose();
} finally {
fs.rmSync(testDir, { recursive: true, force: true });
}
serviceProvider.dispose();
});