feat(vipeye-internal): 支持 match-case 内裸 break 与 fallthrough
Validation / Typecheck (push) Has been cancelled
Validation / Style (push) Has been cancelled
Validation / Test macos-latest (push) Has been cancelled
Validation / Test ubuntu-latest (push) Has been cancelled
Validation / Test windows-latest (push) Has been cancelled
Validation / Build (push) Has been cancelled
Validation / Required (push) Has been cancelled
Validation / Typecheck (push) Has been cancelled
Validation / Style (push) Has been cancelled
Validation / Test macos-latest (push) Has been cancelled
Validation / Test ubuntu-latest (push) Has been cancelled
Validation / Test windows-latest (push) Has been cancelled
Validation / Build (push) Has been cancelled
Validation / Required (push) Has been cancelled
- break:parser 新增 _isInMatchCase(解析 case body 期间置位,与 _isInLoop 同法),_parseBreakStatement 判据放宽为 !_isInLoop && !_isInMatchCase,不再误报 breakOutsideLoop - fallthrough:新增 ParseNodeType.Fallthrough / FallthroughNode;作为软关键字仅在「viper 方言 + case 体内 + 独占简单语句」时成立,其它位置仍是普通标识符 - 同步 parseTreeWalker 两处 switch 与 visitor、parseNodeUtils 的 ParseNodeTypeMap、binder 流程终止、flowChartBuilder 终止语句、completion 关键字、TextMate 高亮 - 测试 viperMatchBreakFallthrough.test.ts(2 用例),全量 jest 825 -> 827 通过
This commit is contained in:
1 parent
9e0d12553d
commit
5463fac3d2
11 files changed
+300
-4
No files matched your search
@@ -169,6 +169,8 @@ packages/vipeye-internal/src/tests/__viper_renum_pointer__/
|
||||
packages/vipeye-internal/src/tests/__viper_renum_pointer_matrix__/
|
||||
packages/vipeye-internal/src/tests/__viper_generic_bound__/
|
||||
packages/vipeye-internal/src/tests/__viper_generic_bound_none__/
|
||||
packages/vipeye-internal/src/tests/__viper_match_break_fallthrough__/
|
||||
packages/vipeye-internal/src/tests/__viper_match_break_fallthrough_neg__/
|
||||
|
||||
# perfCompare.py cached commit bundles
|
||||
/build/perfCompare/
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
DelNode,
|
||||
ExceptNode,
|
||||
ExpressionNode,
|
||||
FallthroughNode,
|
||||
ForNode,
|
||||
FunctionNode,
|
||||
GlobalNode,
|
||||
@@ -1448,6 +1449,17 @@ export class Binder extends ParseTreeWalker {
|
||||
return false;
|
||||
}
|
||||
|
||||
override visitFallthrough(node: FallthroughNode): boolean {
|
||||
// Viper 扩展:`fallthrough` 结束当前 case 体(控制流交给下一个 case),
|
||||
// 故本条之后同 case 体内的语句不可达。去向不是「match 之后」,
|
||||
// 故不像 break 那样挂到 _currentBreakTarget;下一个 case 的入口不建模
|
||||
// (visitMatch 的 postMatchLabel 依赖「未终止的 case 体」收口)。
|
||||
this._currentFlowNode = Binder._unreachableStructuralFlowNode;
|
||||
|
||||
// Fallthrough nodes don't have any children.
|
||||
return false;
|
||||
}
|
||||
|
||||
override visitReturn(node: ReturnNode): boolean {
|
||||
if (this._targetFunctionDeclaration) {
|
||||
if (!this._targetFunctionDeclaration.returnStatements) {
|
||||
|
||||
@@ -302,7 +302,10 @@ class FlowChartBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/** 是否为终止语句(return / raise / break / continue)。它们位于 StatementList 内部。 */
|
||||
/**
|
||||
* 是否为终止语句(return / raise / break / continue / fallthrough)。
|
||||
* 它们位于 StatementList 内部。
|
||||
*/
|
||||
private _isTerminatorStatement(statement: StatementNode): boolean {
|
||||
if (statement.nodeType !== ParseNodeType.StatementList) {
|
||||
return false;
|
||||
@@ -316,7 +319,9 @@ class FlowChartBuilder {
|
||||
nodeType === ParseNodeType.Return ||
|
||||
nodeType === ParseNodeType.Raise ||
|
||||
nodeType === ParseNodeType.Break ||
|
||||
nodeType === ParseNodeType.Continue
|
||||
nodeType === ParseNodeType.Continue ||
|
||||
// Viper 扩展:match-case 内的 `fallthrough`(case 穿透)同样终止当前分支。
|
||||
nodeType === ParseNodeType.Fallthrough
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
EllipsisNode,
|
||||
ErrorNode,
|
||||
ExceptNode,
|
||||
FallthroughNode,
|
||||
ForNode,
|
||||
FormatStringNode,
|
||||
FunctionAnnotationNode,
|
||||
@@ -185,6 +186,9 @@ export function getChildNodes(node: ParseNode): (ParseNode | undefined)[] {
|
||||
case ParseNodeType.Except:
|
||||
return [node.d.typeExpr, node.d.name, node.d.exceptSuite];
|
||||
|
||||
case ParseNodeType.Fallthrough:
|
||||
return [];
|
||||
|
||||
case ParseNodeType.For:
|
||||
return [node.d.targetExpr, node.d.iterableExpr, node.d.forSuite, node.d.elseSuite];
|
||||
|
||||
@@ -441,6 +445,9 @@ export class ParseTreeVisitor<T> {
|
||||
case ParseNodeType.Except:
|
||||
return this.visitExcept(node);
|
||||
|
||||
case ParseNodeType.Fallthrough:
|
||||
return this.visitFallthrough(node);
|
||||
|
||||
case ParseNodeType.For:
|
||||
return this.visitFor(node);
|
||||
|
||||
@@ -711,6 +718,11 @@ export class ParseTreeVisitor<T> {
|
||||
return this._default;
|
||||
}
|
||||
|
||||
// Viper 扩展:match-case 内的裸 `fallthrough` 语句(case 穿透)。
|
||||
visitFallthrough(node: FallthroughNode) {
|
||||
return this._default;
|
||||
}
|
||||
|
||||
visitFor(node: ForNode) {
|
||||
return this._default;
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@ namespace Keywords {
|
||||
'const',
|
||||
'volatile',
|
||||
'typedef',
|
||||
'fallthrough',
|
||||
'inline',
|
||||
'export',
|
||||
'extern',
|
||||
|
||||
@@ -37,6 +37,7 @@ export const ParseNodeTypeMap: ParseNodeTypeMapType = {
|
||||
DictionaryExpandEntry: ParseNodeType.DictionaryExpandEntry,
|
||||
DictionaryKeyEntry: ParseNodeType.DictionaryKeyEntry,
|
||||
Ellipsis: ParseNodeType.Ellipsis,
|
||||
Fallthrough: ParseNodeType.Fallthrough,
|
||||
If: ParseNodeType.If,
|
||||
Import: ParseNodeType.Import,
|
||||
ImportAs: ParseNodeType.ImportAs,
|
||||
|
||||
@@ -49,6 +49,7 @@ export const enum ParseNodeType {
|
||||
|
||||
DictionaryKeyEntry, // 20
|
||||
Ellipsis,
|
||||
Fallthrough,
|
||||
If,
|
||||
Import,
|
||||
ImportAs,
|
||||
@@ -2395,6 +2396,30 @@ export namespace ContinueNode {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Viper 扩展:`fallthrough` 语句(case 穿透)。
|
||||
* 与 `break` 同属「终止当前 match 分支」的控制流语句,区别是去向为**下一个 case 体**
|
||||
* 而不是 match 之后。是软关键字:仅在 match-case 体内、且独占一条简单语句时才是本节点,
|
||||
* 其它位置仍是普通标识符(见 parser._isAtViperFallthroughStatement)。
|
||||
*/
|
||||
export interface FallthroughNode extends ParseNodeBase<ParseNodeType.Fallthrough> {}
|
||||
|
||||
export namespace FallthroughNode {
|
||||
export function create(range: TextRange) {
|
||||
const node: FallthroughNode = {
|
||||
start: range.start,
|
||||
length: range.length,
|
||||
nodeType: ParseNodeType.Fallthrough,
|
||||
id: _nextNodeId++,
|
||||
parent: undefined,
|
||||
a: undefined,
|
||||
d: {},
|
||||
};
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ReturnNode extends ParseNodeBase<ParseNodeType.Return> {
|
||||
d: {
|
||||
expr?: ExpressionNode | undefined;
|
||||
@@ -2873,6 +2898,7 @@ export type ParseNode =
|
||||
| DictionaryExpandEntryNode
|
||||
| DictionaryKeyEntryNode
|
||||
| EllipsisNode
|
||||
| FallthroughNode
|
||||
| IfNode
|
||||
| ImportNode
|
||||
| ImportAsNode
|
||||
|
||||
@@ -68,6 +68,7 @@ import {
|
||||
ErrorNode,
|
||||
ExceptNode,
|
||||
ExpressionNode,
|
||||
FallthroughNode,
|
||||
ForNode,
|
||||
FormatStringNode,
|
||||
FunctionAnnotationNode,
|
||||
@@ -244,6 +245,10 @@ export class Parser {
|
||||
private _parseOptions: ParseOptions = new ParseOptions();
|
||||
private _diagSink: DiagnosticSink = new DiagnosticSink();
|
||||
private _isInLoop = false;
|
||||
// Viper 扩展:正在解析 match 的某个 case 体。与 _isInLoop 一样是"可 break 作用域"
|
||||
// 的一种:match 内的裸 break 表示结束整个 match(对齐编译器语义),故不再报
|
||||
// breakOutsideLoop;fallthrough 也仅在此作用域内才是软关键字语句。
|
||||
private _isInMatchCase = false;
|
||||
private _isInFunction = false;
|
||||
private _isInExceptionGroup = false;
|
||||
private _isParsingTypeAnnotation = false;
|
||||
@@ -988,7 +993,13 @@ export class Parser {
|
||||
guardExpression = this._parseTestExpression(/* allowAssignmentExpression */ true);
|
||||
}
|
||||
|
||||
// Viper 扩展:case 体是「可 break 作用域」——裸 break 结束整个 match,
|
||||
// fallthrough 收窄为软关键字语句;离开 case 体后恢复(与循环的 _isInLoop 同法)。
|
||||
const wasInMatchCase = this._isInMatchCase;
|
||||
this._isInMatchCase = true;
|
||||
const suite = this._parseSuite(this._isInFunction);
|
||||
this._isInMatchCase = wasInMatchCase;
|
||||
|
||||
return CaseNode.create(caseToken, casePattern, this._isPatternIrrefutable(casePattern), guardExpression, suite);
|
||||
}
|
||||
|
||||
@@ -2933,7 +2944,9 @@ export class Parser {
|
||||
private _parseBreakStatement(): BreakNode {
|
||||
const breakToken = this._getKeywordToken(KeywordType.Break);
|
||||
|
||||
if (!this._isInLoop) {
|
||||
// Viper 扩展:match-case 内的裸 break 表示结束整个 match(对齐编译器语义),
|
||||
// 与循环内的 break 一样属于合法「可跳出作用域」,不再报 breakOutsideLoop。
|
||||
if (!this._isInLoop && !this._isInMatchCase) {
|
||||
this._addSyntaxError(LocMessage.breakOutsideLoop(), breakToken);
|
||||
} else if (this._isInExceptionGroup) {
|
||||
this._addSyntaxError(LocMessage.breakInExceptionGroup(), breakToken);
|
||||
@@ -3460,6 +3473,11 @@ export class Parser {
|
||||
return this._parseViperTypedefStatement();
|
||||
}
|
||||
|
||||
// Viper 扩展:match-case 内的裸 `fallthrough`(case 穿透)软关键字语句。
|
||||
if (this._isAtViperFallthroughStatement()) {
|
||||
return this._parseViperFallthroughStatement();
|
||||
}
|
||||
|
||||
switch (this._peekKeywordType()) {
|
||||
case KeywordType.Pass:
|
||||
return this._parsePassStatement();
|
||||
@@ -3611,6 +3629,32 @@ export class Parser {
|
||||
return AssignmentNode.create(leftExpr, rightExpr);
|
||||
}
|
||||
|
||||
// Viper 扩展:判定语句起始处是否为 match-case 体内的裸 `fallthrough`。
|
||||
// `fallthrough` 是软关键字,三个条件同时满足才算语句:
|
||||
// ① 方言为 viper;② 正处于某个 case 体内(_isInMatchCase);
|
||||
// ③ 冒号后独占一条简单语句 —— 后跟行尾 / `;` / 文件尾。
|
||||
// 不满足时保持普通标识符解析(`fallthrough = 1` 是赋值、`fallthrough()` 是调用、
|
||||
// `x.fallthrough` 是成员访问;case 外的裸 `fallthrough` 仍按未定义名报错)。
|
||||
private _isAtViperFallthroughStatement(): boolean {
|
||||
if (this._parseOptions.dialect !== 'viper' || !this._isInMatchCase) {
|
||||
return false;
|
||||
}
|
||||
const keywordToken = this._peekToken();
|
||||
if (keywordToken.type !== TokenType.Identifier || (keywordToken as IdentifierToken).value !== 'fallthrough') {
|
||||
return false;
|
||||
}
|
||||
const nextType = this._peekToken(1).type;
|
||||
return nextType === TokenType.NewLine || nextType === TokenType.Semicolon || nextType === TokenType.EndOfStream;
|
||||
}
|
||||
|
||||
// Viper 扩展:`fallthrough` 语句(case 穿透)。节点只承载位置,
|
||||
// 「穿透到下一个 case」的语义由编译器(TransPyC/TransPyV)承担。
|
||||
private _parseViperFallthroughStatement(): FallthroughNode {
|
||||
const fallthroughToken = this._getNextToken();
|
||||
assert(fallthroughToken.type === TokenType.Identifier);
|
||||
return FallthroughNode.create(fallthroughToken);
|
||||
}
|
||||
|
||||
private _makeExpressionOrTuple(
|
||||
exprListResult: ListResult<ExpressionNode>,
|
||||
enclosedInParens: boolean
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* viperMatchBreakFallthrough.test.ts
|
||||
*
|
||||
* 验证 match-case 内支持**裸 `break`** 与**裸 `fallthrough`**:
|
||||
* - `break` → 结束整个 match(对齐编译器语义,不再报 breakOutsideLoop);
|
||||
* - `fallthrough` → case 穿透(软关键字语句)。
|
||||
*
|
||||
* 背景:TPC/TransPyV 正在支持这两个裸关键字(此前只有 `c.Break()` / `c.NoBreak()`),
|
||||
* LSP 侧原先会把 match 内的裸 break 报成 breakOutsideLoop、把裸 fallthrough 当成未定义标识符。
|
||||
*
|
||||
* 实现要点(仅 LSP):
|
||||
* ① parser 新增 `_isInMatchCase`,在 `_parseCaseStatement` 解析 case body 期间置位
|
||||
* (与循环的 `_isInLoop` 同法保存/恢复);
|
||||
* ② `_parseBreakStatement` 判据放宽为 `!_isInLoop && !_isInMatchCase` 才报错;
|
||||
* ③ 新增 `ParseNodeType.Fallthrough` / `FallthroughNode`,parser 里以**软关键字**识别:
|
||||
* 仅当①方言为 viper、②处于 case 体内、③后跟行尾/`;`/文件尾 时才是该语句,
|
||||
* 其它位置仍是普通标识符(`fallthrough = 1` / `fallthrough()` / `x.fallthrough`)。
|
||||
* ④ binder 把 fallthrough 当作流程终止(其后同 case 体语句不可达);
|
||||
* flowChartBuilder 把它并入终止语句。
|
||||
*
|
||||
* 运行:npx jest src/tests/viperMatchBreakFallthrough.test.ts --runInBand
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { Program } from '../analyzer/program';
|
||||
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
|
||||
import { NullConsole } from '../common/console';
|
||||
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
|
||||
import { createServiceProvider } from '../common/serviceProviderExtensions';
|
||||
import { UriEx } from '../common/uri/uriUtils';
|
||||
import { FullAccessHost } from '../common/fullAccessHost';
|
||||
|
||||
(global as any).__rootDirectory = path.resolve();
|
||||
|
||||
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
|
||||
|
||||
function buildProgramForCode(code: string, dirName: string) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[projectRoot],
|
||||
/* skipNativeLibraries */ false,
|
||||
/* defaultDialect */ 'viper'
|
||||
);
|
||||
configOptions.executionEnvironments = [execEnv];
|
||||
|
||||
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
|
||||
const program = new Program(importResolver, configOptions, serviceProvider);
|
||||
|
||||
const fs = require('fs');
|
||||
const testDir = path.join(__dirname, dirName);
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'main.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
const uri = UriEx.file(testFile);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
return { program, configOptions, uri, serviceProvider, testDir };
|
||||
}
|
||||
|
||||
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any) {
|
||||
const sourceFile = program.getSourceFile(uri);
|
||||
assert(sourceFile, 'source file not found');
|
||||
return (sourceFile.getDiagnostics(configOptions) || []).map((diag: any) => ({
|
||||
category: diag.category,
|
||||
line: diag.range ? diag.range.start.line + 1 : -1,
|
||||
message: diag.message as string,
|
||||
}));
|
||||
}
|
||||
|
||||
/** 实际报错(category 0=Error / 1=Warning)所在行集合。 */
|
||||
function hardErrorLines(program: Program, configOptions: ConfigOptions, uri: any): number[] {
|
||||
return Array.from(
|
||||
new Set(
|
||||
getDiagnostics(program, configOptions, uri)
|
||||
.filter((d) => d.category === 0 || d.category === 1)
|
||||
.map((d) => d.line)
|
||||
)
|
||||
).sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
/** match-case 内裸 break(结束 match)+ 裸 fallthrough(穿透到下一个 case)。 */
|
||||
const MATCH_CODE = `import t
|
||||
from stdint import *
|
||||
|
||||
def pick(x: int) -> int:
|
||||
total: int = 0
|
||||
match x:
|
||||
case 0:
|
||||
total = 1
|
||||
break
|
||||
case 1:
|
||||
total = 2
|
||||
fallthrough
|
||||
case 2:
|
||||
total = 3
|
||||
break
|
||||
case _:
|
||||
total = 4
|
||||
return total
|
||||
`;
|
||||
|
||||
/** 反例:case 外的裸 break / 裸 fallthrough 仍应按原有规则报错。 */
|
||||
const NEGATIVE_CODE = `import t
|
||||
from stdint import *
|
||||
|
||||
def f() -> int:
|
||||
break
|
||||
return 0
|
||||
|
||||
def g() -> int:
|
||||
fallthrough
|
||||
return 0
|
||||
`;
|
||||
|
||||
test('match-case 内裸 break 与 fallthrough 均不报错', () => {
|
||||
const { program, configOptions, uri, testDir, serviceProvider } = buildProgramForCode(
|
||||
MATCH_CODE,
|
||||
'__viper_match_break_fallthrough__'
|
||||
);
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== match break/fallthrough 诊断 ===');
|
||||
diagnostics
|
||||
.filter((d) => d.category === 0 || d.category === 1)
|
||||
.forEach((d) => console.log(` L${d.line} [cat=${d.category}] ${d.message}`));
|
||||
|
||||
const lines = hardErrorLines(program, configOptions, uri);
|
||||
console.log('=== 报错行 ===');
|
||||
lines.forEach((l) => console.log(` L${l}`));
|
||||
assert.deepStrictEqual(
|
||||
lines,
|
||||
[],
|
||||
`case 体内的 break(结束 match)与 fallthrough(case 穿透)都不应报错:${JSON.stringify(
|
||||
diagnostics.filter((d) => d.category === 0 || d.category === 1)
|
||||
)}`
|
||||
);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('case 外的裸 break 报 breakOutsideLoop、裸 fallthrough 仍是未定义标识符', () => {
|
||||
const { program, configOptions, uri, testDir, serviceProvider } = buildProgramForCode(
|
||||
NEGATIVE_CODE,
|
||||
'__viper_match_break_fallthrough_neg__'
|
||||
);
|
||||
|
||||
try {
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== case 外 break/fallthrough 诊断 ===');
|
||||
diagnostics
|
||||
.filter((d) => d.category === 0 || d.category === 1)
|
||||
.forEach((d) => console.log(` L${d.line} [cat=${d.category}] ${d.message}`));
|
||||
|
||||
// L5:循环/match 之外的 break 仍报 breakOutsideLoop;
|
||||
// L9:case 之外的 fallthrough 不是软关键字语句,按未定义标识符报错。
|
||||
const lines = hardErrorLines(program, configOptions, uri);
|
||||
console.log('=== 报错行 ===');
|
||||
lines.forEach((l) => console.log(` L${l}`));
|
||||
assert.deepStrictEqual(
|
||||
lines,
|
||||
[5, 9],
|
||||
`case 外的 break(L5)与 fallthrough(L9)都应报错:${JSON.stringify(
|
||||
diagnostics.filter((d) => d.category === 0 || d.category === 1)
|
||||
)}`
|
||||
);
|
||||
} finally {
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
require('fs').rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
## 1.1.427
|
||||
|
||||
- **Bare `break` and `fallthrough` inside a `match` statement are now accepted** (LSP side only; TransPyC/TransPyV are being taught the same syntax separately). A `break` in a `case` body ends the whole match, so it is no longer reported as `breakOutsideLoop`: the parser tracks a second "breakable scope", `_isInMatchCase`, set while a `case` suite is parsed and restored afterwards, exactly like the existing loop flag (`continue` is untouched). `fallthrough` (case fall-through) becomes a real statement node (`ParseNodeType.Fallthrough` / `FallthroughNode`) but stays a **soft keyword** — it is that statement only when the dialect is Viper, the parser is inside a case body, and it forms a complete simple statement (followed by a newline, `;` or end of file). Everywhere else — `fallthrough = 1`, `fallthrough()`, `x.fallthrough`, or a bare `fallthrough` outside a `match` — it remains an ordinary identifier, so a stray one outside a case is still reported as undefined. The binder treats the node as a flow terminator (statements after it in the same case are unreachable), the flow chart lists it among the terminator statements, completion offers it, and the TextMate grammar highlights it as `keyword.control.flow.viper`
|
||||
- **Auto-discovered compiler `includes`**: when a project does not declare `includes` in `project.vpj` / `project.json`, the extension now falls back to locating the compiler's own standard-library includes automatically. The baseline is the **directory of the currently selected compiler executable** (not the `.vp` file being analyzed): the client walks up at most 4 levels from the executable's directory and, at each level, looks only at the *immediate* subdirectories `includes` / `include` (no recursion), keeping the ones that actually exist. The result is appended to `viper.analysis.extraPaths` in the existing `workspace/configuration` middleware, so the server adds them to the import search roots *after* the project-declared `includes` — e.g. a `TransPyV.exe` under `TransPyC/exe` makes `TransPyC/includes` importable, mirroring `project.vpj`'s `"includes": ["../includes"]`. Non-Viper toolchains are unaffected (a nonexistent executable directory yields no candidates). Switching compilers re-sends `workspace/didChangeConfiguration`, so the new roots take effect without reloading the window
|
||||
- `__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
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"patterns": [
|
||||
{
|
||||
"name": "keyword.control.flow.viper",
|
||||
"match": "\\b(if|elif|else|while|for|in|return|break|continue|pass|raise|try|except|finally|with|as|import|from|yield|async|await|del|assert|lambda|match|case|define)\\b"
|
||||
"match": "\\b(if|elif|else|while|for|in|return|break|continue|fallthrough|pass|raise|try|except|finally|with|as|import|from|yield|async|await|del|assert|lambda|match|case|define)\\b"
|
||||
},
|
||||
{
|
||||
"name": "storage.type.function.viper",
|
||||
|
||||
Reference in new issue
Block a user