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