feat(vipeye-internal): __attribute__ 属性名结构化高亮(未声明的也不报错)

`@__attribute__(section(".text.startup"), aligned(16), stackrealign)` 里
`stackrealign` 原先不高亮(白色)且误报「无法访问类"type[attr]"的属性」。

根因:属性名脱糖成 `t.attr.<name>`,高亮完全依赖能否解析到 `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 条
This commit is contained in:
TermiNexus committed 2026-09-28 14:59:44 +08:00
1 parent b4808c2821
commit 71cb333549
7 files changed
+129 -4

No files matched your search

+1
View File
@@ -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__/
@@ -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.<name>)是编译器级
// 开放集合,未声明在 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.<name>`,见 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;
@@ -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.<name>`,
// 见 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) {
@@ -1723,6 +1723,13 @@ export interface NameNode extends ParseNodeBase<ParseNodeType.Name> {
// parser._parseViperStaticAnnotatedStatement)。变量的 Declaration.node
// 就是这个 NameNode,跨模块取参检查依此判定「声明带 static」。
isViperStatic?: boolean;
// Viper 扩展:`__attribute__(name)` 脱糖(见 parser._makeViperAttrMarker)
// 时由**裸属性名实参**合成的成员名 `t.attr.<name>`:位置取自实参自身的
// token(真实位置,保留跳转/悬停)。属性名是编译器级的开放集合
// (TransPyV 支持的名字未必都声明在 t.attr 里),故该名解析不到时
// 不报「无法访问属性」,语义高亮直接按已声明属性同款(function)着色。
isViperAttrMarkerName?: boolean;
};
}
+10 -2
View File
@@ -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);
}
@@ -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<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;
}
/** 收集注解变量的展示类型(按变量名索引)。 */
function collectAnnotatedVarTypes(program: Program, parseResults: any): Map<string, string> {
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 });
}
});
+1
View File
@@ -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