Fixed bug that incorrectly evaluates a constructor call to a constructor that infers a class-scoped ParamSpec when the passed function is generic. This addresses #8170. (#8215)

This commit is contained in:
Eric Traut 2024-06-24 22:04:09 +02:00 committed by GitHub
parent 6ffb60bd53
commit 3a263135ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 44 additions and 2 deletions

View File

@ -11640,6 +11640,11 @@ export function createTypeEvaluator(
paramSpecTypeVarContext.forEach((paramSpecTypeVarContext) => {
if (paramSpecTypeVarContext) {
specializedReturnType = applySolvedTypeVars(specializedReturnType, paramSpecTypeVarContext);
// It's possible that one or more of the TypeVars or ParamSpecs
// in the typeVarContext refer to TypeVars that were solved in
// the paramSpecTypeVarContext. Apply these solved TypeVars accordingly.
applySourceContextTypeVars(typeVarContext, paramSpecTypeVarContext);
}
});
}

View File

@ -3329,7 +3329,7 @@ export function convertParamSpecValueToType(type: FunctionType): Type {
FunctionType.addHigherOrderTypeVarScopeIds(functionType, withoutParamSpec.details.typeVarScopeId);
FunctionType.addHigherOrderTypeVarScopeIds(functionType, withoutParamSpec.details.higherOrderTypeVarScopeIds);
withoutParamSpec.details.parameters.forEach((entry) => {
withoutParamSpec.details.parameters.forEach((entry, index) => {
FunctionType.addParameter(functionType, {
category: entry.category,
name: entry.name,
@ -3337,7 +3337,7 @@ export function convertParamSpecValueToType(type: FunctionType): Type {
defaultValueExpression: entry.defaultValueExpression,
isNameSynthesized: entry.isNameSynthesized,
hasDeclaredType: true,
type: entry.type,
type: FunctionType.getEffectiveParameterType(withoutParamSpec, index),
});
});

View File

@ -0,0 +1,31 @@
# This sample tests the case where a class is parameterized by a ParamSpec
# which is inferred by a call to the constructor, and the passed value
# is a generic function whose types are informed by additional parameters
# also passed to the constructor.
from typing import Callable, Generic, ParamSpec, TypeVar
P = ParamSpec("P")
T = TypeVar("T")
class ABase: ...
class A(ABase): ...
TA = TypeVar("TA", bound=ABase)
class B(Generic[P, T]):
def __init__(
self, _type: Callable[P, T], *args: P.args, **kwargs: P.kwargs
) -> None: ...
def func1(t: type[TA]) -> TA: ...
b = B(func1, A)
reveal_type(b, expected_text="B[(t: type[A]), A]")

View File

@ -773,6 +773,12 @@ test('Constructor29', () => {
TestUtils.validateResults(analysisResults, 0);
});
test('Constructor30', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['constructor30.py']);
TestUtils.validateResults(analysisResults, 0);
});
test('ConstructorCallable1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['constructorCallable1.py']);