feat(vipeye-internal): union/fn 别名/extern 无函数体及 builtins 6 函数签名

- union 与 enum/rnum 对齐(union class / union A: 单独转 class)
- fn/fun/func/function 行尾单独使用按 def 处理并报缺函数名,
  后跟 :/=/ 保持标识符,避免误伤 func: 注解、func = x、func(x)
- extern def/fn 声明可省略函数体(无冒号无 pass),: pass 方式保留
- TextMate 高亮与 language-configuration 缩进规则同步(含 inline fn 修饰关键字)
- typeshed builtins.vpi 新增 Asm/LLVMIR/AsmInp/AsmOut/LInp/LOut,
  隐式内置直接可用(签名照抄 vipershed c.vpi)
- viperGenericTypeVarCall 9/9、tokenizer 91 测试通过
This commit is contained in:
TermiNexus committed 2026-08-08 18:31:25 +08:00
1 parent 817bc47ee9
commit bbd8b1022e
6 files changed
+235 -15

No files matched your search

+15 -4
View File
@@ -2111,11 +2111,22 @@ export class Parser {
this._isInFinallyBlock = false;
this._isInFinallyLoop = false;
const suite = this._parseSuite(/* isFunction */ true, this._parseOptions.skipFunctionAndClassBody, () => {
if (!functionTypeAnnotationToken) {
functionTypeAnnotationToken = this._getTypeAnnotationCommentText();
let suite: SuiteNode;
const possibleSuiteStart = this._peekToken();
if (viperModifier === 'extern' && possibleSuiteStart.type !== TokenType.Colon) {
// Viper extern 声明允许省略函数体(`extern def f(...) -> T`,无冒号、无 pass)。
// 创建空 suite 并消费行尾换行;`: pass` 方式仍走 _parseSuite 正常解析。
suite = SuiteNode.create(possibleSuiteStart);
if (this._consumeTokenIfType(TokenType.NewLine)) {
extendRange(suite, this._tokenizerOutput!.tokens.getItemAt(this._tokenIndex - 1));
}
});
} else {
suite = this._parseSuite(/* isFunction */ true, this._parseOptions.skipFunctionAndClassBody, () => {
if (!functionTypeAnnotationToken) {
functionTypeAnnotationToken = this._getTypeAnnotationCommentText();
}
});
}
this._isInExceptionGroup = wasInExceptionGroup;
this._isInFinallyBlock = wasInFinallyBlock;
@@ -675,18 +675,24 @@ function getViperStatementKeywordAction(
return 'viperModifier';
}
if (name === 'fn' || name === 'fun' || name === 'func' || name === 'function') {
// def 别名:语句起始(或紧跟在修饰关键字后,如 `inline fn f()`)且后跟
// 标识符(函数名)时按 def 关键字处理。
// def 别名:语句起始(或紧跟修饰关键字后,如 `inline fn f()`)时:
// 后跟标识符(函数名)→ def;
// 行尾单独使用(后面无任何内容)→ def(parser 会报缺函数名);
// 后跟 ':' / '=' / '(' 等 → 保持标识符(保护 TransPyC 现有
// `func:` 注解、`func = x` 赋值、`func(x)` 调用等合法用法)。
if (!isViperAtStatementStart(text, identifierStart) && !precededByViperModifier) {
return undefined;
}
return nextWord.length > 0 && isIdentifierStartChar(nextWord.charCodeAt(0)) ? 'def' : undefined;
if (nextWord.length > 0) {
return isIdentifierStartChar(nextWord.charCodeAt(0)) ? 'def' : undefined;
}
return peekViperNextNonWsChar(text, identifierEnd) === undefined ? 'def' : undefined;
}
if (name === 'enum' || name === 'rnum') {
if (name === 'enum' || name === 'rnum' || name === 'union') {
if (nextWord === 'class' || nextWord === 'struct') {
return 'skip';
}
// `enum A:` / `rnum A:` 单独使用(等价 class A:)。
// `enum A:` / `rnum A:` / `union A:` 单独使用(等价 class A:)。
return isViperAtStatementStart(text, identifierStart) &&
nextWord.length > 0 &&
isIdentifierStartChar(nextWord.charCodeAt(0))
@@ -734,6 +740,19 @@ function isViperAtStatementStart(text: string, identifierStart: number): boolean
return pos === 0 || text[pos - 1] === '\n' || text[pos - 1] === '\r';
}
// 跳过空白后返回首个非空白字符;若到行尾(换行)或文件结束则返回 undefined。
function peekViperNextNonWsChar(text: string, from: number): string | undefined {
let pos = from;
const len = text.length;
while (pos < len && (text[pos] === ' ' || text[pos] === '\t')) {
pos++;
}
if (pos < len && text[pos] !== '\r' && text[pos] !== '\n') {
return text[pos];
}
return undefined;
}
export class Tokenizer {
private _cs = new CharacterStream('');
private _tokens: Token[] = [];
@@ -364,6 +364,15 @@ rnum B:
class Inner:
x: t.CInt
# union + class(等价 t.CUnion 风格)
union class Value:
i: t.CInt
f: t.CFloat
# union 单独使用(等价 class)
union D:
x: t.CInt
def swap(p: IntPair | t.CPtr):
tmp: t.CInt = c.Deref(p).a
c.Deref(p).a = c.Deref(p).b
@@ -372,6 +381,7 @@ def swap(p: IntPair | t.CPtr):
def test_usage():
r: t.CInt = double_export(21)
g: t.CInt = Color.GREEN
u: t.CFloat = Value.f
`;
const testDir = path.join(__dirname, '__viper_keywords_test__');
const fs = require('fs');
@@ -518,6 +528,12 @@ extern def e_ok2(x: t.CInt) -> t.CInt:
extern def e_bad(x: t.CInt) -> t.CInt:
return x
# extern 声明可以省略函数体(无冒号、无 pass),": pass" 方式保留
extern def e_no_body(x: t.CInt) -> t.CInt
# extern + fn 别名,同样可以省略函数体
extern fn e_fn_no_body(x: t.CInt) -> t.CInt
def use():
a: t.CInt = f1(1) + f2(2) + f3(3) + f4(4)
`;
@@ -556,4 +572,147 @@ def use():
fs.rmSync(testDir, { recursive: true, force: true });
}
serviceProvider.dispose();
});
});
test('Viper fn/fun/func/function 单独使用报错,后跟 :/=/ 保持标识符', () => {
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
# fn 行尾单独使用 → 视为 def,报缺函数名
fn
# fun 后跟 '=' → 保持标识符(赋值,不是关键字)
fun = 5
# func 后跟 '(' → 保持标识符(调用,不是关键字)
func(1)
# function 后跟 ':' → 保持标识符(注解声明,不是关键字)
function: t.CInt = 0
# 正常用法不受影响
fn ok(x: t.CInt) -> t.CInt:
return x
`;
const testDir = path.join(__dirname, '__viper_fn_keyword_test__');
const fs = require('fs');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'fn_keyword.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('=== fn_keyword.vp diagnostics ===');
diagnostics.forEach((d) => console.log(` ${d}`));
// 期望恰好 1 个错误级诊断:fn 行尾单独使用报"缺函数名";
// fun=/func(/function: 保持标识符不应产生语法/类型错误。
const errors = diagnostics.filter((d) => d.startsWith('[category=0]'));
assert.strictEqual(
errors.length,
1,
`expected exactly 1 error (fn alone), got: ${JSON.stringify(diagnostics)}`
);
assert.ok(
errors[0].includes('函数名') || errors[0].includes('function name'),
`expected missing-function-name error: ${JSON.stringify(errors)}`
);
program.dispose();
} finally {
fs.rmSync(testDir, { recursive: true, force: true });
}
serviceProvider.dispose();
});
test('builtins.vp 中 Asm/LLVMIR/AsmInp/AsmOut/LInp/LOut 签名可用且不报未定义', () => {
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 test_builtins_asm_llvmir():
r: t.CInt = 0
# Asm:基础 + 输出/输入操作数 + 破坏列表
Asm("nop")
Asm(f"mov {AsmOut(r, t.ASM_DESCR.OUTPUT_REG)}, rdi", op=[t.ASM_DESCR.CLOBBER_MEMORY])
Asm(f"mov eax, {AsmInp(r, t.ASM_DESCR.REG_ANY)}")
# LLVMIR:LInp/LOut 标记操作数
LLVMIR(f"{LOut(r)} = add i32 {LInp(r)}, {LInp(r)}")
`;
const testDir = path.join(__dirname, '__viper_builtins_asm_test__');
const fs = require('fs');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'builtins_asm.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('=== builtins_asm.vp diagnostics ===');
diagnostics.forEach((d) => console.log(` ${d}`));
// 期望没有任何错误级诊断:Asm/LLVMIR/AsmInp/AsmOut/LInp/LOut 由隐式
// builtins(builtins.vpi)提供,无需 import 即可直接使用。
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();
});
@@ -2560,3 +2560,34 @@ if sys.version_info >= (3, 11):
if sys.version_info >= (3, 13):
class PythonFinalizationError(RuntimeError): ...
# --- Viper 扩展:C 内联汇编 / LLVM IR 内联设施 ---
# 签名照抄 vipershed-fallback/stdlib/c.vpi(c.Asm / c.LLVMIR / c.AsmInp / c.AsmOut /
# c.LInp / c.LOut),供 Viper 代码直接以内置函数形式使用。
def Asm(asm: str, *args: Any, inp: Any = None, out: Any = None, op: Any = None) -> Any:
"""内联汇编(等价 c.Asm)。inp 为输入操作数描述符列表,out 为输出操作数
描述符列表,op 为破坏描述符列表。"""
...
def LLVMIR(ir: str, *args: Any, inp: Any = None, out: Any = None, op: Any = None) -> Any:
"""LLVM IR 内联(等价 c.LLVMIR)。inp/out/op 为操作数描述符列表。"""
...
def AsmInp(expr: Any, constraint: Any = None) -> Any:
"""汇编输入操作数标记(等价 c.AsmInp)。constraint 为寄存器约束
(如 t.ASM_DESCR.REG_ANY)。"""
...
def AsmOut(expr: Any, constraint: Any = None) -> Any:
"""汇编输出操作数标记(等价 c.AsmOut)。constraint 为寄存器约束
(如 t.ASM_DESCR.OUTPUT_REG)。"""
...
def LInp(expr: Any, constraint: Any = None) -> Any:
"""LLVM IR 输入操作数标记(等价 c.LInp)。"""
...
def LOut(expr: Any, constraint: Any = None) -> Any:
"""LLVM IR 输出操作数标记(等价 c.LOut)。"""
...
@@ -28,7 +28,7 @@
],
"onEnterRules": [
{
"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*$",
"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*$",
"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|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|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*$",
"decreaseIndentPattern": "^\\s*(elif|else|except|finally)\\b.*?:\\s*$"
}
}
@@ -39,7 +39,7 @@
},
{
"name": "storage.type.function.viper",
"match": "\\b(fn|fun|func|function)\\b(?=\\s+[A-Za-z_][A-Za-z0-9_]*)"
"match": "\\b(fn|fun|func|function)\\b(?=\\s+[A-Za-z_][A-Za-z0-9_]*|\\s*$)"
},
{
"name": "storage.type.class.viper",
@@ -47,11 +47,11 @@
},
{
"name": "storage.modifier.viper",
"match": "\\b(inline|export|extern)\\b(?=\\s+def\\b|[ \\t]*$)"
"match": "\\b(inline|export|extern)\\b(?=\\s+(?:def|fn|fun|func|function)\\b|[ \\t]*$)"
},
{
"name": "storage.type.class.viper",
"match": "\\b(enum|rnum)\\b(?=\\s+(class|struct)\\b|\\s+[A-Za-z_][A-Za-z0-9_]*)"
"match": "\\b(enum|rnum|union)\\b(?=\\s+(class|struct)\\b|\\s+[A-Za-z_][A-Za-z0-9_]*)"
},
{
"name": "storage.type.class.viper",