diff --git a/.gitignore b/.gitignore index 6a176212..de5408f0 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts index c2b61dc6..605292a6 100644 --- a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts +++ b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts @@ -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 的方法 diff --git a/packages/vipeye-internal/src/tests/viperBracketArrayAndConstPtr.test.ts b/packages/vipeye-internal/src/tests/viperBracketArrayAndConstPtr.test.ts index f30e2250..225bc389 100644 --- a/packages/vipeye-internal/src/tests/viperBracketArrayAndConstPtr.test.ts +++ b/packages/vipeye-internal/src/tests/viperBracketArrayAndConstPtr.test.ts @@ -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。 */ +function getSemanticTokens(program: Program, uri: any): Map { + 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(); + 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 }); + } +}); diff --git a/packages/vscode-vipeye/CHANGELOG.md b/packages/vscode-vipeye/CHANGELOG.md index a218be55..12c0a718 100644 --- a/packages/vscode-vipeye/CHANGELOG.md +++ b/packages/vscode-vipeye/CHANGELOG.md @@ -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