fix(vipeye-internal): CArray 元素访问解析出元素类型(成员可高亮)

- kbd_buf: [KBD_BUF_SIZE]usb_kbd_event 的 kbd_buf[i].modifier 原先报
  「无法解析类型上的成员“modifier”访问」且该成员拿不到语义 token(纯白)
- 根因:t.vpi 的 CArray 是 Generic[T, N] + __getitem__ -> T,但 Viper 侧
  TypeVar(...) 不产出可替换类型变量(类型参数落到合成参数 __P0/__P1),
  返回类型永远具体化不了 → 下标恒为 Unknown
- 修法:getTypeOfIndexedObjectOrClass 新增 Viper 分支,按 C 语义直接取
  CArray 第一个类型实参(与 CPtr 分支读 priv.typeArgs[0] 同法),
  `[SIZE]T` 与显式 t.CArray[T, N] 两种写法都覆盖
- 回归用例 +1(元素类型 + modifier 语义 token),jest 808 -> 809
This commit is contained in:
TermiNexus committed 2026-09-27 18:02:53 +08:00
1 parent 5808a76b23
commit c8817cacb1
4 files changed
+120

No files matched your search

+1
View File
@@ -155,6 +155,7 @@ packages/vipeye-internal/src/tests/__viper_const_ptr_deref__/
packages/vipeye-internal/src/tests/__viper_const_ptr_index__/
packages/vipeye-internal/src/tests/__viper_const_ptr_iter__/
packages/vipeye-internal/src/tests/__viper_variadic_noimport__/
packages/vipeye-internal/src/tests/__viper_carray_element__/
# perfCompare.py cached commit bundles
/build/perfCompare/
@@ -8858,6 +8858,26 @@ export function createTypeEvaluator(
const magicMethodName = getIndexAccessMagicMethodName(usage);
// Viper 扩展:`t.CArray[T, N]` 的元素访问语义(C 的 `arr[i]`)。
// CArray 在 vpi 存根里声明为 `class CArray(CType, Generic[T, N])`,
// 但 Viper 侧 `TypeVar(...)` 不产出可替换的类型变量(其类型参数落到
// 合成参数 `__P0/__P1`,见 buildTypeParamsFromTypeArgs),因此
// `def __getitem__(self, index: int) -> T` 的返回类型无法具体化 →
// 下标恒为 Unknown,进而 `buf[i].field` 误报「无法解析类型上的成员访问」。
// 按 C 语义直接取第一个类型实参(元素类型),与 CPtr 分支读
// `baseType.priv.typeArgs[0]` 同法,不依赖 TypeVar 替换。
const viperArrayFileInfo = AnalyzerNodeInfo.getFileInfo(node);
if (
IsViperDialect(viperArrayFileInfo.executionEnvironment.dialect) &&
usage.method === 'get' &&
isClassInstance(baseType) &&
isCArrayType(baseType) &&
baseType.priv.typeArgs &&
baseType.priv.typeArgs.length > 0
) {
return { type: makeTopLevelTypeVarsConcrete(baseType.priv.typeArgs[0]) };
}
// Viper 扩展:CPtr[T] 指针下标解引用语义。
// 当对 CPtr[T] 实例执行下标访问 ptr[i] 时(模拟 C 语言的 ptr[i] = *(ptr + i)):
// 1. 如果 T 本身有 __getitem__/__setitem__/__delitem__,优先调用 T 的方法
@@ -21,6 +21,9 @@
* **只是修饰**,跳过即可:`path: const str` 可 `for ch in path:` 迭代,
* 不再误报「“CConst”不可迭代 / 未定义 __iter__」(判据须认 str / bytes,
* 不能只认 CPtr,见 viperTypeContainsPointer)。
* ④ `[SIZE]StructT` / `t.CArray[StructT, N]` 的元素访问能解析出元素类型:
* `buf[i].field` 不再报「无法解析类型上的成员访问」,成员名可高亮
* (Viper 侧按 C 语义直接取 CArray 的第一个类型实参,不依赖 TypeVar 替换)。
*
* 运行:npx jest src/tests/viperBracketArrayAndConstPtr.test.ts --runInBand
*/
@@ -92,6 +95,35 @@ function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any
}));
}
/** 语义 token:解码相对编码,返回 Map<line(0-based), {text, tokenType}[]>。 */
function getSemanticTokens(program: Program, uri: any): Map<number, { text: string; tokenType: number }[]> {
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 lines = program.getSourceFile(uri)!.getParseResults()!.text.split('\n');
const map = new Map<number, { text: string; tokenType: number }[]>();
let cursorLine = 0;
let cursorChar = 0;
for (let i = 0; i < result.data.length; i += 5) {
const deltaLine = result.data[i];
cursorLine += deltaLine;
cursorChar = deltaLine === 0 ? cursorChar + result.data[i + 1] : result.data[i + 1];
const length = result.data[i + 2];
const entry = {
text: (lines[cursorLine] || '').substring(cursorChar, cursorChar + length),
tokenType: result.data[i + 3],
};
if (!map.has(cursorLine)) {
map.set(cursorLine, []);
}
map.get(cursorLine)!.push(entry);
}
return map;
}
test('[SIZE]T 的 SIZE 支持整型常量运算(乘法/减法/移位/字面量)', () => {
const code = `import t
@@ -298,3 +330,69 @@ def walk_parts(parts: const t.CPtr) -> int:
require('fs').rmSync(testDir, { recursive: true, force: true });
}
});
/*
* ④ `[SIZE]StructT` 数组的元素访问(ViperOS VKernel/Kernel/drivers/input/keyboard/keyboard.vp):
* kbd_buf: [KBD_BUF_SIZE]usb_kbd_event
* kbd_buf[kbd_buf_head].modifier
* CArray 在 vpi 存根里是 `class CArray(CType, Generic[T, N])`,但 Viper 侧
* `TypeVar(...)` 不产出可替换的类型变量(类型参数落到合成参数 `__P0/__P1`),
* 故 `__getitem__ -> T` 无法具体化 → 下标恒为 Unknown → `.modifier` 报
* 「无法解析类型上的成员访问」且该成员名拿不到 property 语义 token(不高亮)。
*/
test('[SIZE]StructT 数组的元素访问能解析出元素类型(成员可高亮)', () => {
const code = `import t
KBD_BUF_SIZE: int = 16
class usb_kbd_event:
modifier: uint8
keycode: uint8
event_type: uint8
kbd_buf: [KBD_BUF_SIZE]usb_kbd_event
explicit: t.CArray[usb_kbd_event, KBD_BUF_SIZE]
kbd_buf_head: int = 0
def touch():
m: uint8 = kbd_buf[kbd_buf_head].modifier
n: uint8 = explicit[0].keycode
e: usb_kbd_event = kbd_buf[0]
`;
const { program, configOptions, uri, serviceProvider, testDir } = buildProgramForCode(
code,
'__viper_carray_element__'
);
try {
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== [SIZE]StructT 元素访问诊断 ===');
diagnostics.forEach((d) => console.log(` L${d.line}:${d.col} [category=${d.category}] ${d.message}`));
const real = diagnostics.filter((d) => d.category === 0 || d.category === 1);
assert.strictEqual(
real.length,
0,
`数组元素成员访问不应报错:${JSON.stringify(real)}`
);
// 成员名拿到语义 token(Viper 类字段解析到 VariableDeclaration → variable(3),
// 与普通结构体字段一致)—— 即「高亮」;修前是 undefined(11)(默认白色)。
const tokensByLine = getSemanticTokens(program, uri);
const memberLine = tokensByLine.get(15) || [];
console.log('=== `kbd_buf[kbd_buf_head].modifier` 所在行语义 token ===');
memberLine.forEach((t) => console.log(` [${t.tokenType}] ${t.text}`));
const modifierToken = memberLine.find((t) => t.text === 'modifier');
assert(modifierToken, `该行应有 modifier 的语义 token:${JSON.stringify(memberLine)}`);
assert.strictEqual(
modifierToken!.tokenType,
3,
`modifier 应为 variable(3) 语义 token(未解析时是 undefined(11)),实际 ${modifierToken!.tokenType}`
);
} finally {
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(testDir, { recursive: true, force: true });
}
});
+1
View File
@@ -36,6 +36,7 @@
- C type descriptors are **skipped** when checking iterability, so a `const` pointer can be iterated: `for ch in path:` with `path: const str` (and `const t.CPtr`) no longer reports "“CConst”不可迭代 / 未定义“__iter__”方法". The predicate now recognizes every Viper pointer (CPtr, `str`, `bytes`) rather than only `CPtr`, which is why `const str` had been missed — `CConst` fell through to the "not iterable" path
- The `static` modifier is also accepted **inside a type annotation**: `buf_head: static int = 0` — the spelling actually used throughout ViperOS (`VKernel/Kernel/drivers/**`). It desugars to the very same `t.CStatic | T` and marks the declaration as a static symbol, so it renders as `static int` and the cross-module rule applies identically; previously the `static` inside the annotation was read as an ordinary name and reported `"static" is not defined`. `static` stays a **soft** keyword in this position too — it is consumed only when a type atom follows, so `static` as a variable name (`static: int = 0`, `static = 1`) is untouched. It is now highlighted as a modifier inside annotations as well
- `*args` is bound to the `va_list` **pointer** even when the file doesn't `import t`: the Viper var-arg rule used to look up `t.CPtr` through the source-visible `t` module only, so in a file like ViperOS `drivers/serial/uart/serial.vp` (no `import t`) it fell back to Python's `tuple` semantics and `viperlib.vsnprintf(&buf, 1024, fmt, args)` was reported as "无法将"tuple"类型的参数分配给函数"vsnprintf"中类型为"str"的参数"ap"". The lookup now falls back to the compiler built-in `rawptr` from `builtins.vpi` (= `t.CPtr`), the same mechanism `T*` annotations and `[SIZE]T` already use
- Indexing a C array now yields the **element type**, so member access on an element resolves: with `kbd_buf: [KBD_BUF_SIZE]usb_kbd_event`, `kbd_buf[i].modifier` used to report "无法解析类型上的成员"modifier"访问,对象可能未定义" and the member name got no semantic token at all (plain white). The CArray stub declares `class CArray(CType, Generic[T, N])` with `def __getitem__(self, index: int) -> T`, but Viper-side `TypeVar(...)` does not produce substitutable type variables (the class's type parameters end up as synthesized `__P0` / `__P1`), so the return type never became concrete. The Viper index path now reads the **first type argument** directly (C semantics: `arr[i]` has the element type), the same way the `CPtr[T]` branch reads `priv.typeArgs[0]`; this covers both `[SIZE]T` and the explicit `t.CArray[T, N]` spelling
## 1.1.425