From 71cb3335495a3f71a4d8d5532bf71387dfcc13a2 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 28 Sep 2026 14:59:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(vipeye-internal):=20=5F=5Fattribute=5F=5F?= =?UTF-8?q?=20=E5=B1=9E=E6=80=A7=E5=90=8D=E7=BB=93=E6=9E=84=E5=8C=96?= =?UTF-8?q?=E9=AB=98=E4=BA=AE=EF=BC=88=E6=9C=AA=E5=A3=B0=E6=98=8E=E7=9A=84?= =?UTF-8?q?=E4=B9=9F=E4=B8=8D=E6=8A=A5=E9=94=99=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `@__attribute__(section(".text.startup"), aligned(16), stackrealign)` 里 `stackrealign` 原先不高亮(白色)且误报「无法访问类"type[attr]"的属性」。 根因:属性名脱糖成 `t.attr.`,高亮完全依赖能否解析到 `class attr` 的声明。而 `stackrealign` 在真实 t.py 与 LSP 存根 t.vpi 的 attr 类里都没有 (两边一致、无缺失),只在 TransPyV 的 codegen 映射(FuncGenBase.vp:181) 里支持,ViperOS main.vp:347 在用。 改法(结构化,不依赖存根声明): - parseNodes:NameNode.d 新增 isViperAttrMarkerName - parser._makeViperAttrMarker:给合成的属性名成员名打该标记 - semanticTokenProvider._classifyNameNode:该标记直接按 function 着色 (与 section/aligned/naked 等已声明属性同款) - typeEvaluator:新增 isViperAttrMarkerMember,与 isViperBracketArraySyntheticMember 一起跳过 memberAccess 诊断 (编译器对未知属性是静默丢弃,不是报错,故这是误报) - viperAttribute.test.ts:增至 9 用例(新增未声明属性名 stackrealign 的 高亮 + 不报错断言) - .gitignore:新增 __viper_attribute_unknown__/ - CHANGELOG:1.1.426 追加 1 条 --- .gitignore | 1 + .../src/analyzer/typeEvaluator.ts | 14 ++- .../languageService/semanticTokenProvider.ts | 9 +- .../vipeye-internal/src/parser/parseNodes.ts | 7 ++ packages/vipeye-internal/src/parser/parser.ts | 12 ++- .../src/tests/viperAttribute.test.ts | 89 +++++++++++++++++++ packages/vscode-vipeye/CHANGELOG.md | 1 + 7 files changed, 129 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index fd7da7c9..d4be4842 100644 --- a/.gitignore +++ b/.gitignore @@ -145,6 +145,7 @@ packages/vipeye-internal/src/tests/__viper_attribute_hover__/ packages/vipeye-internal/src/tests/__viper_attribute_prefix__/ packages/vipeye-internal/src/tests/__viper_attribute_noimport__/ packages/vipeye-internal/src/tests/__viper_attribute_implicit_scope__/ +packages/vipeye-internal/src/tests/__viper_attribute_unknown__/ packages/vipeye-internal/src/tests/__viper_volatile__/ packages/vipeye-internal/src/tests/__viper_volatile_bad__/ packages/vipeye-internal/src/tests/__viper_callable__/ diff --git a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts index bd6ff029..68c22f02 100644 --- a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts +++ b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts @@ -6857,7 +6857,10 @@ export function createTypeEvaluator( // Viper 扩展:`[SIZE]T` 数组语法脱糖出的合成 `t.CArray` 成员访问, // 当合成 `t` 未解析(源文件未 import t)时静默退化为 Unknown, // 不再叠加"无法解析成员"诊断(见 parser._parseViperBracketArrayType)。 - if (!isViperBracketArraySyntheticMember(node)) { + // 同理,`__attribute__(name)` 脱糖出的属性名(t.attr.)是编译器级 + // 开放集合,未声明在 t.attr 的名字(如 stackrealign)也不报(见 + // isViperAttrMarkerMember)。 + if (!isViperBracketArraySyntheticMember(node) && !isViperAttrMarkerMember(node)) { addDiagnostic( rule, diagMessage.format({ name: memberName, type: printType(baseType) }) + diag.getString(), @@ -8833,6 +8836,15 @@ export function createTypeEvaluator( ); } + // Viper 扩展:判断成员访问是否是 `__attribute__(name)` 脱糖出的属性名成员访问 + // (`t.attr.`,见 parser._makeViperAttrMarker)。属性名是编译器级的**开放 + // 集合**:TransPyV 的 codegen 支持的名字(如 stackrealign)未必都声明在 t.attr + // 里,而编译器对不认识的属性是静默丢弃、不是报错,故这里不报「无法访问属性」 + // (否则 `@__attribute__(stackrealign)` 会误报一条警告)。 + function isViperAttrMarkerMember(node: MemberAccessNode): boolean { + return node.d.member.d.isViperAttrMarkerName === true; + } + // Viper 扩展:REnum 变体字段类型(仅取类型,见 getViperREnumVariantFieldInfo)。 function getViperREnumVariantFieldType(renumType: ClassType, memberName: string): Type | undefined { return getViperREnumVariantFieldInfo(renumType, memberName)?.type; diff --git a/packages/vipeye-internal/src/languageService/semanticTokenProvider.ts b/packages/vipeye-internal/src/languageService/semanticTokenProvider.ts index 14f25b00..b2b6f0b0 100644 --- a/packages/vipeye-internal/src/languageService/semanticTokenProvider.ts +++ b/packages/vipeye-internal/src/languageService/semanticTokenProvider.ts @@ -605,7 +605,14 @@ class SemanticTokenWalker extends ParseTreeWalker { // 注意:必须在 declInfo 查询之前优先处理,因为 CPtr 解引用转发 // 可能让 getDeclInfoForNameNode 返回 CPtr.__getattr__ 的 variable // 声明(VariableDeclaration),导致误显示为变量颜色。 - if (nameText === '__before_init__' && this._isViperMemberAccess(node)) { + // Viper 扩展:`__attribute__(name)` 脱糖出的属性名(`t.attr.`, + // 见 parser._makeViperAttrMarker)。属性名是编译器级的**开放集合**,未必声明在 + // t.attr 里(如 stackrealign),故不能依赖符号解析(解析不到会落到 undefined + // 白色),直接按已声明属性同款(function)着色。 + if (node.d.isViperAttrMarkerName === true) { + tokenType = SemanticTokenTypes.function; + tokenModifiers = 0; + } else if (nameText === '__before_init__' && this._isViperMemberAccess(node)) { tokenType = SemanticTokenTypes.function; tokenModifiers = 0; } else if (!declInfo || !declInfo.decls || declInfo.decls.length === 0) { diff --git a/packages/vipeye-internal/src/parser/parseNodes.ts b/packages/vipeye-internal/src/parser/parseNodes.ts index 2752f519..0b1fd1ca 100644 --- a/packages/vipeye-internal/src/parser/parseNodes.ts +++ b/packages/vipeye-internal/src/parser/parseNodes.ts @@ -1723,6 +1723,13 @@ export interface NameNode extends ParseNodeBase { // parser._parseViperStaticAnnotatedStatement)。变量的 Declaration.node // 就是这个 NameNode,跨模块取参检查依此判定「声明带 static」。 isViperStatic?: boolean; + + // Viper 扩展:`__attribute__(name)` 脱糖(见 parser._makeViperAttrMarker) + // 时由**裸属性名实参**合成的成员名 `t.attr.`:位置取自实参自身的 + // token(真实位置,保留跳转/悬停)。属性名是编译器级的开放集合 + // (TransPyV 支持的名字未必都声明在 t.attr 里),故该名解析不到时 + // 不报「无法访问属性」,语义高亮直接按已声明属性同款(function)着色。 + isViperAttrMarkerName?: boolean; }; } diff --git a/packages/vipeye-internal/src/parser/parser.ts b/packages/vipeye-internal/src/parser/parser.ts index 4cb5b2e4..f3a4e3b5 100644 --- a/packages/vipeye-internal/src/parser/parser.ts +++ b/packages/vipeye-internal/src/parser/parser.ts @@ -5524,6 +5524,10 @@ export class Parser { // - 已是 `t.attr.X` / `c.Attribute(...)` 形态 → 原样保留(允许混写) // 与 TPC/TPV 的 includes/ast/parser.vp `_MakeTAttrMarker` 逐条对应; // 成员名复用实参自身的 token(真实位置),故属性名上的跳转/悬停仍指向 t.vpi。 + // 合成的成员名打上 isViperAttrMarkerName 标记:属性名是编译器级的开放集合 + // (TransPyV 支持的名字未必都声明在 t.attr 里,如 stackrealign),故该名解析 + // 不到时不报「无法访问属性」,语义高亮按已声明属性同款着色(见 + // typeEvaluator.getTypeOfMemberAccess / semanticTokenProvider._classifyNameNode)。 // 根 `t` 是合成的「隐式模块根」(见 _createViperImplicitModuleName), // 不要求源码 `import t`。 private _makeViperAttrMarker(argExpr: ExpressionNode): ExpressionNode { @@ -5533,11 +5537,15 @@ export class Parser { ); if (argExpr.nodeType === ParseNodeType.Name) { - return MemberAccessNode.create(attrRoot, NameNode.create(argExpr.d.token)); + const memberNameNode = NameNode.create(argExpr.d.token); + memberNameNode.d.isViperAttrMarkerName = true; + return MemberAccessNode.create(attrRoot, memberNameNode); } if (argExpr.nodeType === ParseNodeType.Call && argExpr.d.leftExpr.nodeType === ParseNodeType.Name) { - const inner = MemberAccessNode.create(attrRoot, NameNode.create(argExpr.d.leftExpr.d.token)); + const memberNameNode = NameNode.create(argExpr.d.leftExpr.d.token); + memberNameNode.d.isViperAttrMarkerName = true; + const inner = MemberAccessNode.create(attrRoot, memberNameNode); return CallNode.create(inner, argExpr.d.args, argExpr.d.trailingComma); } diff --git a/packages/vipeye-internal/src/tests/viperAttribute.test.ts b/packages/vipeye-internal/src/tests/viperAttribute.test.ts index 2f1dd2f5..f0c4e0dc 100644 --- a/packages/vipeye-internal/src/tests/viperAttribute.test.ts +++ b/packages/vipeye-internal/src/tests/viperAttribute.test.ts @@ -11,6 +11,8 @@ * 求值为 t.CAttr(修饰符),不参与类型检查、展示不泄漏 * ④ 值位置 `volatile f()` / `volatile 结构体()`(标记调用为 volatile) * ⑤ `__attribute__(` 括号内的属性名补全 + * ⑥ 属性名是编译器级开放集合:未声明在 `t.attr` 的名字(如 `stackrealign`) + * 也按 function 高亮,且不报「无法访问属性」 * 负例:`volatile = 2` / `const = 1` / `volatile 1`(非调用)仍须报错。 * * 运行:npx jest src/tests/viperAttribute.test.ts --runInBand @@ -32,6 +34,7 @@ import { UriEx } from '../common/uri/uriUtils'; import { FullAccessHost } from '../common/fullAccessHost'; import { CompletionProvider } from '../languageService/completionProvider'; import { HoverProvider } from '../languageService/hoverProvider'; +import { SemanticTokenTypes } from '../languageService/semanticTokenTypes'; import { ParseNodeType, TypeAnnotationNode } from '../parser/parseNodes'; (global as any).__rootDirectory = path.resolve(); @@ -89,6 +92,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; +} + /** 收集注解变量的展示类型(按变量名索引)。 */ function collectAnnotatedVarTypes(program: Program, parseResults: any): Map { const evaluator = program.evaluator; @@ -408,3 +440,60 @@ Attr: __attribute__(section(".x")) const int = 1 require('fs').rmSync(testDir, { recursive: true, force: true }); } }); + +/* + * ⑥ 属性名是编译器级的**开放集合**,不一定声明在 `t.attr` 里: + * `stackrealign` 只在 TransPyV 的 codegen 映射里有(FuncGenBase.vp:181), + * 真实 t.py 与 LSP 存根 t.vpi 的 `class attr` 都没有它,ViperOS + * `VKernel/Kernel/main.vp:347` 在用。修前 `t.attr.stackrealign` 解析不到 → + * 语义 token undefined(白色、不高亮)+ 误报「无法访问类"type[attr]"的属性」。 + * 修后:解析不到也不报错,语义高亮直接按已声明属性同款(function)着色。 + */ +test('未在 t.attr 声明的属性名(stackrealign)也高亮且不报「无法访问属性」', () => { + const code = `import t + +@__attribute__(section(".text.startup"), aligned(16), stackrealign) +def _start() -> None: + pass + +@__attribute__(naked) +def _naked() -> None: + pass +`; + const { program, configOptions, uri, serviceProvider, testDir } = buildProgramForCode(code, '__viper_attribute_unknown__'); + + try { + const diagnostics = getDiagnostics(program, configOptions, uri); + console.log('=== 未声明属性名诊断 ==='); + diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`)); + + // ① 不再误报「无法访问类 attr 的属性 stackrealign」(编译器对未知属性是静默丢弃)。 + const attrDiags = diagnostics.filter( + (d) => d.message.includes('无法访问') || d.message.includes('stackrealign') + ); + assert.strictEqual( + attrDiags.length, + 0, + `未声明属性名不应报「无法访问属性」:${JSON.stringify(attrDiags)}` + ); + + // ② 语义高亮:section / aligned / stackrealign 都应为 function(与已声明属性同款)。 + const tokensByLine = getSemanticTokens(program, uri); + const decoratorLine = tokensByLine.get(2) || []; + console.log('=== 装饰器行语义 token ==='); + decoratorLine.forEach((t) => console.log(` [${t.tokenType}] ${t.text}`)); + for (const name of ['section', 'aligned', 'stackrealign']) { + const tok = decoratorLine.find((t) => t.text === name); + assert(tok, `该行应有 ${name} 的语义 token:${JSON.stringify(decoratorLine)}`); + assert.strictEqual( + tok!.tokenType, + SemanticTokenTypes.function, + `${name} 应为 function 语义 token(未声明属性名原先落到 undefined),实际 ${tok!.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 ec948c72..2f14a4f1 100644 --- a/packages/vscode-vipeye/CHANGELOG.md +++ b/packages/vscode-vipeye/CHANGELOG.md @@ -39,6 +39,7 @@ - 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 +- Attribute names inside `__attribute__(...)` are highlighted **structurally**, so a name that the compiler supports but that isn't declared in the `attr` class still colours like the declared ones. `stackrealign` (supported by TransPyV's code generator — `FuncGenBase.vp` maps it to the LLVM `"stackrealign"` attribute — and used by ViperOS `VKernel/Kernel/main.vp`) is declared in neither `t.py` nor the `t.vpi` stub, so `t.attr.stackrealign` used to be unresolvable: the name got no semantic token (rendered unhighlighted) and the file was additionally flagged with "无法访问类"type[attr]"的属性"stackrealign"". The parser now marks the synthesized attribute-name node, the semantic token provider colours it as `function` (exactly like a declared attribute such as `section` / `aligned` / `naked`), and the member-access diagnostic is suppressed for these names — the compiler silently drops unknown attributes rather than rejecting them, so reporting them was a false positive. Declared names keep resolving through `t.attr` as before, so hover and jump-to-definition still work for them ## 1.1.425