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:
1 parent
b4808c2821
commit
71cb333549
7 files changed
+129
-4
No files matched your search
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user