diff --git a/.gitignore b/.gitignore index d1f9ff1d..fd7da7c9 100644 --- a/.gitignore +++ b/.gitignore @@ -156,6 +156,7 @@ 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__/ +packages/vipeye-internal/src/tests/__viper_bracket_array_no_import_t__/ packages/vipeye-internal/src/tests/__viper_export_extern__/ # perfCompare.py cached commit bundles diff --git a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts index c0850e4f..bd6ff029 100644 --- a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts +++ b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts @@ -5396,9 +5396,13 @@ export function createTypeEvaluator( } else if (node.d.isViperBracketArraySynthetic) { // Viper 扩展:`[SIZE]T` 数组语法脱糖出的合成 `t` 名 // (见 parser._parseViperBracketArrayType)。源文件未 import t 时 - // 该名无法解析,静默退化为 Unknown(不报"未定义 t"), - // 数组类型语义随之退化;正常 Viper 源码均带 import t。 - type = UnknownType.create(); + // 该名在词法作用域里解析不到,此时**回退到 builtins 作用域的同名模块 + // 绑定**(与 `T*` 的 rawptr 回退、`__attribute__` 的隐式模块根同口径, + // 见 getViperImplicitModuleType):否则 `coroutines: [8]Coroutine` + // 这类数组字段注解会整体退化成 Unknown,其元素成员访问 + // (`m.coroutines[i].rsp`)随之解析不出来。 + // 仍取不到才静默退化为 Unknown(不报"未定义 t")。 + type = getViperImplicitModuleType(node, name) ?? UnknownType.create(); } else if (node.d.isViperTypedef) { // Viper 扩展:`typedef T1 = T2` 脱糖出的合成 `t` 名 // (见 parser._parseViperTypedefStatement)。该写法不要求源码 diff --git a/packages/vipeye-internal/src/tests/viperBracketArrayAndConstPtr.test.ts b/packages/vipeye-internal/src/tests/viperBracketArrayAndConstPtr.test.ts index 225bc389..392d0671 100644 --- a/packages/vipeye-internal/src/tests/viperBracketArrayAndConstPtr.test.ts +++ b/packages/vipeye-internal/src/tests/viperBracketArrayAndConstPtr.test.ts @@ -24,6 +24,10 @@ * ④ `[SIZE]StructT` / `t.CArray[StructT, N]` 的元素访问能解析出元素类型: * `buf[i].field` 不再报「无法解析类型上的成员访问」,成员名可高亮 * (Viper 侧按 C 语义直接取 CArray 的第一个类型实参,不依赖 TypeVar 替换)。 + * ⑤ `[SIZE]T` 脱糖出的合成 `t` 名**不要求源码 `import t`**:文件没 `import t` 时 + * 回退到 builtins 作用域的 `t` 模块(与 `T*` 的 rawptr 回退同口径),否则 + * `coroutines: [8]Coroutine` 会整条塌成 Unknown,`m.coroutines[i].rsp` 报 + * 「无法解析类型上的成员“rsp”访问」且 `.rsp` 拿不到语义 token(黄标)。 * * 运行:npx jest src/tests/viperBracketArrayAndConstPtr.test.ts --runInBand */ @@ -396,3 +400,66 @@ def touch(): require('fs').rmSync(testDir, { recursive: true, force: true }); } }); + +/* + * ⑤ `[SIZE]T` 数组不要求源码 `import t`(ViperOS 协程调度器场景): + * class CoroutineManager: + * coroutines: [8]Coroutine + * m: CoroutineManager* = _coro_mgr_ptr + * m.coroutines[prev_cid].rsp + * 合成 `t` 名(见 parser._parseViperBracketArrayType)解析不到时原先直接降级为 + * Unknown,导致 `coroutines` 字段类型整条塌成 Unknown → `m.coroutines[i].rsp` + * 报「无法解析类型上的成员“rsp”访问」,`.rsp` 也随之不高亮。修复后回退到 + * builtins 作用域的 `t` 模块(与 `T*` 的 rawptr 回退同口径),无 import 也成立。 + */ +test('无 import t 时 [SIZE]StructT 数组仍解析元素类型(.rsp 可高亮)', () => { + const code = `class Coroutine: + rsp: uint64 + cid: int + +class CoroutineManager: + current_cid: int + coroutines: [8]Coroutine + +_coro_mgr_ptr: CoroutineManager* = None + +def f() -> uint64: + m: CoroutineManager* = _coro_mgr_ptr + prev_cid: int = m.current_cid + return m.coroutines[prev_cid].rsp +`; + const { program, configOptions, uri, serviceProvider, testDir } = buildProgramForCode( + code, + '__viper_bracket_array_no_import_t__' + ); + + try { + const diagnostics = getDiagnostics(program, configOptions, uri); + console.log('=== 无 import t 的 [SIZE]T 元素访问诊断 ==='); + 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, + `无 import t 时数组元素成员访问不应报错:${JSON.stringify(real)}` + ); + + // `.rsp`(第 14 行,0-based 13)应拿到 variable(3) 语义 token —— 即「高亮」。 + const tokensByLine = getSemanticTokens(program, uri); + const rspLine = tokensByLine.get(13) || []; + console.log('=== `m.coroutines[prev_cid].rsp` 所在行语义 token ==='); + rspLine.forEach((t) => console.log(` [${t.tokenType}] ${t.text}`)); + const rspToken = rspLine.find((t) => t.text === 'rsp'); + assert(rspToken, `该行应有 rsp 的语义 token:${JSON.stringify(rspLine)}`); + assert.strictEqual( + rspToken!.tokenType, + 3, + `rsp 应为 variable(3) 语义 token(未解析时是 undefined(11)),实际 ${rspToken!.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 26371b6a..ec948c72 100644 --- a/packages/vscode-vipeye/CHANGELOG.md +++ b/packages/vscode-vipeye/CHANGELOG.md @@ -38,6 +38,7 @@ - `*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, and works for a union base too (`CArray[T, N] | t.CExport`) - **`export` / `extern` on variable declarations** are now supported: `export gdt: [7]gdt_entry = []`, `export tss_buf: [104]uint8` and `extern ext_flag: int`. The variable form is told apart from the function form (`export def f()` / `export f(...)`) by the third token — `:` is a variable (type annotation), `(` / `[` is a function, the same criterion as TransPyC's `_is_export_extern_var`). They desugar exactly like TransPyC's `_parse_export_extern_var`: `export NAME: T [= v]` becomes `T | t.CExport` (bare-named definition, **no module SHA1 header**), `extern NAME: T` becomes `T | t.State` (= `CExtern | CExport`, declaration only). An **initializer is allowed for `export` but rejected for `extern`** — `extern a: int = 1` reports "extern 变量声明不能带初始值(符号定义在外部)", matching the compiler's fail-fast. Storage-class markers render as a prefix qualifier (`export CArray[gdt_entry, 7]`, `extern int`), and the marker is treated as a modifier everywhere else: in the `Struct | CPtr` fold (so `extern p: t.CPtr` stays a pointer instead of degrading to `CPtr[State]`), in the union index access, and as a CArray element access (so `gdt[i].access` still resolves to `uint8` and the zero-initializer `= []` is accepted) +- `[SIZE]T` array annotations no longer require the file to `import t`. The parser desugars `coroutines: [8]Coroutine` into a synthetic `t.CArray[Coroutine, 8]` member access; when that synthetic `t` could not be resolved through the lexical scope it silently degraded to `Unknown`, so the whole array annotation collapsed and `m.coroutines[i].rsp` reported "无法解析类型上的成员“rsp”访问" with `rsp` getting no semantic token at all (rendered unhighlighted, no jump-to-definition). The synthetic `t` now falls back to the `t` module bound in the built-in `builtins.vpi` scope — the same mechanism `T*` uses for `rawptr` and `__attribute__(...)` uses for its implicit module roots — so a file without `import t` still gets the real `CArray[Coroutine, Literal[8]]` and the element's member resolves ## 1.1.425