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:
1 parent
5808a76b23
commit
c8817cacb1
4 files changed
+120
No files matched your search
@@ -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 });
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user