From ad3eecb07728e144ec0e31cc56664a9a76aaf76a Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 8 Aug 2026 13:01:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(vipeye-internal):=20=E6=B3=9B=E5=9E=8B?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E6=98=BE=E5=BC=8F=E7=B1=BB=E5=9E=8B=E5=AE=9E?= =?UTF-8?q?=E5=8F=82=E8=B0=83=E7=94=A8=E4=B8=8D=E5=86=8D=E8=AF=AF=E6=8A=A5?= =?UTF-8?q?=E4=B8=8D=E5=8F=AF=E4=B8=8B=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PEP 695 风格 fn[T] 的显式类型实参 fn[t.CInt] 在 Viper 方言下直接忽略并按普通函数求值,避免把函数误报为不可下标。新增 double[t.CInt](21) 回归测试;临时测试目录改到工作区内以适配沙箱写权限限制。 --- .../src/analyzer/typeEvaluator.ts | 12 ++++ .../src/tests/viperGenericTypeVarCall.test.ts | 67 ++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts index dfa991bb..2675f2c5 100644 --- a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts +++ b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts @@ -8093,6 +8093,18 @@ export function createTypeEvaluator( isCArrayType(subtype)) ); + // Viper 扩展:PEP 695 风格显式类型实参 `fn[TypeArgs]`。 + // Viper 的 `def double[T](x: int) -> int` 中 `[T]` 已被预处理移除, + // 类型系统中 double 是无类型参数的普通函数;调用处 `double[t.CInt](21)` + // 的 `[t.CInt]` 只是显式类型标注,直接忽略并按 `double` 处理, + // 否则会把函数误报为 "不可下标"。 + if ( + IsViperDialect(viperIndexFileInfo.executionEnvironment.dialect) && + (isFunction(baseTypeResult.type) || isOverloaded(baseTypeResult.type)) + ) { + return { type: baseTypeResult.type, isIncomplete }; + } + const type = mapSubtypesExpandTypeVars( baseTypeResult.type, /* options */ undefined, diff --git a/packages/vipeye-internal/src/tests/viperGenericTypeVarCall.test.ts b/packages/vipeye-internal/src/tests/viperGenericTypeVarCall.test.ts index a5dbdc36..2ad97712 100644 --- a/packages/vipeye-internal/src/tests/viperGenericTypeVarCall.test.ts +++ b/packages/vipeye-internal/src/tests/viperGenericTypeVarCall.test.ts @@ -126,7 +126,7 @@ def make_box(): b = Box(42) return b.get() `; - const testDir = path.join(TRANSPYC_INCLUDES, '__typevar_call_test__'); + const testDir = path.join(__dirname, '__typevar_call_test__'); const fs = require('fs'); fs.mkdirSync(testDir, { recursive: true }); const testFile = path.join(testDir, 'typevar.vp'); @@ -160,3 +160,68 @@ def make_box(): } serviceProvider.dispose(); }); + + + +test('PEP 695 显式类型实参 double[t.CInt](21) 不报不可下标', () => { + 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 double[T](x: int) -> int: + return x * 2 + +def test_explicit_type_arg(): + r: t.CInt = double[t.CInt](21) +`; + const testDir = path.join(__dirname, '__typevar_call_test__'); + const fs = require('fs'); + fs.mkdirSync(testDir, { recursive: true }); + const testFile = path.join(testDir, 'explicit_type_arg.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('=== explicit_type_arg.vp diagnostics ==='); + diagnostics.forEach((d) => console.log(` ${d}`)); + + const notSubscriptable = diagnostics.filter( + (d) => d.includes('not subscriptable') || d.includes('不可下标') + ); + assert.strictEqual( + notSubscriptable.length, + 0, + `unexpected subscriptable errors: ${JSON.stringify(diagnostics)}` + ); + + program.dispose(); + } finally { + fs.rmSync(testDir, { recursive: true, force: true }); + } + serviceProvider.dispose(); +}); \ No newline at end of file