refactor(vipeye-internal): str/bytes 迭代元素类型统一为 char
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

接上次下标统一:`for ch in s` 的迭代元素,以及 str/bytes 作为 Iterable/Iterator 实参
时的泛型推断(enumerate/zip/map/filter/reversed/sorted/list() 等)也由 t.CChar 别名
int 改为内置 char。至此 `s[i]`、`*s`、迭代三种取字符方式元素类型一致。
删除已无引用的 getViperCCharType 辅助函数。

回归用例:viperEnumerate.test.ts 断言改为 enumerate[char] / ch 为 char。
版本号提升至 1.1.422 并更新 CHANGELOG。
This commit is contained in:
TermiNexus committed 2026-09-22 21:28:50 +08:00
1 parent d2f66811fb
commit 988ba922f4
9 files changed
+30 -81

No files matched your search

+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "vipeye-internal",
"version": "1.1.421",
"version": "1.1.422",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vipeye-internal",
"version": "1.1.421",
"version": "1.1.422",
"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.421",
"version": "1.1.422",
"license": "MIT",
"private": true,
"files": [
@@ -3291,15 +3291,11 @@ export function createTypeEvaluator(
}
return UnknownType.create(isIncomplete);
}
// Viper 扩展:str/bytes 类型迭代返回 t.CChar(字符)。
// Viper 语义:str/bytes = i8* 指针(const char*),迭代返回单个字符 CChar。
// 使用 getViperCCharType 而非裸 int,保持与 s[i] 下标访问一致(t.CChar 显示)。
// Viper 扩展:str/bytes 类型迭代返回 char(i8 字符)。
// Viper 语义:str/bytes = i8* 指针(const char*),迭代返回单个字符。
// 与下标访问 s[i]、解引用 *s 保持同一元素类型 char。
if (ClassType.isBuiltIn(subtype, ['str', 'bytes'])) {
const ccharType = getViperCCharType(errorNode);
if (ccharType) {
return ccharType;
}
return getBuiltInObject(errorNode, 'int');
return getBuiltInObject(errorNode, 'char');
}
// 检查联合类型中是否包含 CPtr
const typeContainsCPtr = findSubtype(type, (s) =>
@@ -8728,57 +8724,6 @@ export function createTypeEvaluator(
return getViperREnumVariantFieldInfo(renumType, memberName)?.type;
}
// Viper 扩展:构造带 t.CChar 类型别名信息的 int 类型(表示 C 的 char 类型)。
// Viper 中 str/bytes 是 i8* 指针,下标访问/迭代/字符运算返回 t.CChar(int 别名)。
// 优先从 t 模块查找 CChar 类型别名以保留显示信息;找不到时回退为
// 构造带 typeAliasInfo 的 int 类型。查找失败返回 undefined。
function getViperCCharType(node: ExpressionNode): Type | undefined {
const tSymbolWithScope = lookUpSymbolRecursive(node, 't', /* honorCodeFlow */ true);
if (tSymbolWithScope) {
const tEffectiveType = getEffectiveTypeOfSymbolForUsage(
tSymbolWithScope.symbol,
/* usageNode */ undefined,
/* useLastDecl */ true
).type;
if (tEffectiveType && isModule(tEffectiveType)) {
const ccharSymbol = ModuleType.getField(tEffectiveType, 'CChar');
if (ccharSymbol) {
// 优先使用声明类型(保留 typeAliasInfo)
const ccharDeclType = getDeclaredTypeOfSymbol(ccharSymbol)?.type;
if (ccharDeclType) {
return convertToInstance(ccharDeclType);
}
// 回退到有效类型
const ccharType = getEffectiveTypeOfSymbolForUsage(
ccharSymbol,
/* usageNode */ undefined,
/* useLastDecl */ true
).type;
if (ccharType) {
return convertToInstance(ccharType);
}
}
}
}
// 回退:构造带 CChar 别名信息的 int 类型
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
const intType = getBuiltInObject(node, 'int');
const ccharAliasInfo: TypeAliasInfo = {
shared: {
name: 'CChar',
fullName: 't.CChar',
moduleName: 't',
fileUri: fileInfo.fileUri,
typeVarScopeId: '',
isTypeAliasType: false,
typeParams: undefined,
computedVariance: undefined,
},
typeArgs: undefined,
};
return TypeBase.cloneForTypeAlias(intType, ccharAliasInfo);
}
function getTypeOfIndexedObjectOrClass(
node: IndexNode,
baseType: ClassType,
@@ -14195,8 +14140,8 @@ export function createTypeEvaluator(
if (expectedType && requiresSpecialization(expectedType)) {
// Viper 扩展:str/bytes 实参传给可迭代协议参数(Iterable[_T]/Iterator[_T] 等)
// 时,泛型参数 _T 应从 str.__iter__ 的声明返回类型(Iterator[str])推断。
// 但 Viper 中 str 是 i8* 指针,迭代元素应为 t.CChar 而非 str。
// 因此将 argType 替换为 Iterator[CChar] 实例,使协议匹配把 _T 绑定为 CChar,
// 但 Viper 中 str 是 i8* 指针,迭代元素应为 char 而非 str。
// 因此将 argType 替换为 Iterator[char] 实例,使协议匹配把 _T 绑定为 char,
// 修复 `for i, ch in enumerate(src)` 中 ch 被推断为 str 的问题
// (同样覆盖 zip/map/filter/reversed/sorted/list() 等 Iterable 参数场景)。
let argTypeForConstraint = argType;
@@ -14205,11 +14150,11 @@ export function createTypeEvaluator(
isClassInstance(argType) &&
ClassType.isBuiltIn(argType, ['str', 'bytes'])
) {
const ccharType = getViperCCharType(argParam.errorNode);
const charType = getBuiltInObject(argParam.errorNode, 'char');
const iteratorClass = getTypingType(argParam.errorNode, 'Iterator');
if (ccharType && iteratorClass && isInstantiableClass(iteratorClass)) {
if (iteratorClass && isInstantiableClass(iteratorClass)) {
argTypeForConstraint = ClassType.cloneAsInstance(
ClassType.specialize(iteratorClass, [ccharType])
ClassType.specialize(iteratorClass, [charType])
);
}
}
@@ -2,8 +2,8 @@
* viperEnumerate.test.ts
*
* 验证 Viper 方言下内置 enumerate 的语义:
* - enumerate(str) 调用类型为 enumerate[t.CChar](_T 按 for 原迭代逻辑绑定元素类型)
* - `for i, ch in enumerate(str)` 中 i 为 t.CSizeT(size_t 索引)、ch 为迭代元素 t.CChar
* - enumerate(str) 调用类型为 enumerate[char](_T 按 for 原迭代逻辑绑定元素类型)
* - `for i, ch in enumerate(str)` 中 i 为 t.CSizeT(size_t 索引)、ch 为迭代元素 char
* - 与 Python 声明(Iterable[_T] 协议、tuple[int, _T])的差异由 Viper 特判覆盖
*
* 运行:npx jest src/tests/viperEnumerate.test.ts --runInBand
@@ -104,7 +104,7 @@ function collectForTargets(
return { iterableTypes, targets };
}
test('for i, ch in enumerate(kw_arg):i 为 t.CSizeT、ch 为 t.CChar', () => {
test('for i, ch in enumerate(kw_arg):i 为 t.CSizeT、ch 为 char', () => {
const source = `import t
def f(kw_arg: str) -> int:
@@ -116,16 +116,16 @@ def f(kw_arg: str) -> int:
const { iterableTypes, targets } = collectForTargets(program, parseResults!);
// 1. enumerate 调用类型:元素类型按 for 原迭代逻辑(str → t.CChar)
assert(iterableTypes.some((t) => t === 'enumerate[CChar]'), `实际: ${JSON.stringify(iterableTypes)}`);
// 1. enumerate 调用类型:元素类型按 for 原迭代逻辑(str → char)
assert(iterableTypes.some((t) => t === 'enumerate[char]'), `实际: ${JSON.stringify(iterableTypes)}`);
// 2. 索引 i 为 t.CSizeT(size_t)
const iTypes = targets.get('i');
assert(iTypes && iTypes.some((t) => t === 'CSizeT'), `i 应为 CSizeT,实际: ${JSON.stringify(iTypes)}`);
// 3. 元素 ch 为 t.CChar
// 3. 元素 ch 为 char(str/bytes 迭代元素,与 s[i] / *s 统一)
const chTypes = targets.get('ch');
assert(chTypes && chTypes.some((t) => t === 'CChar'), `ch 应为 CChar,实际: ${JSON.stringify(chTypes)}`);
assert(chTypes && chTypes.some((t) => t === 'char'), `ch 应为 char,实际: ${JSON.stringify(chTypes)}`);
program.dispose();
serviceProvider.dispose();
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "vipeye",
"version": "1.1.421",
"version": "1.1.422",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vipeye",
"version": "1.1.421",
"version": "1.1.422",
"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.421",
"version": "1.1.422",
"license": "MIT",
"author": {
"name": "TermiNexus"
+5 -1
View File
@@ -1,8 +1,12 @@
# Change Log
## 1.1.422
- `str` / `bytes` iteration now yields `char` as well: `for ch in s` and `Iterable` / `Iterator` argument inference (`enumerate`, `zip`, `map`, `filter`, `reversed`, `sorted`, `list()` …) bind the element type to `char`, matching subscript `s[i]` and dereference `*s`. The old `t.CChar`-aliased `int` construction is removed
## 1.1.421
- `s[i]` on `str` / `bytes` now yields `char` (`i8`) instead of `t.CChar`, matching the dereference result of `*s` (the two remain implicitly convertible). Covers both the plain subscript path and the type-set subscript path (e.g. `CConst | str | CPtr` style annotations); iteration (`for ch in s`) and `Iterable` argument inference still produce `t.CChar`
- `s[i]` on `str` / `bytes` now yields `char` (`i8`) instead of `t.CChar`, matching the dereference result of `*s` (the two remain implicitly convertible). Covers both the plain subscript path and the type-set subscript path (e.g. `CConst | str | CPtr` style annotations). Iteration and `Iterable` inference were unified in 1.1.422
## 1.1.420
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "vscode-vipeye",
"version": "1.1.421",
"version": "1.1.422",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vscode-vipeye",
"version": "1.1.421",
"version": "1.1.422",
"license": "MIT",
"dependencies": {
"mermaid": "^11.12.0",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "vipeye",
"displayName": "Vipeye",
"description": "VS Code language support for Viper (LSP client)",
"version": "1.1.421",
"version": "1.1.422",
"private": true,
"license": "MIT",
"author": {