feat(vipeye-internal): extern/export 支持直接声明形式 extern A(...)

- extern/export 后跟标识符时按函数声明解析(等价 extern def A),
  def 形式保留(extern def A / export def A 均可用)
- extern/export 声明均可省略函数体(无冒号无 pass)
- checker 对 extern/export 声明跳过缺失返回值检查
- TextMate 高亮与缩进规则支持 extern/export 直接声明
- viperGenericTypeVarCall 11/11 测试通过
This commit is contained in:
TermiNexus committed 2026-08-17 10:35:46 +08:00
1 parent 41bc302916
commit ac08b01baa
5 files changed
+106 -12

No files matched your search

@@ -4830,11 +4830,11 @@ export class Checker extends ParseTreeWalker {
declaredReturnType !== undefined &&
isPureCModifierType(declaredReturnType);
// Viper 扩展:extern 关键字声明的函数仅声明原型(函数体必须为 pass/None),
// 不要求返回语句与声明类型匹配。
// Viper 扩展:extern/export 关键字声明的函数仅声明原型(extern 函数体必须
// 为 pass/None),不要求返回语句与声明类型匹配。
const isViperExternDecl =
IsViperDialect(this._fileInfo.executionEnvironment.dialect) &&
node.d.viperModifier === 'extern';
(node.d.viperModifier === 'extern' || node.d.viperModifier === 'export');
if (
declaredReturnType &&
+30 -6
View File
@@ -430,8 +430,11 @@ export class Parser {
switch (this._peekKeywordType()) {
case KeywordType.ViperModifier: {
// Viper 扩展:inline/export/extern 必须与 def(或其别名)搭配使用。
// 与 def 搭配时跳过修饰关键字并正常解析函数;单独使用时报错。
// Viper 扩展:inline/export/extern 修饰关键字。
// 与 def(或其别名)搭配时跳过修饰关键字并正常解析函数;
// extern/export 还支持直接声明形式:`extern A(...)`(等价 `extern def A(...)`),
// 此时以下一个标识符作为函数名解析函数定义;
// 其余单独使用时报错。
const modifierToken = this._peekToken() as KeywordToken;
const modifierText = this._fileContents!.slice(
modifierToken.start,
@@ -444,6 +447,21 @@ export class Parser {
this._getNextToken();
return this._parseFunctionDef(/* asyncToken */ undefined, /* decorators */ undefined, modifierText);
}
if (
(modifierText === 'extern' || modifierText === 'export') &&
this._peekToken(1).type === TokenType.Identifier
) {
// extern/export 直接声明形式:`extern A(...)`。
// 消费修饰关键字后,下一个标识符即函数名;defToken 用修饰关键字
// 作为范围起点(diagnostic 定位)。
this._getNextToken();
return this._parseFunctionDef(
/* asyncToken */ undefined,
/* decorators */ undefined,
modifierText,
modifierToken
);
}
this._addSyntaxError(
LocMessage.viperModifierRequiresDef().format({ name: modifierText }),
modifierToken
@@ -2052,9 +2070,12 @@ export class Parser {
private _parseFunctionDef(
asyncToken?: KeywordToken,
decorators?: DecoratorNode[],
viperModifier?: string
viperModifier?: string,
defTokenOverride?: Token
): FunctionNode | ErrorNode {
const defToken = this._getKeywordToken(KeywordType.Def);
// Viper 扩展:extern/export 直接声明形式(`extern A(...)`)没有 def 关键字,
// 用修饰关键字 token 作为范围起点;否则正常消费 Def 关键字 token。
const defToken = defTokenOverride ?? this._getKeywordToken(KeywordType.Def);
const nameToken = this._getTokenIfIdentifier();
if (!nameToken) {
@@ -2113,8 +2134,11 @@ export class Parser {
let suite: SuiteNode;
const possibleSuiteStart = this._peekToken();
if (viperModifier === 'extern' && possibleSuiteStart.type !== TokenType.Colon) {
// Viper extern 声明允许省略函数体(`extern def f(...) -> T`,无冒号、无 pass)。
if (
(viperModifier === 'extern' || viperModifier === 'export') &&
possibleSuiteStart.type !== TokenType.Colon
) {
// Viper extern/export 声明允许省略函数体(`extern def f(...) -> T` 无冒号、无 pass)。
// 创建空 suite 并消费行尾换行;`: pass` 方式仍走 _parseSuite 正常解析。
suite = SuiteNode.create(possibleSuiteStart);
if (this._consumeTokenIfType(TokenType.NewLine)) {
@@ -806,3 +806,73 @@ def test_len_any():
}
serviceProvider.dispose();
});
test('extern/export 支持直接声明形式 extern A(...)(无 def)', () => {
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
# extern/export 直接声明形式(无 def)
extern E1(x: t.CInt) -> t.CInt
extern E2(x: t.CInt) -> t.CInt: pass
export X1(x: t.CInt) -> t.CInt
export X2(x: t.CInt) -> t.CInt: pass
# def 形式保留
extern def E3(x: t.CInt) -> t.CInt
export def X3(x: t.CInt) -> t.CInt
# extern + fn 别名(无函数体)仍可用
extern fn E4(x: t.CInt) -> t.CInt
def use():
a: t.CInt = E1(1) + X1(2) + E3(3) + X3(4)
`;
const testDir = path.join(__dirname, '__viper_extern_direct_test__');
const fs = require('fs');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'extern_direct.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('=== extern_direct.vp diagnostics ===');
diagnostics.forEach((d) => console.log(` ${d}`));
// 期望没有任何错误级诊断:extern/export 直接声明形式与 def 形式均可。
const errors = diagnostics.filter((d) => d.startsWith('[category=0]'));
assert.strictEqual(
errors.length,
0,
`unexpected error diagnostics: ${JSON.stringify(diagnostics)}`
);
program.dispose();
} finally {
fs.rmSync(testDir, { recursive: true, force: true });
}
serviceProvider.dispose();
});
@@ -28,7 +28,7 @@
],
"onEnterRules": [
{
"beforeText": "^\\s*(?:(?:inline|export|extern|enum|rnum|union)\\s+)?(?:def|fn|fun|func|function|class|for|if|elif|else|while|try|finally|with|except|async|struct)\\b.*?:\\s*$",
"beforeText": "^\\s*(?:(?:inline|export|extern)\\s+(?:def|fn|fun|func|function)|(?:export|extern)\\s+[A-Za-z_][A-Za-z0-9_]*|(?:enum|rnum|union)\\s+(?:class|struct)|(?:enum|rnum|union)\\s+[A-Za-z_][A-Za-z0-9_]*|(?:def|fn|fun|func|function|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*(?:(?:inline|export|extern|enum|rnum|union)\\s+)?(?:class|def|fn|fun|func|function|elif|else|except|finally|for|if|try|while|with|struct|async\\s+def|async\\s+for|async\\s+with)\\b.*?:\\s*$",
"increaseIndentPattern": "^\\s*(?:(?:inline|export|extern)\\s+(?:def|fn|fun|func|function)|(?:export|extern)\\s+[A-Za-z_][A-Za-z0-9_]*|(?:enum|rnum|union)\\s+(?:class|struct)|(?:enum|rnum|union)\\s+[A-Za-z_][A-Za-z0-9_]*|(?:class|def|fn|fun|func|function|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*$"
}
}
@@ -47,7 +47,7 @@
},
{
"name": "storage.modifier.viper",
"match": "\\b(inline|export|extern)\\b(?=\\s+(?:def|fn|fun|func|function)\\b|[ \\t]*$)"
"match": "\\b(inline|export|extern)\\b(?=\\s+(?:def|fn|fun|func|function)\\b|\\s+[A-Za-z_][A-Za-z0-9_]*|[ \\t]*$)"
},
{
"name": "storage.type.class.viper",