修复了 T(v) 类型初始化
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
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:
1 parent
024366a85a
commit
b47f43d5f2
3 files changed
+30671
-30443
No files matched your search
@@ -631,9 +631,24 @@ export class Binder extends ParseTreeWalker {
|
||||
|
||||
// Don't walk the body of the function until we're done analyzing
|
||||
// the current scope.
|
||||
// Viper 扩展:泛型类的方法需要能引用类的类型参数(如 `self.value = T(v)`)。
|
||||
// Python 默认把方法的 parent scope 设为最近的非 Class scope(跳过 Class 与
|
||||
// typeParamScope),导致方法内 `T` 解析到模块级符号而非本类 type param。
|
||||
// 当 enclosingClass 声明了 typeParams(PEP 695 `class Box[T]`)时,将方法
|
||||
// parent 设为 Class scope,使查找链 Function → Class → typeParamScope 可达
|
||||
// 类型参数。类成员(如 `value: T`)仍通过 self 访问,不受影响。
|
||||
let methodParentScope: Scope | undefined;
|
||||
if (
|
||||
containingClassNode &&
|
||||
containingClassNode.d.typeParams &&
|
||||
IsViperDialect(this._fileInfo.executionEnvironment.dialect)
|
||||
) {
|
||||
methodParentScope = AnalyzerNodeInfo.getScope(containingClassNode);
|
||||
}
|
||||
|
||||
this._createNewScope(
|
||||
ScopeType.Function,
|
||||
typeParamScope ?? this._getNonClassParentScope(),
|
||||
methodParentScope ?? typeParamScope ?? this._getNonClassParentScope(),
|
||||
/* proxyScope */ undefined,
|
||||
/* chainedModuleLevelScopeLookup */ undefined,
|
||||
() => {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* viperGenericTypeVarCall.test.ts
|
||||
*
|
||||
* 验证 Viper 泛型类方法中 `T(v)`(用类型变量做 C 风格强转)不应报
|
||||
* "Object of type TypeVar is not callable" / "Attribute __call__ is unknown"。
|
||||
*
|
||||
* 场景:TransPyC/Test/GenericTest/App/main.vp
|
||||
* class Box[T]():
|
||||
* value: T
|
||||
* def __init__(self, v: T):
|
||||
* self.value = T(v) # ← T(v) 类型变量调用
|
||||
*
|
||||
* 运行:npx jest src/tests/viperGenericTypeVarCall.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 GENERIC_TEST = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\Test\\GenericTest\\App\\main.vp';
|
||||
|
||||
function buildProgram(targetPath: string) {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const projectRoot = UriEx.file(path.dirname(targetPath));
|
||||
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('GenericTest 泛型类 Box 中 T(v) 类型变量调用不报 not callable', () => {
|
||||
const { program, serviceProvider, uri, configOptions } = buildProgram(GENERIC_TEST);
|
||||
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== GenericTest not-callable diagnostics ===');
|
||||
const notCallable = diagnostics.filter(
|
||||
(d) => d.includes('not callable') || d.includes('not callable') || d.includes('不可调用')
|
||||
);
|
||||
notCallable.forEach((d) => console.log(` ${d}`));
|
||||
|
||||
assert.strictEqual(
|
||||
notCallable.length,
|
||||
0,
|
||||
`unexpected not-callable errors: ${JSON.stringify(notCallable)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
test('最小复现:泛型类方法中 T(v) 类型变量调用不报 not callable', () => {
|
||||
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
|
||||
|
||||
class Box[T]():
|
||||
value: T
|
||||
|
||||
def __init__(self, v: T):
|
||||
self.value = T(v)
|
||||
|
||||
def get(self) -> T:
|
||||
return self.value
|
||||
|
||||
def make_box():
|
||||
b = Box(42)
|
||||
return b.get()
|
||||
`;
|
||||
const testDir = path.join(TRANSPYC_INCLUDES, '__typevar_call_test__');
|
||||
const fs = require('fs');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'typevar.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('=== typevar.vp diagnostics ===');
|
||||
diagnostics.forEach((d) => console.log(` ${d}`));
|
||||
|
||||
const notCallable = diagnostics.filter(
|
||||
(d) => d.includes('not callable') || d.includes('不可调用') || d.includes('__call__')
|
||||
);
|
||||
assert.strictEqual(
|
||||
notCallable.length,
|
||||
0,
|
||||
`unexpected not-callable errors: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
} finally {
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
Reference in new issue
Block a user