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:
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();
|
||||
});
|
||||
Reference in new issue
Block a user