feat(vipeye-internal): extern 函数体必须为 pass/None;fn/fun/func/function 作为 def 别名

extern 关键字声明的函数(FunctionNode 记录 viperModifier)函数体只允许 pass 或 None,否则报错;同时 extern 声明函数跳过缺失返回值的检查。fn/fun/func/function 在语句起始(或紧跟修饰关键字)且后跟函数名时按 def 处理。tmLanguage 与 language-configuration 同步支持 def 别名高亮与自动缩进。
This commit is contained in:
TermiNexus committed 2026-08-08 17:29:21 +08:00
1 parent e5aacbfd0b
commit 817bc47ee9
10 files changed
+203 -27

No files matched your search

@@ -36,6 +36,7 @@ import {
ClassNode,
ComprehensionIfNode,
ComprehensionNode,
ConstantNode,
DelNode,
DictionaryNode,
ErrorNode,
@@ -89,7 +90,7 @@ import {
} from '../parser/parseNodes';
import { ParserOutput } from '../parser/parser';
import { UnescapeError, UnescapeErrorType, getUnescapedString } from '../parser/stringTokenUtils';
import { OperatorType, StringTokenFlags, TokenType } from '../parser/tokenizerTypes';
import { KeywordType, OperatorType, StringTokenFlags, TokenType } from '../parser/tokenizerTypes';
import { AnalyzerFileInfo } from './analyzerFileInfo';
import * as AnalyzerNodeInfo from './analyzerNodeInfo';
import { ConstraintTracker } from './constraintTracker';
@@ -217,6 +218,26 @@ interface TypeVarUsageInfo {
// functions to be emitted.
const isPrintCodeComplexityEnabled = false;
// Viper 扩展:extern 函数体允许的语句——只允许 pass 或 None(仅声明原型)。
function isViperExternAllowedBody(stmt: ParseNode): boolean {
if (stmt.nodeType === ParseNodeType.Pass) {
return true;
}
if (stmt.nodeType === ParseNodeType.StatementList) {
const smallStatements = (stmt as StatementListNode).d.statements;
return smallStatements.every((s) => {
if (s.nodeType === ParseNodeType.Pass) {
return true;
}
if (s.nodeType === ParseNodeType.Constant) {
return (s as ConstantNode).d.constType === KeywordType.None;
}
return false;
});
}
return false;
}
export class Checker extends ParseTreeWalker {
private readonly _moduleNode: ModuleNode;
private readonly _fileInfo: AnalyzerFileInfo;
@@ -430,6 +451,22 @@ export class Checker extends ParseTreeWalker {
);
}
// Viper 扩展:extern 声明的函数体必须为 pass 或 None(仅声明原型,不定义实现)。
if (
node.d.viperModifier === 'extern' &&
IsViperDialect(this._fileInfo.executionEnvironment.dialect)
) {
const bodyStatements = node.d.suite.d.statements;
const invalidStatement = bodyStatements.find((stmt) => !isViperExternAllowedBody(stmt));
if (invalidStatement) {
this._evaluator.addDiagnostic(
DiagnosticRule.reportGeneralTypeIssues,
LocMessage.viperExternBodyInvalid().format({ name: node.d.name.d.value }),
invalidStatement
);
}
}
if (functionTypeResult) {
// Track whether we have seen a *args: P.args parameter. Named
// parameters after this need to be flagged as an error.
@@ -4793,7 +4830,19 @@ export class Checker extends ParseTreeWalker {
declaredReturnType !== undefined &&
isPureCModifierType(declaredReturnType);
if (declaredReturnType && !functionNeverReturns && implicitlyReturnsNone && !isPureModifierReturn) {
// Viper 扩展:extern 关键字声明的函数仅声明原型(函数体必须为 pass/None),
// 不要求返回语句与声明类型匹配。
const isViperExternDecl =
IsViperDialect(this._fileInfo.executionEnvironment.dialect) &&
node.d.viperModifier === 'extern';
if (
declaredReturnType &&
!functionNeverReturns &&
implicitlyReturnsNone &&
!isPureModifierReturn &&
!isViperExternDecl
) {
if (isNever(declaredReturnType)) {
// If the function consists entirely of "...", assume that it's
// an abstract method or a protocol method and don't require that
@@ -1180,6 +1180,8 @@ export namespace Localizer {
export const unexpectedUnindent = () => getRawString('Diagnostic.unexpectedUnindent');
export const viperModifierRequiresDef = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperModifierRequiresDef'));
export const viperExternBodyInvalid = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.viperExternBodyInvalid'));
export const unhashableDictKey = () => getRawString('Diagnostic.unhashableDictKey');
export const unhashableSetEntry = () => getRawString('Diagnostic.unhashableSetEntry');
export const unionForwardReferenceNotAllowed = () => getRawString('Diagnostic.unionForwardReferenceNotAllowed');
@@ -1619,6 +1619,7 @@
"unexpectedIndent": "Unexpected indentation",
"unexpectedUnindent": "Unindent not expected",
"viperModifierRequiresDef": "The modifier \"{name}\" must be used with 'def'",
"viperExternBodyInvalid": "The extern function \"{name}\" must have a body of only 'pass' or 'None'",
"unhashableDictKey": "Dictionary key must be hashable",
"unhashableSetEntry": {
"message": "Set entry must be hashable",
@@ -617,6 +617,7 @@
"unexpectedIndent": "意外缩进",
"unexpectedUnindent": "不应取消缩进",
"viperModifierRequiresDef": "修饰关键字 \"{name}\" 必须与 def 搭配使用",
"viperExternBodyInvalid": "extern 函数 \"{name}\" 的函数体必须为 pass 或 None",
"unhashableDictKey": "字典密钥必须可哈希",
"unhashableSetEntry": "Set 条目必须是可哈希的",
"uninitializedAbstractVariables": "抽象基类中定义的变量未在 final 类 \"{classType}\" 中初始化",
@@ -471,11 +471,19 @@ export interface FunctionNode extends ParseNodeBase<ParseNodeType.Function> {
returnAnnotation: ExpressionNode | undefined;
funcAnnotationComment: FunctionAnnotationNode | undefined;
suite: SuiteNode;
// Viper 扩展:函数声明前的 C 修饰关键字(inline/export/extern),用于语义校验。
viperModifier: string | undefined;
};
}
export namespace FunctionNode {
export function create(defToken: Token, name: NameNode, suite: SuiteNode, typeParams?: TypeParameterListNode) {
export function create(
defToken: Token,
name: NameNode,
suite: SuiteNode,
typeParams?: TypeParameterListNode,
viperModifier?: string
) {
const node: FunctionNode = {
start: defToken.start,
length: defToken.length,
@@ -493,6 +501,7 @@ export namespace FunctionNode {
returnAnnotation: undefined,
funcAnnotationComment: undefined,
suite: suite,
viperModifier,
},
};
+13 -9
View File
@@ -430,23 +430,22 @@ export class Parser {
switch (this._peekKeywordType()) {
case KeywordType.ViperModifier: {
// Viper 扩展:inline/export/extern 必须与 def 搭配使用。
// Viper 扩展:inline/export/extern 必须与 def(或其别名)搭配使用。
// 与 def 搭配时跳过修饰关键字并正常解析函数;单独使用时报错。
const modifierToken = this._peekToken() as KeywordToken;
const modifierText = this._fileContents!.slice(
modifierToken.start,
modifierToken.start + modifierToken.length
);
if (
this._peekToken(1).type === TokenType.Keyword &&
(this._peekToken(1) as KeywordToken).keywordType === KeywordType.Def
) {
this._getNextToken();
return this._parseFunctionDef();
return this._parseFunctionDef(/* asyncToken */ undefined, /* decorators */ undefined, modifierText);
}
this._addSyntaxError(
LocMessage.viperModifierRequiresDef().format({
name: this._fileContents!.slice(
modifierToken.start,
modifierToken.start + modifierToken.length
),
}),
LocMessage.viperModifierRequiresDef().format({ name: modifierText }),
modifierToken
);
this._getNextToken();
@@ -2050,7 +2049,11 @@ export class Parser {
// funcdef: 'def' NAME parameters ['->' test] ':' suite
// parameters: '(' [typedargslist] ')'
private _parseFunctionDef(asyncToken?: KeywordToken, decorators?: DecoratorNode[]): FunctionNode | ErrorNode {
private _parseFunctionDef(
asyncToken?: KeywordToken,
decorators?: DecoratorNode[],
viperModifier?: string
): FunctionNode | ErrorNode {
const defToken = this._getKeywordToken(KeywordType.Def);
const nameToken = this._getTokenIfIdentifier();
@@ -2119,6 +2122,7 @@ export class Parser {
this._isInFinallyLoop = wasInFinallyLoop;
const functionNode = FunctionNode.create(defToken, NameNode.create(nameToken), suite, typeParameters);
functionNode.d.viperModifier = viperModifier;
if (asyncToken) {
functionNode.d.isAsync = true;
extendRange(functionNode, asyncToken);
@@ -644,35 +644,44 @@ enum MagicsKind {
// Viper 扩展:C 风格修饰/声明关键字处理。
// Viper 支持将 C 修饰/声明语义写成语句前缀关键字:
// inline/export/extern def f(...) —— 修饰关键字,必须与 def 搭配
// fn/fun/func/function 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 关键字;
// 返回 'viperModifier':inline/export/extern 单独使用(未与 def 搭配),由 parser 报错;
// 返回 'def':把 fn/fun/func/function 当作 def 关键字;
// 返回 'viperModifier':inline/export/extern 生成修饰关键字 token,由 parser
// 判断是否与 def 搭配(搭配则跳过并记录,单独使用则报错);
// undefined:按普通标识符处理(变量名、成员名、导入名、`state:` 注解等)。
function getViperStatementKeywordAction(
text: string,
identifierStart: number,
identifierEnd: number,
name: string
): 'skip' | 'class' | 'viperModifier' | undefined {
name: string,
precededByViperModifier: boolean
): 'skip' | 'class' | 'def' | 'viperModifier' | undefined {
const nextWord = peekViperKeywordWord(text, identifierEnd);
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 报错。
// 一律生成修饰关键字 token,由 parser 判断是否与 def 搭配。
return 'viperModifier';
}
if (name === 'fn' || name === 'fun' || name === 'func' || name === 'function') {
// def 别名:语句起始(或紧跟在修饰关键字后,如 `inline fn f()`)且后跟
// 标识符(函数名)时按 def 关键字处理。
if (!isViperAtStatementStart(text, identifierStart) && !precededByViperModifier) {
return undefined;
}
return nextWord.length > 0 && isIdentifierStartChar(nextWord.charCodeAt(0)) ? 'def' : undefined;
}
if (name === 'enum' || name === 'rnum') {
if (nextWord === 'class' || nextWord === 'struct') {
return 'skip';
@@ -1468,12 +1477,20 @@ export class Tokenizer {
} else {
const value = this._internIdentifier(text, start, end, length);
// Viper 扩展:C 风格修饰/声明关键字。
// inline/export/extern 作为 def 前缀修饰、enum/rnum 作为 class/struct
// 前缀时跳过该 token(保留原始位置与缩进;着色由 TextMate 规则处理);
// inline/export/extern 生成修饰关键字 token(parser 判断是否配 def);
// enum/rnum/struct 在语句起始且后跟类名时按 class 关键字处理;
// inline/export/extern 单独使用(未与 def 搭配)时生成修饰关键字 token,
// 由 parser 报错。
const viperAction = getViperStatementKeywordAction(text, start, end, value);
// fn/fun/func/function 在语句起始(或紧跟修饰关键字)时按 def 关键字处理。
const precededByViperModifier =
this._tokens.length > 0 &&
this._tokens[this._tokens.length - 1].type === TokenType.Keyword &&
(this._tokens[this._tokens.length - 1] as KeywordToken).keywordType === KeywordType.ViperModifier;
const viperAction = getViperStatementKeywordAction(
text,
start,
end,
value,
precededByViperModifier
);
if (viperAction === 'skip') {
return true;
}
@@ -1481,6 +1498,10 @@ export class Tokenizer {
this._tokens.push(KeywordToken.create(start, length, KeywordType.Class, this._getComments()));
return true;
}
if (viperAction === 'def') {
this._tokens.push(KeywordToken.create(start, length, KeywordType.Def, this._getComments()));
return true;
}
if (viperAction === 'viperModifier') {
this._tokens.push(
KeywordToken.create(start, length, KeywordType.ViperModifier, this._getComments())
@@ -340,7 +340,7 @@ export def double_export(x: t.CInt) -> t.CInt:
return x * 2
extern def double_extern(x: t.CInt) -> t.CInt:
return x * 2
pass
# state 不是关键字,仍可作字段名/成员名
state: t.CInt = 0
@@ -471,4 +471,89 @@ inline def ok(x: t.CInt) -> t.CInt:
fs.rmSync(testDir, { recursive: true, force: true });
}
serviceProvider.dispose();
});
test('Viper def 别名 fn/fun/func/function 与 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
# def 别名
fn f1(x: t.CInt) -> t.CInt:
return x
fun f2(x: t.CInt) -> t.CInt:
return x
func f3(x: t.CInt) -> t.CInt:
return x
function f4(x: t.CInt) -> t.CInt:
return x
# extern:函数体必须为 pass 或 None
extern def e_ok1(x: t.CInt) -> t.CInt:
pass
extern def e_ok2(x: t.CInt) -> t.CInt:
None
extern def e_bad(x: t.CInt) -> t.CInt:
return x
def use():
a: t.CInt = f1(1) + f2(2) + f3(3) + f4(4)
`;
const testDir = path.join(__dirname, '__viper_alias_extern_test__');
const fs = require('fs');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'alias_extern.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('=== alias_extern.vp diagnostics ===');
diagnostics.forEach((d) => console.log(` ${d}`));
// 期望恰好 1 个 extern 函数体错误(e_bad),fn/fun/func/function 别名与
// extern pass/None 函数体都不应报错。
const externErrors = diagnostics.filter(
(d) => d.includes('必须为 pass 或 None') || d.includes("body of only 'pass' or 'None'")
);
assert.strictEqual(
externErrors.length,
1,
`expected 1 extern body error, got: ${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)\\s+)?(?:def|class|for|if|elif|else|while|try|finally|with|except|async|struct)\\b.*?:\\s*$",
"beforeText": "^\\s*(?:(?:inline|export|extern|enum|rnum)\\s+)?(?: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)\\s+)?(?:class|def|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|enum|rnum)\\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*$",
"decreaseIndentPattern": "^\\s*(elif|else|except|finally)\\b.*?:\\s*$"
}
}
@@ -37,6 +37,10 @@
"name": "storage.type.function.viper",
"match": "\\bdef\\b"
},
{
"name": "storage.type.function.viper",
"match": "\\b(fn|fun|func|function)\\b(?=\\s+[A-Za-z_][A-Za-z0-9_]*)"
},
{
"name": "storage.type.class.viper",
"match": "\\bclass\\b"