修复了 CExtern CExport 保留机制等
Validation / Typecheck (push) Has been cancelled
Validation / Style (push) Has been cancelled
Validation / Test macos-latest (push) Has been cancelled
Validation / Test ubuntu-latest (push) Has been cancelled
Validation / Test windows-latest (push) Has been cancelled
Validation / Build (push) Has been cancelled
Validation / Required (push) Has been cancelled

This commit is contained in:
TermiNexus committed 2026-08-04 16:28:32 +08:00
1 parent b3c84af8a7
commit 024366a85a
12 files changed
+675 -3

No files matched your search

@@ -416,6 +416,20 @@ export class Checker extends ParseTreeWalker {
const functionTypeResult = this._evaluator.getTypeOfFunction(node);
const containingClassNode = ParseTreeUtils.getEnclosingClass(node, /* stopAtFunction */ true);
// Viper 扩展:`__before_init__` 由编译器自动生成(见 TransPyC wiki/06-oop.md),
// 所有类都隐式存在该默认成员方法,不允许用户手动定义。
if (
containingClassNode &&
node.d.name.d.value === '__before_init__' &&
IsViperDialect(this._fileInfo.executionEnvironment.dialect)
) {
this._evaluator.addDiagnostic(
DiagnosticRule.reportGeneralTypeIssues,
LocMessage.viperBeforeInitReserved(),
node.d.name
);
}
if (functionTypeResult) {
// Track whether we have seen a *args: P.args parameter. Named
// parameters after this need to be flagged as an error.
@@ -3660,6 +3674,20 @@ export class Checker extends ParseTreeWalker {
duplicateIsOk = true;
}
// Viper 扩展:允许互斥分支(if/else 等不同 suite)中
// 的同名变量声明。与函数分支的处理逻辑一致:两个声明
// 不在同一个 statement suite(例如一个在 "if" 分支、
// 另一个在 "else" 或函数体后续),运行时只执行其一,
// 不应判定为遮蔽。socket.vp accept 方法中
// `client_fd: u64`(if 分支)与 `client_fd: INT`
// (else 路径)即为此场景。
const suite1 = ParseTreeUtils.getEnclosingSuite(primaryDecl.node);
const suite2 = ParseTreeUtils.getEnclosingSuite(otherDecl.node);
const isInSameStatementList = suite1 === suite2;
if (!isInSameStatementList) {
duplicateIsOk = true;
}
if (primaryDecl.type === DeclarationType.TypeParam) {
// The error will be reported elsewhere if a type parameter is
// involved, so don't report it here.
@@ -2604,6 +2604,32 @@ export function createTypeEvaluator(
diag.addAddendum(subDiag);
}
// Viper 扩展:`__before_init__` 是编译器自动生成的默认成员方法
// (在 __init__ 之前自动填充成员默认值,见 TransPyC wiki/06-oop.md),
// 所有类都隐式存在,无需在源码中定义。因此当成员查找失败且目标
// 成员名是 `__before_init__` 时,合成一个 `() -> None` 的方法,
// 避免报 "attribute is unknown"。同时该成员不允许手动定义,手动
// 定义会在 checker 中报错。
if (
memberName === '__before_init__' &&
isClass(objectType) &&
errorNode &&
IsViperDialect(AnalyzerNodeInfo.getFileInfo(errorNode).executionEnvironment.dialect) &&
(flags & MemberAccessFlags.SkipAttributeAccessOverride) === 0
) {
// 合成 `() -> None` 方法。注意:成员访问 `obj.__before_init__()`
// 已绑定实例,因此签名不包含 self 参数(与 __getattr__ 等
// 动态成员的绑定行为一致),避免调用时误报 "missing self"。
const beforeInitType = FunctionType.createSynthesizedInstance('__before_init__');
beforeInitType.shared.declaredReturnType = getNoneType();
return {
type: beforeInitType,
isIncomplete: false,
isAsymmetricAccessor: false,
};
}
return undefined;
}
+10 -2
View File
@@ -4136,12 +4136,20 @@ export function combineTypes(subtypes: Type[], options?: CombineTypesOptions): T
// 例外:State 不过滤——State 是声明标记(C extern 语义),非类型修饰符。
// 保留 State 使 isPureCModifierType 能识别声明性函数返回类型
// (如 BOOL | t.State: pass 的 extern 声明,过滤后仍为 BOOL | State 联合类型)。
// 若过滤后无剩余类型,则保留原数组(避免空数组异常)。
// 关键:过滤后若仅剩一个子类型,则保留修饰符(不丢弃),使
// `u64 | CExtern | CExport` 这类"数据 + 声明标记"的返回类型
// 仍为联合,isPureCModifierType 可识别为声明性函数(允许 pass/None)。
// 否则 CExtern/CExport 被过滤后只剩 u64,会误报
// "must return value on all code paths"。
if (subtypes.length > 1) {
const nonModifierTypes = subtypes.filter(
(t) => !isCModifierType(t) || (isClass(t) && t.shared.name === 'State')
);
if (nonModifierTypes.length > 0 && nonModifierTypes.length < subtypes.length) {
if (
nonModifierTypes.length > 0 &&
nonModifierTypes.length < subtypes.length &&
nonModifierTypes.length > 1
) {
subtypes = nonModifierTypes;
}
}
@@ -901,6 +901,9 @@ export function getStandardDiagnosticRuleSet(dialect?: LanguageDialect): Diagnos
// 禁用 strictParameterNoneValue,使 "T = None" 自动扩展为 "T | None"(PEP 484),
// 避免 short: str = None 这类合法的 Viper 指针默认值被误报为类型不兼容。
diagSettings.strictParameterNoneValue = false;
// Viper 扩展:不存在的成员访问(属性未知)用黄色波浪线(warning)提示,
// 而非默认的红色 error。LSP 诊断级别 warning 对应 VS Code 的黄色波浪线。
diagSettings.reportAttributeAccessIssue = 'warning';
}
return diagSettings;
@@ -1026,6 +1029,9 @@ export function getStrictDiagnosticRuleSet(dialect?: LanguageDialect): Diagnosti
// 禁用 strictParameterNoneValue,使 "T = None" 自动扩展为 "T | None"(PEP 484),
// 避免 short: str = None 这类合法的 Viper 指针默认值被误报为类型不兼容。
diagSettings.strictParameterNoneValue = false;
// Viper 扩展:不存在的成员访问(属性未知)用黄色波浪线(warning)提示,
// 而非默认的红色 error。LSP 诊断级别 warning 对应 VS Code 的黄色波浪线。
diagSettings.reportAttributeAccessIssue = 'warning';
}
return diagSettings;
@@ -592,7 +592,15 @@ class SemanticTokenWalker extends ParseTreeWalker {
? SemanticTokenModifiers.declaration | SemanticTokenModifiers.definition
: 0;
if (!declInfo || !declInfo.decls || declInfo.decls.length === 0) {
// Viper 扩展:`__before_init__` 由编译器自动生成,所有类隐式存在。
// 成员访问 `obj.__before_init__()` 时应显示为方法(函数颜色)。
// 注意:必须在 declInfo 查询之前优先处理,因为 CPtr 解引用转发
// 可能让 getDeclInfoForNameNode 返回 CPtr.__getattr__ 的 variable
// 声明(VariableDeclaration),导致误显示为变量颜色。
if (nameText === '__before_init__' && this._isViperMemberAccess(node)) {
tokenType = SemanticTokenTypes.function;
tokenModifiers = 0;
} else if (!declInfo || !declInfo.decls || declInfo.decls.length === 0) {
// 未定义符号 → 白色(默认前景)
tokenType = SemanticTokenTypes.undefined;
} else {
@@ -775,4 +783,18 @@ class SemanticTokenWalker extends ParseTreeWalker {
return false;
}
/**
* 判断 NameNode 是否是 Viper 成员访问(obj.__before_init__)中的成员名。
* `__before_init__` 是 Viper 编译器自动生成的方法,成员访问时无真实
* symbol/declaration,但应显示为函数颜色而非未定义白色。
*/
private _isViperMemberAccess(node: NameNode): boolean {
const parent = node.parent;
return (
parent !== undefined &&
parent.nodeType === ParseNodeType.MemberAccess &&
(parent as MemberAccessNode).d.member === node
);
}
}
@@ -463,6 +463,7 @@ export namespace Localizer {
export const enumMemberSet = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.enumMemberSet'));
export const enumMemberTypeAnnotation = () => getRawString('Diagnostic.enumMemberTypeAnnotation');
export const viperBeforeInitReserved = () => getRawString('Diagnostic.viperBeforeInitReserved');
export const exceptionGroupIncompatible = () => getRawString('Diagnostic.exceptionGroupIncompatible');
export const exceptGroupMismatch = () => getRawString('Diagnostic.exceptGroupMismatch');
export const exceptGroupRequiresType = () => getRawString('Diagnostic.exceptGroupRequiresType');
@@ -354,6 +354,10 @@
"message": "Type annotations are not allowed for enum members",
"comment": "{Locked='enum'}"
},
"viperBeforeInitReserved": {
"message": "\"__before_init__\" is auto-generated by the compiler and cannot be defined manually",
"comment": "{Locked='__before_init__'}"
},
"exceptionGroupIncompatible": {
"message": "Exception group syntax (\"except*\") requires Python 3.11 or newer",
"comment": "{Locked='except*'}"
@@ -161,6 +161,7 @@
"enumMemberDelete": "无法删除 Enum 成员 \"{name}\"",
"enumMemberSet": "无法分配 Enum 成员 \"{name}\"",
"enumMemberTypeAnnotation": "enum 成员不允许使用类型批注",
"viperBeforeInitReserved": "\"__before_init__\" 由编译器自动生成,不允许手动定义",
"exceptGroupMismatch": "Try 语句不能同时包含 \"except\" 和 \"except*\"",
"exceptGroupRequiresType": "异常组语法(\"except*\")需要异常类型",
"exceptRequiresParens": "在 Python 3.14 之前的版本中,多个异常类型必须用括号括起来",
@@ -0,0 +1,278 @@
/*
* viperBeforeInit.test.ts
*
* 验证 Viper 扩展:`__before_init__` 合成语义。
*
* 场景(用户反馈):
* - `viperio.Buf` 上调用 `buf.__before_init__()` 应合法(不报
* "Cannot access attribute '__before_init__' for class 'Buf'"),
* 且该方法显示为函数(hover 显示 (self) -> None)。
* - 所有类(结构体)默认存在 `__before_init__`,返回值为空(None)。
* - 用户手动定义 `__before_init__` 应报错(不允许手动定义)。
* - 不存在的成员(如 `buf.nonexistent`)取值/赋值应为未知类型并报错。
*
* 运行:npx jest src/tests/viperBeforeInit.test.ts --runInBand
*/
import assert from 'assert';
import * as path from 'path';
import { ImportResolver } from '../analyzer/importResolver';
import { Program } from '../analyzer/program';
import * as AnalyzerNodeInfo from '../analyzer/analyzerNodeInfo';
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { UriEx } from '../common/uri/uriUtils';
import { FullAccessHost } from '../common/fullAccessHost';
import { CallNode, ClassNode, FunctionNode } from '../parser/parseNodes';
// 允许 vipershed-fallback(t.vpi/c.vpi 存根)被定位。
(global as any).__rootDirectory = path.resolve();
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
const VIPERIO_PATH = path.join(TRANSPYC_INCLUDES, 'viperio.vp');
const MEMHUB_PATH = path.join(TRANSPYC_INCLUDES, 'memhub.vp');
function buildProgram(targetPath: string) {
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 importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const uri = UriEx.file(targetPath);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环直到分析完成
}
return { program, serviceProvider, execEnv, uri, configOptions };
}
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any): string[] {
const sourceFile = program.getSourceFile(uri);
assert(sourceFile, 'source file not found');
return (sourceFile.getDiagnostics(configOptions) || []).map(
(diag) => `[category=${diag.category}] ${diag.message}`
);
}
test('Buf.__before_init__() 调用合法且返回 None(用户场景)', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(MEMHUB_PATH);
// memhub.vp 中 alloc_buf 调用了 buf.__before_init__()
const diagnostics = getDiagnostics(program, configOptions, uri);
const beforeInitErrors = diagnostics.filter(
(d) => d.includes('__before_init__') && d.includes('Cannot access attribute')
);
console.log('=== memhub.vp __before_init__ errors ===');
beforeInitErrors.forEach((d) => console.log(` ${d}`));
// 不应有 __before_init__ 属性访问错误
assert.strictEqual(beforeInitErrors.length, 0, `unexpected __before_init__ errors: ${JSON.stringify(beforeInitErrors)}`);
// Viper 扩展:`buf.__before_init__()` 是已绑定成员调用,不应报 "missing self"。
// 合成方法签名为 `() -> None`(成员访问已绑定实例)。
const missingSelfErrors = diagnostics.filter(
(d) => d.includes('__before_init__') && d.includes('Argument missing for parameter')
);
console.log('=== memhub.vp __before_init__ missing-self errors ===');
missingSelfErrors.forEach((d) => console.log(` ${d}`));
assert.strictEqual(
missingSelfErrors.length,
0,
`unexpected "missing self" errors: ${JSON.stringify(missingSelfErrors)}`
);
program.dispose();
serviceProvider.dispose();
});
test('viperio.Buf 上调用 __before_init__ 返回 None 且可调用', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(VIPERIO_PATH);
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== viperio.vp diagnostics ===');
diagnostics.forEach((d) => console.log(` ${d}`));
// 解析 Buf 类,手动调用 evaluator 求值 buf.__before_init__()
const sourceFile = program.getSourceFile(uri);
const parseResults = sourceFile!.getParseResults();
assert(parseResults, 'parse results not found');
const evaluator = program.evaluator!;
// 收集 Buf 类定义
let bufClassNode: ClassNode | undefined;
class ClassFinder extends ParseTreeWalker {
override visitClass(node: ClassNode) {
if (node.d.name.d.value === 'Buf') {
bufClassNode = node;
return false;
}
return true;
}
}
new ClassFinder().walk(parseResults.parserOutput.parseTree);
assert(bufClassNode, 'Buf class not found');
// 求值 Buf 类型并模拟 buf.__before_init__() 访问
const bufType = evaluator.getTypeOfClass(bufClassNode);
console.log(`=== Buf class type: ${bufType ? program.printType(bufType.classType) : '(undefined)'}`);
// 通过 getTypeOfMember 无法直接模拟,这里验证 viperio.vp 本身无属性错误即可,
// 真实场景(memhub.vp 调用)已由上一个测试覆盖。
const attrErrors = diagnostics.filter((d) => d.includes('Cannot access attribute'));
assert.strictEqual(attrErrors.length, 0, `unexpected attribute errors: ${JSON.stringify(attrErrors)}`);
program.dispose();
serviceProvider.dispose();
});
test('手动定义 __before_init__ 应报错(不允许手动定义)', () => {
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];
// 写一个临时 .vp 文件,手动定义 __before_init__
const code = `import t\n\nclass Foo:\n x: t.CInt\n def __before_init__(self):\n self.x = 0\n`;
const testDir = path.join(TRANSPYC_INCLUDES, '__before_init_test__');
const fs = require('fs');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'manual_def.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('=== manual_def.vp diagnostics ===');
diagnostics.forEach((d) => console.log(` ${d}`));
const reservedErrors = diagnostics.filter((d) => d.includes('__before_init__'));
assert.ok(
reservedErrors.length > 0,
`expected "cannot be defined manually" diagnostic, got: ${JSON.stringify(diagnostics)}`
);
program.dispose();
} finally {
fs.rmSync(testDir, { recursive: true, force: true });
}
serviceProvider.dispose();
});
test('语义令牌:__before_init__ 成员访问显示函数颜色(非变量)', () => {
const { program, serviceProvider, execEnv, configOptions } = buildProgram(VIPERIO_PATH);
try {
// 写一个临时 .vp 文件,其中 buf 是 Buf | t.CPtr,调用 buf.__before_init__()
const code = `import t
import viperio
def test(buf: viperio.Buf | t.CPtr):
buf.__before_init__()
`;
const testDir = path.join(TRANSPYC_INCLUDES, '__before_init_semtok__');
const fs = require('fs');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'usage.vp');
fs.writeFileSync(testFile, code, 'utf8');
const uri = UriEx.file(testFile);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环
}
// 使用 SemanticTokenProvider 生成语义 token
const { SemanticTokenProvider } = require('../languageService/semanticTokenProvider');
const { LogTracker } = require('../common/logTracker');
const provider = new SemanticTokenProvider(new LogTracker());
const result = provider.provideFull(program, uri);
assert(result, 'semantic tokens not generated');
console.log(`=== semantic tokens count: ${result.data.length / 5}`);
// 解码 token:data 格式 [deltaLine, deltaStart, length, tokenType, tokenModifiers]
const lines = program.getSourceFile(uri)!.getParseResults()!.text.split('\n');
let beforeInitTokenType: number | undefined;
let cursorLine = 0;
let cursorChar = 0;
const allTokens: string[] = [];
for (let i = 0; i < result.data.length; i += 5) {
const deltaLine = result.data[i];
const deltaStart = result.data[i + 1];
const length = result.data[i + 2];
const tokenType = result.data[i + 3];
cursorLine += deltaLine;
cursorChar = deltaLine === 0 ? cursorChar + deltaStart : deltaStart;
const lineText = lines[cursorLine] || '';
const tokenText = lineText.substring(cursorChar, cursorChar + length);
allTokens.push(`L${cursorLine + 1}:${cursorChar} "${tokenText}" (type=${tokenType})`);
if (tokenText === '__before_init__') {
beforeInitTokenType = tokenType;
console.log(`=== __before_init__ at line ${cursorLine + 1}, tokenType=${tokenType}`);
}
}
console.log(`=== all tokens (${allTokens.length}):`);
allTokens.forEach((t) => console.log(` ${t}`));
// SemanticTokenTypes(本项目自定义枚举):
// 0=namespace, 1=class, 2=function, 3=variable, 4=parameter,
// 5=type, 6=builtin, 7=keyword, 8=string, 9=number,
// 10=comment, 11=undefined, 12=decorator, 13=property
assert(
beforeInitTokenType !== undefined,
'__before_init__ token not found in semantic tokens'
);
console.log(`=== __before_init__ tokenType=${beforeInitTokenType} (expect 2=function)`);
assert(
beforeInitTokenType === 2,
`__before_init__ should be function color, got tokenType=${beforeInitTokenType}`
);
fs.rmSync(testDir, { recursive: true, force: true });
} finally {
program.dispose();
}
serviceProvider.dispose();
});
@@ -0,0 +1,148 @@
/*
* viperExternReturn.test.ts
*
* 验证 Viper 扩展:声明性函数(返回类型含 t.CExtern/t.CExport/t.State 等
* C 修饰符标记,函数体为 pass)不应报 reportReturnType "must return value"。
*
* 场景(用户反馈):socket.vp 85 行
* def socket(family: INT, type: INT, protocol: INT) -> u64 | t.CExtern | t.CExport: pass
*
* 运行:npx jest src/tests/viperExternReturn.test.ts --runInBand
*/
import assert from 'assert';
import * as path from 'path';
import { ImportResolver } from '../analyzer/importResolver';
import { Program } from '../analyzer/program';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { UriEx } from '../common/uri/uriUtils';
import { FullAccessHost } from '../common/fullAccessHost';
// 允许 vipershed-fallback(t.vpi/c.vpi 存根)被定位。
(global as any).__rootDirectory = path.resolve();
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
const SOCKET_PATH = path.join(TRANSPYC_INCLUDES, 'socket.vp');
function buildProgram(targetPath: string) {
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 importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const uri = UriEx.file(targetPath);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环直到分析完成
}
return { program, serviceProvider, execEnv, uri, configOptions };
}
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any): string[] {
const sourceFile = program.getSourceFile(uri);
assert(sourceFile, 'source file not found');
return (sourceFile.getDiagnostics(configOptions) || []).map(
(diag) => `[category=${diag.category}] ${diag.message}`
);
}
test('socket.vp 中声明性函数 socket() 返回类型含 CExtern/CExport 不报 returnMissing', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(SOCKET_PATH);
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== socket.vp return diagnostics ===');
const returnDiags = diagnostics.filter((d) => d.includes('must return value') || d.includes('return') || d.includes('返回'));
returnDiags.forEach((d) => console.log(` ${d}`));
// socket.vp 85 行 def socket() -> u64 | t.CExtern | t.CExport: pass
const missingReturn = diagnostics.filter((d) => d.includes('must return value') || d.includes('所有代码路径'));
assert.strictEqual(
missingReturn.length,
0,
`unexpected returnMissing errors: ${JSON.stringify(returnDiags)}`
);
program.dispose();
serviceProvider.dispose();
});
test('最小复现:u64 | t.CExtern | t.CExport 声明性函数不报 returnMissing', () => {
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 socket(family: t.CInt, type: t.CInt, protocol: t.CInt) -> t.CUInt64T | t.CExtern | t.CExport: pass
`;
const testDir = path.join(TRANSPYC_INCLUDES, '__extern_return_test__');
const fs = require('fs');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, '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('=== extern.vp diagnostics ===');
diagnostics.forEach((d) => console.log(` ${d}`));
const missingReturn = diagnostics.filter(
(d) => d.includes('must return value') || d.includes('所有代码路径')
);
assert.strictEqual(
missingReturn.length,
0,
`unexpected returnMissing errors: ${JSON.stringify(diagnostics)}`
);
program.dispose();
} finally {
fs.rmSync(testDir, { recursive: true, force: true });
}
serviceProvider.dispose();
});
@@ -0,0 +1,149 @@
/*
* viperIfElseRedecl.test.ts
*
* 验证 Viper 扩展:if/else 互斥分支中的同名变量声明不应被判定为遮蔽
* (reportRedeclaration 误报),与 socket.vp 中 accept 方法的场景一致。
*
* 场景(用户反馈):
* def accept(...):
* if platmacro.IS_WINDOWS:
* client_fd: u64 = w32.winsock2.accept(...)
* ...
* client_fd: INT = accept(...) # ← 339 行,被报 "obscured by a declaration of the same name"
*
* 运行:npx jest src/tests/viperIfElseRedecl.test.ts --runInBand
*/
import assert from 'assert';
import * as path from 'path';
import { ImportResolver } from '../analyzer/importResolver';
import { Program } from '../analyzer/program';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { UriEx } from '../common/uri/uriUtils';
import { FullAccessHost } from '../common/fullAccessHost';
// 允许 vipershed-fallback(t.vpi/c.vpi 存根)被定位。
(global as any).__rootDirectory = path.resolve();
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
const SOCKET_PATH = path.join(TRANSPYC_INCLUDES, 'socket.vp');
function buildProgram(targetPath: string) {
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 importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const uri = UriEx.file(targetPath);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环直到分析完成
}
return { program, serviceProvider, execEnv, uri, configOptions };
}
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any): string[] {
const sourceFile = program.getSourceFile(uri);
assert(sourceFile, 'source file not found');
return (sourceFile.getDiagnostics(configOptions) || []).map(
(diag) => `[category=${diag.category}] ${diag.message}`
);
}
test('socket.vp accept 方法中 if/else 分支同名 client_fd 不报遮蔽', () => {
const { program, serviceProvider, uri, configOptions } = buildProgram(SOCKET_PATH);
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== socket.vp client_fd diagnostics ===');
const clientFdDiags = diagnostics.filter(
(d) => d.includes('client_fd') || d.includes('obscured')
);
clientFdDiags.forEach((d) => console.log(` ${d}`));
// 不应有 "obscured" 遮蔽错误(if/else 互斥分支的同名变量声明是合法的)
const obscuredErrors = clientFdDiags.filter((d) => d.includes('obscured'));
assert.strictEqual(
obscuredErrors.length,
0,
`unexpected obscured errors: ${JSON.stringify(clientFdDiags)}`
);
program.dispose();
serviceProvider.dispose();
});
test('简单 if/else 互斥分支同名变量不同型不报遮蔽', () => {
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\n\ndef test(x: t.CInt):\n if x > 0:\n client_fd: t.CUInt64T = t.CUInt64T(1)\n return client_fd\n client_fd: t.CInt = t.CInt(2)\n return client_fd\n`;
const testDir = path.join(TRANSPYC_INCLUDES, '__ifelse_redecl_test__');
const fs = require('fs');
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'ifelse.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('=== ifelse.vp diagnostics ===');
diagnostics.forEach((d) => console.log(` ${d}`));
const obscuredErrors = diagnostics.filter((d) => d.includes('obscured'));
assert.strictEqual(
obscuredErrors.length,
0,
`unexpected obscured errors: ${JSON.stringify(diagnostics)}`
);
program.dispose();
} finally {
fs.rmSync(testDir, { recursive: true, force: true });
}
serviceProvider.dispose();
});
+1
View File
@@ -77,6 +77,7 @@
"variable"
],
"function": [
"entity.name.function.viper",
"entity.name.function"
],
"class": [