feat(vipeye-internal): 修饰关键字收窄为 inline/export/extern,单独使用报错;修复修饰函数自动缩进

state 不再作为关键字(恢复普通标识符,hashlib 的 state: 字段与 self.state 不受影响)。inline/export/extern 在语句起始且未与 def 搭配时报 '修饰关键字必须与 def 搭配使用'(新增 KeywordType.ViperModifier 与 LocMessage);后跟 ':' 的注解用法仍按标识符。tmLanguage 高亮与 language-configuration 的 onEnterRules/increaseIndentPattern 同步(修饰前缀函数回车自动缩进)。
This commit is contained in:
TermiNexus committed 2026-08-08 17:15:10 +08:00
1 parent 9c816ec5b9
commit e5aacbfd0b
9 files changed
+141 -15

No files matched your search

@@ -1178,6 +1178,8 @@ export namespace Localizer {
export const unexpectedExprToken = () => getRawString('Diagnostic.unexpectedExprToken');
export const unexpectedIndent = () => getRawString('Diagnostic.unexpectedIndent');
export const unexpectedUnindent = () => getRawString('Diagnostic.unexpectedUnindent');
export const viperModifierRequiresDef = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperModifierRequiresDef'));
export const unhashableDictKey = () => getRawString('Diagnostic.unhashableDictKey');
export const unhashableSetEntry = () => getRawString('Diagnostic.unhashableSetEntry');
export const unionForwardReferenceNotAllowed = () => getRawString('Diagnostic.unionForwardReferenceNotAllowed');
@@ -1618,6 +1618,7 @@
"unexpectedExprToken": "Unexpected token at end of expression",
"unexpectedIndent": "Unexpected indentation",
"unexpectedUnindent": "Unindent not expected",
"viperModifierRequiresDef": "The modifier \"{name}\" must be used with 'def'",
"unhashableDictKey": "Dictionary key must be hashable",
"unhashableSetEntry": {
"message": "Set entry must be hashable",
@@ -616,6 +616,7 @@
"unexpectedExprToken": "表达式末尾出现意外标记",
"unexpectedIndent": "意外缩进",
"unexpectedUnindent": "不应取消缩进",
"viperModifierRequiresDef": "修饰关键字 \"{name}\" 必须与 def 搭配使用",
"unhashableDictKey": "字典密钥必须可哈希",
"unhashableSetEntry": "Set 条目必须是可哈希的",
"uninitializedAbstractVariables": "抽象基类中定义的变量未在 final 类 \"{classType}\" 中初始化",
@@ -429,6 +429,30 @@ export class Parser {
}
switch (this._peekKeywordType()) {
case KeywordType.ViperModifier: {
// Viper 扩展:inline/export/extern 必须与 def 搭配使用。
// 与 def 搭配时跳过修饰关键字并正常解析函数;单独使用时报错。
const modifierToken = this._peekToken() as KeywordToken;
if (
this._peekToken(1).type === TokenType.Keyword &&
(this._peekToken(1) as KeywordToken).keywordType === KeywordType.Def
) {
this._getNextToken();
return this._parseFunctionDef();
}
this._addSyntaxError(
LocMessage.viperModifierRequiresDef().format({
name: this._fileContents!.slice(
modifierToken.start,
modifierToken.start + modifierToken.length
),
}),
modifierToken
);
this._getNextToken();
return this._parseSimpleStatement();
}
case KeywordType.If:
return this._parseIfStatement();
@@ -643,22 +643,35 @@ enum MagicsKind {
// Viper 扩展:C 风格修饰/声明关键字处理。
// Viper 支持将 C 修饰/声明语义写成语句前缀关键字:
// inline/export/extern/state def f(...) —— 等价 t.CInline / t.CExport / t.CExtern / t.State
// inline/export/extern def f(...) —— 修饰关键字,必须与 def 搭配
// enum/rnum class|struct Foo —— 等价 t.CEnum / t.REnum
// enum Foo: / rnum Foo: —— 单独使用(等价 class Foo:)
// struct Foo: —— 独立的 struct 关键字(等价 class)
// 返回 'skip':跳过该关键字 token(后续 def/class 正常解析,位置与缩进不变);
// 返回 'class':把 enum/rnum/struct 当作 class 关键字;undefined:按普通标识符处理
// (变量名、成员名、导入名等,如 `import struct as s` 不受影响)。
// 返回 'class':把 enum/rnum/struct 当作 class 关键字;
// 返回 'viperModifier':inline/export/extern 单独使用(未与 def 搭配),由 parser 报错;
// undefined:按普通标识符处理(变量名、成员名、导入名、`state:` 注解等)。
function getViperStatementKeywordAction(
text: string,
identifierStart: number,
identifierEnd: number,
name: string
): 'skip' | 'class' | undefined {
): 'skip' | 'class' | 'viperModifier' | undefined {
const nextWord = peekViperKeywordWord(text, identifierEnd);
if (name === 'inline' || name === 'export' || name === 'extern' || name === 'state') {
return nextWord === 'def' ? 'skip' : undefined;
if (name === 'inline' || name === 'export' || name === 'extern') {
// 修饰关键字仅在语句起始处生效,避免误伤 `self.inline` 等成员名。
if (!isViperAtStatementStart(text, identifierStart)) {
return undefined;
}
if (nextWord === 'def') {
return 'skip';
}
// 后跟 ':'(注解,如 `state: t.CArray[...]` 风格的字段声明)按普通标识符处理。
if (isViperNextCharColon(text, identifierEnd)) {
return undefined;
}
// 单独使用(未与 def 搭配)→ 关键字 token,由 parser 报错。
return 'viperModifier';
}
if (name === 'enum' || name === 'rnum') {
if (nextWord === 'class' || nextWord === 'struct') {
@@ -695,6 +708,15 @@ function peekViperKeywordWord(text: string, from: number): string {
return text.slice(start, pos);
}
function isViperNextCharColon(text: string, from: number): boolean {
let pos = from;
const len = text.length;
while (pos < len && (text[pos] === ' ' || text[pos] === '\t')) {
pos++;
}
return pos < len && text[pos] === ':';
}
function isViperAtStatementStart(text: string, identifierStart: number): boolean {
let pos = identifierStart;
while (pos > 0 && (text[pos - 1] === ' ' || text[pos - 1] === '\t')) {
@@ -1446,9 +1468,11 @@ export class Tokenizer {
} else {
const value = this._internIdentifier(text, start, end, length);
// Viper 扩展:C 风格修饰/声明关键字。
// inline/export/extern/state 作为 def 前缀修饰、enum/rnum 作为 class/struct
// inline/export/extern 作为 def 前缀修饰、enum/rnum 作为 class/struct
// 前缀时跳过该 token(保留原始位置与缩进;着色由 TextMate 规则处理);
// struct 在语句起始且后跟类名时按 class 关键字处理。
// enum/rnum/struct 在语句起始且后跟类名时按 class 关键字处理;
// inline/export/extern 单独使用(未与 def 搭配)时生成修饰关键字 token,
// 由 parser 报错。
const viperAction = getViperStatementKeywordAction(text, start, end, value);
if (viperAction === 'skip') {
return true;
@@ -1457,6 +1481,12 @@ export class Tokenizer {
this._tokens.push(KeywordToken.create(start, length, KeywordType.Class, this._getComments()));
return true;
}
if (viperAction === 'viperModifier') {
this._tokens.push(
KeywordToken.create(start, length, KeywordType.ViperModifier, this._getComments())
);
return true;
}
this._tokens.push(IdentifierToken.create(start, length, value, this._getComments()));
}
return true;
@@ -150,6 +150,8 @@ export const enum KeywordType {
While,
With,
Yield,
// Viper 扩展:inline/export/extern 等 C 风格修饰关键字(必须与 def 搭配)。
ViperModifier,
}
export const softKeywords = [
@@ -332,7 +332,7 @@ struct IntPair:
a: t.CInt
b: t.CInt
# 修饰关键字 + def(等价 t.CInline / t.CExport / t.CExtern / t.State)
# 修饰关键字 + def(等价 t.CInline / t.CExport / t.CExtern)
inline def double_inline(x: t.CInt) -> t.CInt:
return x * 2
@@ -342,8 +342,8 @@ export def double_export(x: t.CInt) -> t.CInt:
extern def double_extern(x: t.CInt) -> t.CInt:
return x * 2
state def double_state(x: t.CInt) -> t.CInt:
return x * 2
# state 不是关键字,仍可作字段名/成员名
state: t.CInt = 0
# enum / rnum + class(等价 t.CEnum / t.REnum)
enum class Color:
@@ -405,4 +405,70 @@ def test_usage():
fs.rmSync(testDir, { recursive: true, force: true });
}
serviceProvider.dispose();
});
test('Viper 修饰关键字 inline/export/extern 单独使用报错', () => {
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 code = `import t
inline
export = 5
extern + 1
inline def ok(x: t.CInt) -> t.CInt:
return x
`;
const testDir = path.join(__dirname, '__viper_modifier_test__');
const fs = require('fs');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'modifier.vp');
fs.writeFileSync(testFile, code, 'utf8');
try {
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const uri = UriEx.file(testFile);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环
}
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== modifier.vp diagnostics ===');
diagnostics.forEach((d) => console.log(` ${d}`));
// 期望:3 个"必须与 def 搭配"错误(inline / export / extern 单独使用),
// 而 `inline def ok(...)` 不报错。
const modifierErrors = diagnostics.filter(
(d) => d.includes('必须与 def 搭配') || d.includes('must be used with')
);
assert.strictEqual(
modifierErrors.length,
3,
`expected 3 modifier errors, got: ${JSON.stringify(diagnostics)}`
);
program.dispose();
} finally {
fs.rmSync(testDir, { recursive: true, force: true });
}
serviceProvider.dispose();
});
@@ -28,7 +28,7 @@
],
"onEnterRules": [
{
"beforeText": "^\\s*(?:def|class|for|if|elif|else|while|try|finally|with|except|async).*?:\\s*$",
"beforeText": "^\\s*(?:(?:inline|export|extern|enum|rnum)\\s+)?(?:def|class|for|if|elif|else|while|try|finally|with|except|async|struct)\\b.*?:\\s*$",
"action": { "indent": "indent" }
},
{
@@ -45,7 +45,7 @@
},
"wordPattern": "(?x)\n \\b\\w*\\b \n | (?<=\\.)\\w+\n | (?<=@)\\w+\n | \\p{Lu}[\\p{Ll}_\\p{Lu}\\p{Nd}]*\n | [[:alpha:]_][[:alpha:]_\\p{Nd}]*\n | \\p{Lu}[\\p{Ll}_\\p{Lu}\\p{Nd}]*f?\\b",
"indentationRules": {
"increaseIndentPattern": "^\\s*(class|def|elif|else|except|finally|for|if|try|while|with|async\\s+def|async\\s+for|async\\s+with)\\b.*:\\s*$",
"decreaseIndentPattern": "^\\s*(elif|else|except|finally)\\b.*:\\s*$"
"increaseIndentPattern": "^\\s*(?:(?:inline|export|extern|enum|rnum)\\s+)?(?:class|def|elif|else|except|finally|for|if|try|while|with|struct|async\\s+def|async\\s+for|async\\s+with)\\b.*?:\\s*$",
"decreaseIndentPattern": "^\\s*(elif|else|except|finally)\\b.*?:\\s*$"
}
}
@@ -43,7 +43,7 @@
},
{
"name": "storage.modifier.viper",
"match": "\\b(inline|export|extern|state)\\b(?=\\s+def\\b)"
"match": "\\b(inline|export|extern)\\b(?=\\s+def\\b|[ \\t]*$)"
},
{
"name": "storage.type.class.viper",