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:
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 &&
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user