feat(vipeye-internal): __attribute__ 免 import t/c 与状态栏分析进度
- __attribute__ 脱糖出的 t / c 模块根改为「隐式模块根」(零长度合成名 +
isViperImplicitModule 标记),作用域链取不到时回退 builtins 直接查
symbolTable,故不再要求源码 import t / import c;手写裸 t / c 仍报未定义。
补上 builtins.vpi 缺失的 import c(原先只有 import t,装饰器路径一直报
「未定义 c」)。
- 新增左下角状态栏分析进度:逐文件回调 → 后台线程节流 50ms 回传
analysisProgress 消息 → 前台 sendNotification('viper/analysisStatus')
→ 客户端 StatusBarItem 显示 `C:\xxx\1.vp (4/90) 4.44% loading...`,
分析结束自动隐藏,让用户能区分「插件在忙」与「插件卡死」。
- viperAttribute.test.ts 补回被误删的 hover 用例并新增 2 个回归用例;
jest 794 → 796 通过。
This commit is contained in:
1 parent
baec16e540
commit
4d5a0d203b
15 files changed
+339
-24
No files matched your search
@@ -143,6 +143,8 @@ packages/vipeye-internal/src/tests/__viper_attribute__/
|
||||
packages/vipeye-internal/src/tests/__viper_attribute_completion__/
|
||||
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_volatile__/
|
||||
packages/vipeye-internal/src/tests/__viper_volatile_bad__/
|
||||
packages/vipeye-internal/src/tests/__viper_callable__/
|
||||
|
||||
@@ -37,6 +37,26 @@ export interface RequiringAnalysisCount {
|
||||
cells: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Viper 扩展:分析进度的快照,供 LSP 客户端在状态栏显示
|
||||
* 「C:\xxx\1.vp (4/90) 0.04% loading...」这类进度信息。
|
||||
*/
|
||||
export interface AnalysisProgress {
|
||||
/** 当前正在分析的文件路径(文件系统路径,Windows 下形如 C:\xxx\1.vp)。 */
|
||||
currentFile: string | undefined;
|
||||
|
||||
/** 已分析的用户文件数。 */
|
||||
analyzed: number;
|
||||
|
||||
/** 需要分析的用户文件总数。 */
|
||||
total: number;
|
||||
|
||||
/** 是否已分析完毕(true 表示可以收起状态栏进度项)。 */
|
||||
done: boolean;
|
||||
}
|
||||
|
||||
export type AnalysisProgressCallback = (progress: AnalysisProgress) => void;
|
||||
|
||||
export type AnalysisCompleteCallback = (results: AnalysisResults) => void;
|
||||
|
||||
export function analyzeProgram(
|
||||
@@ -45,7 +65,8 @@ export function analyzeProgram(
|
||||
configOptions: ConfigOptions,
|
||||
callback: AnalysisCompleteCallback | undefined,
|
||||
console: ConsoleInterface,
|
||||
token: CancellationToken
|
||||
token: CancellationToken,
|
||||
progressCallback?: AnalysisProgressCallback
|
||||
): boolean {
|
||||
let moreToAnalyze = false;
|
||||
|
||||
@@ -55,7 +76,17 @@ export function analyzeProgram(
|
||||
throwIfCancellationRequested(token);
|
||||
|
||||
const duration = new Duration();
|
||||
moreToAnalyze = program.analyze(maxTime, token);
|
||||
// 逐文件上报进度(analyze 每检查完一个文件就回一次),
|
||||
// 让客户端状态栏能实时反映「正在分析哪个文件」。
|
||||
moreToAnalyze = program.analyze(
|
||||
maxTime,
|
||||
token,
|
||||
progressCallback ? () => progressCallback({ ...program.getAnalysisProgress(), done: false }) : undefined
|
||||
);
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback({ ...program.getAnalysisProgress(), done: !moreToAnalyze });
|
||||
}
|
||||
|
||||
const requiringAnalysisCount = program.getFilesToAnalyzeCount();
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ServiceProvider } from '../common/serviceProvider';
|
||||
import '../common/serviceProviderExtensions';
|
||||
import { Range } from '../common/textRange';
|
||||
import { Uri } from '../common/uri/uri';
|
||||
import { AnalysisCompleteCallback, analyzeProgram } from './analysis';
|
||||
import { AnalysisCompleteCallback, AnalysisProgressCallback, analyzeProgram } from './analysis';
|
||||
import { ImportResolver } from './importResolver';
|
||||
import { MaxAnalysisTime, OpenFileOptions, Program } from './program';
|
||||
import { TypeStubWriter } from './typeStubWriter';
|
||||
@@ -33,6 +33,7 @@ export class BackgroundAnalysisProgram {
|
||||
private _program: Program;
|
||||
private _disposed = false;
|
||||
private _onAnalysisCompletion: AnalysisCompleteCallback | undefined;
|
||||
private _onAnalysisProgress: AnalysisProgressCallback | undefined;
|
||||
private _preEditAnalysis: IBackgroundAnalysis | undefined;
|
||||
|
||||
constructor(
|
||||
@@ -157,6 +158,11 @@ export class BackgroundAnalysisProgram {
|
||||
this._backgroundAnalysis?.setCompletionCallback(callback);
|
||||
}
|
||||
|
||||
setAnalysisProgressCallback(callback?: AnalysisProgressCallback) {
|
||||
this._onAnalysisProgress = callback;
|
||||
this._backgroundAnalysis?.setAnalysisProgressCallback(callback);
|
||||
}
|
||||
|
||||
startAnalysis(token: CancellationToken): boolean {
|
||||
if (this._backgroundAnalysis) {
|
||||
this._backgroundAnalysis.startAnalysis(token);
|
||||
@@ -169,7 +175,8 @@ export class BackgroundAnalysisProgram {
|
||||
this._configOptions,
|
||||
this._onAnalysisCompletion,
|
||||
this._serviceProvider.console(),
|
||||
token
|
||||
token,
|
||||
this._onAnalysisProgress
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -160,6 +160,10 @@ export class Program {
|
||||
private _editModeTracker = new EditModeTracker();
|
||||
private _sourceFileFactory: ISourceFileFactory;
|
||||
|
||||
// Viper 扩展:LSP 状态栏进度所需的「当前正在分析的文件路径」。
|
||||
// 在 analyze() 的循环里逐文件更新,见 getAnalysisProgress()。
|
||||
private _currentAnalysisFilePath: string | undefined;
|
||||
|
||||
// Viper 扩展:includes/builtins.vp(真实源码,非 .vpi 存根)的 typedef 符号
|
||||
// 合并状态。key =(typeshed builtins 存根 + 执行环境 + 被分析文件目录),
|
||||
// 避免对同一环境/目录下的文件重复扫描/合并;不同目录或不同执行环境会各自尝试。
|
||||
@@ -714,7 +718,11 @@ export class Program {
|
||||
// whether the method needs to be called again to complete the
|
||||
// analysis. In interactive mode, the timeout is always limited
|
||||
// to the smaller value to maintain responsiveness.
|
||||
analyze(maxTime?: MaxAnalysisTime, token: CancellationToken = CancellationToken.None): boolean {
|
||||
analyze(
|
||||
maxTime?: MaxAnalysisTime,
|
||||
token: CancellationToken = CancellationToken.None,
|
||||
onProgress?: () => void
|
||||
): boolean {
|
||||
return this._runEvaluatorWithCancellationToken(token, () => {
|
||||
const elapsedTime = new Duration();
|
||||
|
||||
@@ -727,6 +735,8 @@ export class Program {
|
||||
|
||||
// Check the open files.
|
||||
for (const sourceFileInfo of openFiles) {
|
||||
this._currentAnalysisFilePath = sourceFileInfo.uri.getFilePath();
|
||||
onProgress?.();
|
||||
if (this._checkTypes(sourceFileInfo)) {
|
||||
if (elapsedTime.getDurationInMilliseconds() > effectiveMaxTime) {
|
||||
return true;
|
||||
@@ -751,6 +761,8 @@ export class Program {
|
||||
continue;
|
||||
}
|
||||
|
||||
this._currentAnalysisFilePath = sourceFileInfo.uri.getFilePath();
|
||||
onProgress?.();
|
||||
if (this._checkTypes(sourceFileInfo)) {
|
||||
if (elapsedTime.getDurationInMilliseconds() > effectiveMaxTime) {
|
||||
return true;
|
||||
@@ -763,6 +775,22 @@ export class Program {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Viper 扩展:返回分析进度的快照,供 LSP 客户端在状态栏显示
|
||||
* 「正在分析哪个文件 / 已分析多少 / 共多少个用户文件」。
|
||||
* 计数与 AnalysisResults.requiringAnalysisCount 同源(都走 getFilesToAnalyzeCount),
|
||||
* 因此百分比不会与实际分析进度脱节。
|
||||
*/
|
||||
getAnalysisProgress(): { currentFile: string | undefined; analyzed: number; total: number } {
|
||||
const total = this.getUserFileCount();
|
||||
const pending = this.getFilesToAnalyzeCount().files;
|
||||
return {
|
||||
currentFile: this._currentAnalysisFilePath,
|
||||
analyzed: Math.min(total, Math.max(0, total - pending)),
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
// Performs parsing and analysis of a single file in the program. If the file is not part of
|
||||
// the program returns false to indicate analysis was not performed.
|
||||
analyzeFile(fileUri: Uri, token: CancellationToken = CancellationToken.None): boolean {
|
||||
|
||||
@@ -52,7 +52,7 @@ import {
|
||||
isFile,
|
||||
tryStat,
|
||||
} from '../common/uri/uriUtils';
|
||||
import { AnalysisCompleteCallback } from './analysis';
|
||||
import { AnalysisCompleteCallback, AnalysisProgressCallback } from './analysis';
|
||||
import {
|
||||
BackgroundAnalysisProgram,
|
||||
BackgroundAnalysisProgramFactory,
|
||||
@@ -340,6 +340,14 @@ export class AnalyzerService {
|
||||
this._backgroundAnalysisProgram.setCompletionCallback(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Viper 扩展:注册分析进度回调(逐文件上报「正在分析哪个文件」),
|
||||
* 由 languageServerBase 转成客户端状态栏进度通知。
|
||||
*/
|
||||
setAnalysisProgressCallback(callback: AnalysisProgressCallback | undefined): void {
|
||||
this._backgroundAnalysisProgram.setAnalysisProgressCallback(callback);
|
||||
}
|
||||
|
||||
setOptions(commandLineOptions: CommandLineOptions): void {
|
||||
this._commandLineOptions = commandLineOptions;
|
||||
|
||||
|
||||
@@ -5379,6 +5379,23 @@ export function createTypeEvaluator(
|
||||
// 它们是 builtins stub 中的别名,正常 Viper 工程均可解析;
|
||||
// 解析不到时静默退化为 Unknown,不报「未定义」(与 [SIZE]T 同一取舍)。
|
||||
type = UnknownType.create();
|
||||
} else if (node.d.isViperImplicitModule) {
|
||||
// Viper 扩展:`__attribute__` 脱糖出的「隐式模块根」名 `t` / `c`
|
||||
// (见 parser._createViperImplicitModuleName)。源码里并不存在这两个
|
||||
// 名字,也不要求源码 `import t` / `import c` —— 编译器对属性的识别是
|
||||
// AST 级的,同样不看符号可见性。故在词法作用域里取不到时回退到
|
||||
// builtins 作用域的同名绑定;仍取不到才报未定义。
|
||||
const implicitModuleType = getViperImplicitModuleType(node, name);
|
||||
if (implicitModuleType) {
|
||||
type = implicitModuleType;
|
||||
} else {
|
||||
addDiagnostic(
|
||||
DiagnosticRule.reportUndefinedVariable,
|
||||
LocMessage.symbolIsUndefined().format({ name }),
|
||||
node
|
||||
);
|
||||
type = UnknownType.create();
|
||||
}
|
||||
} else {
|
||||
// Viper 扩展:前向引用支持(收紧版)。
|
||||
// Viper 是 AOT 语言,函数/类的定义顺序无关(类似 C 语言的前向声明),
|
||||
@@ -9442,12 +9459,41 @@ export function createTypeEvaluator(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Viper 扩展:解析 `__attribute__` 脱糖出的「隐式模块根」名(`t` / `c`)。
|
||||
// 这类名字源码里并不存在(见 parser._createViperImplicitModuleName),也不要求
|
||||
// 源码 `import t` / `import c` —— 编译器对属性的识别是 AST 级的,同样不看符号
|
||||
// 可见性。故在词法作用域里取不到时沿 scope 链(含 builtins 作用域)查找同名绑定:
|
||||
// builtins.vpi 通过 `from t import ...` / `from c import ...` 绑定了 `t` / `c`,
|
||||
// 但 import-only 绑定对下游不可见(isExternallyHidden),故这里直接查符号表,
|
||||
// 而不是走 lookUpSymbolRecursive。
|
||||
// 用户源码里手写的裸 `t` / `c` 不带该标记,仍照旧报未定义。
|
||||
function getViperImplicitModuleType(node: ParseNode, name: string): Type | undefined {
|
||||
let scope = ScopeUtils.getScopeForNode(node);
|
||||
while (scope) {
|
||||
const symbol = scope.lookUpSymbol(name);
|
||||
if (symbol) {
|
||||
const effectiveType = getEffectiveTypeOfSymbolForUsage(
|
||||
symbol,
|
||||
/* usageNode */ undefined,
|
||||
/* useLastDecl */ true
|
||||
).type;
|
||||
if (effectiveType) {
|
||||
return effectiveType;
|
||||
}
|
||||
}
|
||||
scope = scope.parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Viper 扩展:解析 `t.attr.<name>` / `t.attr.<name>(...)` 属性标记 → t.CAttr 实例。
|
||||
//
|
||||
// 形态(与 TransPyC HandlesAssign._TAttrName / TransPyV ClassGenBase._tattr_name
|
||||
// 同判据:只看最外层属性名):
|
||||
// t.attr.packed (Attribute:t.attr.llvm.nounwind → 最外层 nounwind)
|
||||
// t.attr.section(".x") (Call:取 func 的最外层属性名)
|
||||
// 根 `t` 可以是手写的(需 import t)或 `__attribute__` 脱糖出的隐式模块根
|
||||
// (isViperImplicitModule,不要求 import)。
|
||||
// 这类表达式出现在类型注解联合中(`T | t.attr.section(".x")`、
|
||||
// `T | t.attr.llvm.nounwind`),或由 parser 从 `__attribute__(...)` 修饰器脱糖而来;
|
||||
// 注解位置求值为 CAttr 标记——与 CConst / CVolatile 同族的修饰符
|
||||
@@ -9470,14 +9516,17 @@ export function createTypeEvaluator(
|
||||
}
|
||||
|
||||
const moduleSymbolWithScope = lookUpSymbolRecursive(cur, cur.d.value, /* honorCodeFlow */ true);
|
||||
if (!moduleSymbolWithScope) {
|
||||
return undefined;
|
||||
let moduleType: Type | undefined;
|
||||
if (moduleSymbolWithScope) {
|
||||
moduleType = getEffectiveTypeOfSymbolForUsage(
|
||||
moduleSymbolWithScope.symbol,
|
||||
/* usageNode */ undefined,
|
||||
/* useLastDecl */ true
|
||||
).type;
|
||||
} else if (cur.d.isViperImplicitModule) {
|
||||
// `__attribute__` 脱糖出的隐式 `t`:源码未 import t 时回退到 builtins 绑定。
|
||||
moduleType = getViperImplicitModuleType(cur, cur.d.value);
|
||||
}
|
||||
const moduleType = getEffectiveTypeOfSymbolForUsage(
|
||||
moduleSymbolWithScope.symbol,
|
||||
/* usageNode */ undefined,
|
||||
/* useLastDecl */ true
|
||||
).type;
|
||||
if (!moduleType || !isModule(moduleType)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import { MessageChannel, MessagePort, Worker, parentPort, threadId, workerData }
|
||||
|
||||
import {
|
||||
AnalysisCompleteCallback,
|
||||
AnalysisProgress,
|
||||
AnalysisProgressCallback,
|
||||
AnalysisResults,
|
||||
RequiringAnalysisCount,
|
||||
analyzeProgram,
|
||||
@@ -51,6 +53,7 @@ import { ProgramView } from './common/extensibility';
|
||||
export interface IBackgroundAnalysis extends Disposable {
|
||||
setProgramView(program: Program): void;
|
||||
setCompletionCallback(callback?: AnalysisCompleteCallback): void;
|
||||
setAnalysisProgressCallback(callback?: AnalysisProgressCallback): void;
|
||||
setImportResolver(importResolver: ImportResolver): void;
|
||||
setConfigOptions(configOptions: ConfigOptions): void;
|
||||
setTrackedFiles(fileUris: Uri[]): void;
|
||||
@@ -84,6 +87,9 @@ export class BackgroundAnalysisBase implements IBackgroundAnalysis {
|
||||
|
||||
private _worker: Worker | undefined;
|
||||
private _onAnalysisCompletion: AnalysisCompleteCallback = nullCallback;
|
||||
private _onAnalysisProgress: AnalysisProgressCallback = () => {
|
||||
/* empty */
|
||||
};
|
||||
private _messageChannel: MessageChannel;
|
||||
|
||||
protected program: ProgramView | undefined;
|
||||
@@ -115,6 +121,14 @@ export class BackgroundAnalysisBase implements IBackgroundAnalysis {
|
||||
this._onAnalysisCompletion = callback ?? nullCallback;
|
||||
}
|
||||
|
||||
setAnalysisProgressCallback(callback?: AnalysisProgressCallback) {
|
||||
this._onAnalysisProgress =
|
||||
callback ??
|
||||
(() => {
|
||||
/* empty */
|
||||
});
|
||||
}
|
||||
|
||||
setImportResolver(importResolver: ImportResolver) {
|
||||
this.enqueueRequest({ requestType: 'setImportResolver', data: serialize(importResolver.host.kind) });
|
||||
}
|
||||
@@ -322,6 +336,11 @@ export class BackgroundAnalysisBase implements IBackgroundAnalysis {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'analysisProgress': {
|
||||
this._onAnalysisProgress(deserialize<AnalysisProgress>(msg.data));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
debug.fail(`${msg.requestType} is not expected. Message structure: ${JSON.stringify(msg)}`);
|
||||
}
|
||||
@@ -344,6 +363,11 @@ export class BackgroundAnalysisBase implements IBackgroundAnalysis {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'analysisProgress': {
|
||||
this._onAnalysisProgress(deserialize<AnalysisProgress>(msg.data));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'analysisPaused': {
|
||||
// Analysis request has completed, but there is more to
|
||||
// analyze, so queue another message to resume later.
|
||||
@@ -607,13 +631,25 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
protected handleResumeAnalysis(port: MessagePort, token: CancellationToken) {
|
||||
// Report results at the interval of the max analysis time.
|
||||
const maxTime = { openFilesTimeInMs: 50, noOpenFilesTimeInMs: 200 };
|
||||
|
||||
// Viper 扩展:把逐文件的分析进度回传前台(节流到 50ms 一次,避免
|
||||
// 大量文件时 IPC 消息过密)。收尾那次(done)一定要发。
|
||||
let lastProgressSent = 0;
|
||||
const moreToAnalyze = analyzeProgram(
|
||||
this.program,
|
||||
maxTime,
|
||||
this._configOptions,
|
||||
(result) => this.onAnalysisCompletion(port, result),
|
||||
this.getConsole(),
|
||||
token
|
||||
token,
|
||||
(progress) => {
|
||||
const now = Date.now();
|
||||
if (!progress.done && now - lastProgressSent < 50) {
|
||||
return;
|
||||
}
|
||||
lastProgressSent = now;
|
||||
this.onAnalysisProgress(port, progress);
|
||||
}
|
||||
);
|
||||
|
||||
if (moreToAnalyze) {
|
||||
@@ -770,6 +806,14 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase
|
||||
port.postMessage({ requestType: 'analysisResult', data: serialize(result) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Viper 扩展:把「正在分析哪个文件 / 已分析多少」回传前台线程,
|
||||
* 由 languageServerBase 转成 `viper/analysisStatus` 通知给客户端状态栏。
|
||||
*/
|
||||
protected onAnalysisProgress(port: MessagePort, progress: AnalysisProgress) {
|
||||
port.postMessage({ requestType: 'analysisProgress', data: serialize(progress) });
|
||||
}
|
||||
|
||||
private _onMessageWrapper(msg: BackgroundRequest) {
|
||||
try {
|
||||
return this.onMessage(msg);
|
||||
@@ -881,7 +925,7 @@ export interface BackgroundRequest {
|
||||
sharedUsageBuffer?: SharedArrayBuffer;
|
||||
}
|
||||
|
||||
export type BackgroundResponseKind = 'log' | 'analysisResult' | 'analysisPaused' | 'analysisDone';
|
||||
export type BackgroundResponseKind = 'log' | 'analysisResult' | 'analysisProgress' | 'analysisPaused' | 'analysisDone';
|
||||
|
||||
export interface BackgroundResponse {
|
||||
requestType: BackgroundResponseKind;
|
||||
|
||||
@@ -84,7 +84,7 @@ import {
|
||||
} from 'vscode-languageserver';
|
||||
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument';
|
||||
import { AnalysisResults } from './analyzer/analysis';
|
||||
import { AnalysisProgress, AnalysisResults } from './analyzer/analysis';
|
||||
import { BackgroundAnalysisProgram, InvalidatedReason } from './analyzer/backgroundAnalysisProgram';
|
||||
import { ImportResolver } from './analyzer/importResolver';
|
||||
import { MaxAnalysisTime } from './analyzer/program';
|
||||
@@ -369,9 +369,19 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis
|
||||
});
|
||||
|
||||
service.setCompletionCallback((results) => this.onAnalysisCompletedHandler(service.fs, results));
|
||||
service.setAnalysisProgressCallback((progress) => this.sendAnalysisStatus(progress));
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Viper 扩展:把「正在分析哪个文件 / 已分析多少 / 共多少」通知给客户端,
|
||||
* 供其在左下角状态栏显示 `C:\xxx\1.vp (4/90) 0.04% loading...`,
|
||||
* 让用户能判断语言服务是在工作还是卡死。
|
||||
*/
|
||||
protected sendAnalysisStatus(progress: AnalysisProgress) {
|
||||
this.connection.sendNotification('viper/analysisStatus', progress);
|
||||
}
|
||||
|
||||
async getWorkspaces(): Promise<Workspace[]> {
|
||||
const workspaces = this.workspaceFactory.items();
|
||||
for (const workspace of workspaces) {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
* 没有可命中的节点。
|
||||
*/
|
||||
|
||||
import { ScopeType } from '../analyzer/scope';
|
||||
import { getScopeForNode } from '../analyzer/scopeUtils';
|
||||
import { Symbol, SymbolTable } from '../analyzer/symbol';
|
||||
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
|
||||
@@ -77,12 +76,15 @@ export function getViperAttributeSymbols(
|
||||
evaluator: TypeEvaluator,
|
||||
scopeNode: ParseNode
|
||||
): { symbolTable: SymbolTable; attrClass: ClassType } | undefined {
|
||||
const scope = getScopeForNode(scopeNode);
|
||||
if (!scope || scope.type !== ScopeType.Module) {
|
||||
return undefined;
|
||||
// 取 `t` 模块:优先本文件的 `import t`,否则沿 scope 链回退到 builtins 作用域
|
||||
// (builtins.vpi 通过 `from t import ...` 绑定了 `t`)—— 与 `__attribute__`
|
||||
// 脱糖出的隐式 `t` 一致(源码不 import t 时属性名补全/悬停仍应可用)。
|
||||
let scope = getScopeForNode(scopeNode);
|
||||
let tSymbol: Symbol | undefined;
|
||||
while (scope && !tSymbol) {
|
||||
tSymbol = scope.lookUpSymbol('t');
|
||||
scope = scope.parent;
|
||||
}
|
||||
|
||||
const tSymbol = scope.lookUpSymbol('t');
|
||||
if (!tSymbol) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1704,6 +1704,14 @@ export interface NameNode extends ParseNodeBase<ParseNodeType.Name> {
|
||||
// 但脱糖目标是 builtins stub 中的别名,不要求源码 import t;
|
||||
// 名字解析不到时静默退化为 Unknown。
|
||||
isViperTypeDescriptor?: boolean;
|
||||
|
||||
// Viper 扩展:`__attribute__` 脱糖(见 parser._desugarViperMagicAttribute /
|
||||
// _makeViperAttrMarker)时合成出来的 `t` / `c` **模块根**名:零长度、
|
||||
// 不对应源码字符。编译器的属性识别是 AST 级的(不要求源码 import t / import c),
|
||||
// 故这里也允许隐式解析:该名在词法作用域链上取不到时回退到 builtins 作用域
|
||||
// 的同名绑定(builtins.vpi 通过 `from t import ...` / `from c import ...`
|
||||
// 绑定了它们),而不是报"未定义 t"。
|
||||
isViperImplicitModule?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5435,9 +5435,11 @@ export class Parser {
|
||||
// - 已是 `t.attr.X` / `c.Attribute(...)` 形态 → 原样保留(允许混写)
|
||||
// 与 TPC/TPV 的 includes/ast/parser.vp `_MakeTAttrMarker` 逐条对应;
|
||||
// 成员名复用实参自身的 token(真实位置),故属性名上的跳转/悬停仍指向 t.vpi。
|
||||
// 根 `t` 是合成的「隐式模块根」(见 _createViperImplicitModuleName),
|
||||
// 不要求源码 `import t`。
|
||||
private _makeViperAttrMarker(argExpr: ExpressionNode): ExpressionNode {
|
||||
const attrRoot = MemberAccessNode.create(
|
||||
NameNode.create(IdentifierToken.create(argExpr.start, 0, 't', /* comments */ undefined)),
|
||||
this._createViperImplicitModuleName(argExpr.start, 't'),
|
||||
NameNode.create(IdentifierToken.create(argExpr.start, 0, 'attr', /* comments */ undefined))
|
||||
);
|
||||
|
||||
@@ -5454,6 +5456,17 @@ export class Parser {
|
||||
return argExpr;
|
||||
}
|
||||
|
||||
// Viper 扩展:合成 zero-length 的「隐式模块根」名(`t` / `c`)。
|
||||
// `__attribute__` 的脱糖节点里引用 `t` / `c`,但**不要求源码 import t / import c**
|
||||
// —— 与编译器一致(TPC/TPV 的属性识别是 AST 级的,不看符号是否可见)。
|
||||
// 该名在词法作用域里解析不到时,typeEvaluator 会回退到 builtins 作用域的同名
|
||||
// 模块绑定(见 NameNode.d.isViperImplicitModule)。
|
||||
private _createViperImplicitModuleName(start: number, name: string): NameNode {
|
||||
const nameNode = NameNode.create(IdentifierToken.create(start, 0, name, /* comments */ undefined));
|
||||
nameNode.d.isViperImplicitModule = true;
|
||||
return nameNode;
|
||||
}
|
||||
|
||||
// Viper 扩展:把 `@__attribute__(...)` 装饰器脱糖为
|
||||
// `@c.Attribute(t.attr.a, t.attr.b)`——与 TPC/TPV 的
|
||||
// includes/ast/parser.vp `_DesugarMagicAttribute` 逐字段同形,因此 class 与 def
|
||||
@@ -5468,7 +5481,7 @@ export class Parser {
|
||||
}
|
||||
|
||||
const attributeCallee = MemberAccessNode.create(
|
||||
NameNode.create(IdentifierToken.create(expr.start, 0, 'c', /* comments */ undefined)),
|
||||
this._createViperImplicitModuleName(expr.start, 'c'),
|
||||
NameNode.create(IdentifierToken.create(expr.start, 0, 'Attribute', /* comments */ undefined))
|
||||
);
|
||||
|
||||
|
||||
@@ -325,6 +325,64 @@ def f() -> int:
|
||||
}
|
||||
});
|
||||
|
||||
test('__attribute__ 不要求 import t / import c(隐式模块根)', () => {
|
||||
const code = `@__attribute__(section(".text.wo"), always_inline)
|
||||
def decorated(a: int) -> int:
|
||||
return a
|
||||
|
||||
@__attribute__(packed, aligned(16))
|
||||
class Packed:
|
||||
x: int
|
||||
|
||||
AttrConst: __attribute__(section(".PRVERSN")) const int = 1
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider, testDir } = buildProgramForCode(code, '__viper_attribute_noimport__');
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== __attribute__ 免 import 诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
|
||||
|
||||
const realDiagnostics = diagnostics.filter((d) => d.category === 0 || d.category === 1);
|
||||
assert.strictEqual(
|
||||
realDiagnostics.length,
|
||||
0,
|
||||
`__attribute__ 脱糖出的 t / c 不应报未定义:${JSON.stringify(realDiagnostics)}`
|
||||
);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('隐式模块根只作用于 __attribute__ 脱糖:手写裸 t / c 仍报未定义', () => {
|
||||
const code = `Bad: t.CInt = 1
|
||||
|
||||
def use_c() -> int:
|
||||
return c.SomeThing
|
||||
`;
|
||||
const { program, configOptions, uri, serviceProvider, testDir } = buildProgramForCode(
|
||||
code,
|
||||
'__viper_attribute_implicit_scope__'
|
||||
);
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== 手写裸 t / c 诊断 ===');
|
||||
diagnostics.forEach((d) => console.log(` L${d.line} [category=${d.category}] ${d.message}`));
|
||||
|
||||
const undefinedT = diagnostics.filter((d) => d.message.includes('未定义') && d.message.includes('t'));
|
||||
const undefinedC = diagnostics.filter((d) => d.message.includes('未定义') && d.message.includes('c'));
|
||||
assert(undefinedT.length > 0, `手写裸 t 应仍报未定义:${JSON.stringify(diagnostics)}`);
|
||||
assert(undefinedC.length > 0, `手写裸 c 应仍报未定义:${JSON.stringify(diagnostics)}`);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('hover 在 __attribute__(...) 括号内提示全部属性名', () => {
|
||||
// 第 2 行第 22 列落在实参 `section` 上。
|
||||
const code = `import t
|
||||
|
||||
@@ -2,6 +2,12 @@ import _ast
|
||||
import _sitebuiltins
|
||||
import _typeshed
|
||||
import sys
|
||||
# Viper 扩展:`__attribute__` 脱糖出的隐式模块根 `c` / `t`
|
||||
# (见 parseNodes.NameNode.d.isViperImplicitModule)。这两个 import 是
|
||||
# import-only 绑定(isExternallyHidden),普通名字解析查不到,仅供
|
||||
# typeEvaluator.getViperImplicitModuleType 直接查 symbolTable 时回退使用,
|
||||
# 因此源码里手写裸 `t` / `c` 仍然照旧报未定义。
|
||||
import c
|
||||
import t
|
||||
import types
|
||||
from _collections_abc import dict_items, dict_keys, dict_values
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
- `__attribute__` is highlighted as a type modifier (same scope as `const` / `volatile`): the TextMate grammar matches it when followed by `(`, in both the decorator and the modifier spelling
|
||||
- `__attribute__` is also offered by completion (typing `__a` lists it) and **hovering inside `__attribute__(...)`** shows the full list of available attribute names (`section` / `aligned` / `always_inline` / `weak` / …), taken from the `attr` class of the `t` stub. Both share one helper (`viperAttributeUtils`) for locating the parentheses and enumerating `t.attr`, so the completion list and the hover list cannot drift apart
|
||||
- Lowercase **`callable[[A, B], R]`** is now supported in a type annotation and means exactly `t.Callable[[A, B], R]` — all three spellings (`callable[...]`, `Callable[...]`, `t.Callable[...]`) and all the usual argument forms (`callable[[int, str], bool]`, `callable[..., bool]`, `callable[[], bool]`, as a parameter, return or variable annotation) evaluate to the same callable type. The parser rewrites the bare name `callable` to `Callable` **only in type-annotation position**, so the built-in function is untouched: `callable(x)` / `if callable(x):` still behave as before. `Callable` is declared in the built-in `builtins.vpi` stub (`Callable: t.CTypedef = t.Callable`, next to `rawptr` / `CConst`), so no `import t` is required. Previously `callable[[int], bool]` silently degraded to the type of the `callable` *function* (the Viper-dialect rule that ignores explicit type arguments on a callable), and bare `Callable` was reported as undefined
|
||||
- `__attribute__` no longer requires the file to `import t` / `import c`: the `t` / `c` module roots that `__attribute__(...)` desugars into are synthesized as **implicit module names** (zero-length, marked on the AST node — the compiler's attribute handling is AST-level and never checks symbol visibility either). When such a synthetic name cannot be resolved through the lexical scope chain, it falls back to the same-named binding in the built-in `builtins.vpi` stub; the fallback only applies to the desugared nodes, so a hand-written bare `t` / `c` in a file without the import still reports "未定义"
|
||||
- **Analysis progress in the status bar**: while the language server builds the dependency graph and type-checks the workspace, the bottom-left status bar now shows `$(sync~spin) C:\xxx\1.vp (4/90) 4.44% loading...` — the file currently being analyzed, how many files have been processed out of how many, and the percentage. The worker reports per-file progress (throttled to one message every 50 ms) through a new `analysisProgress` background message and a new `viper/analysisStatus` notification; the client shows a left-aligned `StatusBarItem` and hides it as soon as analysis finishes (or when there is nothing to analyze). Previously the only feedback was the work-done progress bar for open-files analysis, which said nothing about which file was being processed, so a long full-workspace pass was indistinguishable from a hung server
|
||||
|
||||
## 1.1.425
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
Position,
|
||||
ProgressLocation,
|
||||
Range,
|
||||
StatusBarAlignment,
|
||||
StatusBarItem,
|
||||
TextEditor,
|
||||
TextEditorEdit,
|
||||
Uri,
|
||||
@@ -82,6 +84,41 @@ function isDevelopmentCopyLoaded(): boolean {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端 `viper/analysisStatus` 通知的载荷,对应 vipeye-internal 的
|
||||
* `AnalysisProgress`(依赖图构建 / import 分析阶段的逐文件进度)。
|
||||
*/
|
||||
interface AnalysisStatusNotification {
|
||||
/** 当前正在分析的文件路径(Windows 下形如 C:\xxx\1.vp)。 */
|
||||
currentFile: string | undefined;
|
||||
|
||||
/** 已分析的用户文件数。 */
|
||||
analyzed: number;
|
||||
|
||||
/** 需要分析的用户文件总数。 */
|
||||
total: number;
|
||||
|
||||
/** 分析是否已结束(true 时收起状态栏进度项)。 */
|
||||
done: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 左下角状态栏的分析进度项:显示 `C:\xxx\1.vp (4/90) 4.44% loading...`。
|
||||
* 依赖图构建 / import 分析期间没有其他可见反馈,用户无法区分「插件在忙」
|
||||
* 与「插件卡死」,故这里给出实时进度;分析结束后自动隐藏。
|
||||
*/
|
||||
function UpdateAnalysisStatusItem(item: StatusBarItem, progress: AnalysisStatusNotification): void {
|
||||
if (progress.done || progress.total <= 0) {
|
||||
item.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const percent = Math.min(100, (progress.analyzed / progress.total) * 100).toFixed(2);
|
||||
item.text = `$(sync~spin) ${progress.currentFile ?? '...'} (${progress.analyzed}/${progress.total}) ${percent}% loading...`;
|
||||
item.tooltip = `Viper:正在分析 ${progress.total} 个文件(依赖图构建 / import 分析)`;
|
||||
item.show();
|
||||
}
|
||||
|
||||
export async function activate(context: ExtensionContext) {
|
||||
// 调试宿主让路:launch.json 用 --extensionDevelopmentPath 加载开发版时,VS Code 不会
|
||||
// 自动停用已安装的发布版,两者在同一扩展宿主内同时激活并注册同名 'viper.*' 命令,
|
||||
@@ -185,6 +222,16 @@ export async function activate(context: ExtensionContext) {
|
||||
const client = new LanguageClient('vipeye', 'Vipeye', serverOptions, clientOptions);
|
||||
languageClient = client;
|
||||
|
||||
// 左下角状态栏的分析进度项:显示正在分析哪个文件(`... (4/90) 4.44% loading...`),
|
||||
// 让用户知道依赖图构建 / import 分析是否在进行,而不是卡死了。
|
||||
const analysisStatusItem = window.createStatusBarItem(StatusBarAlignment.Left, 100);
|
||||
context.subscriptions.push(analysisStatusItem);
|
||||
context.subscriptions.push(
|
||||
client.onNotification('viper/analysisStatus', (progress: AnalysisStatusNotification) => {
|
||||
UpdateAnalysisStatusItem(analysisStatusItem, progress);
|
||||
})
|
||||
);
|
||||
|
||||
// Register our custom commands.
|
||||
const textEditorCommands = [Commands.orderImports];
|
||||
textEditorCommands.forEach((commandName) => {
|
||||
|
||||
Reference in new issue
Block a user