Fixed a bug that resulted in a false positive error when bidirectional type inference was used with a class that defines a __new__ method within a generic class that returns an instance of the class whose type arguments do not match the type parameters. This addresses https://github.com/microsoft/pyright/issues/5404.

This commit is contained in:
Eric Traut 2023-07-20 15:25:05 -07:00
parent c6e0f563f2
commit a65b45ed45
3 changed files with 55 additions and 1 deletions

View File

@ -10635,7 +10635,9 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
/* isTypeArgumentExplicit */ false
);
effectiveExpectedType = applySolvedTypeVars(genericReturnType, tempTypeVarContext);
effectiveExpectedType = applySolvedTypeVars(genericReturnType, tempTypeVarContext, {
unknownIfNotFound: true,
});
}
} else if (isFunction(effectiveReturnType)) {
// If the return type is a callable and the expected type is a union that

View File

@ -3667,6 +3667,19 @@ class ApplySolvedTypeVarsTransformer extends TypeVarTransformer {
if (typeVar.scopeId && this._typeVarContext.hasSolveForScope(typeVar.scopeId)) {
let replacement = signatureContext.getTypeVarType(typeVar, !!this._options.useNarrowBoundOnly);
// If the type is unknown, see if there's a known wide bound that we can use.
if (
replacement &&
isUnknown(replacement) &&
!this._options.useNarrowBoundOnly &&
this._options.unknownIfNotFound
) {
const entry = signatureContext.getTypeVar(typeVar);
if (entry?.wideBound) {
replacement = entry?.wideBound;
}
}
// If there was no narrow bound but there is a wide bound that
// contains literals or a TypeVar, we'll use the wide bound even if
// "useNarrowBoundOnly" is specified.

View File

@ -0,0 +1,39 @@
# This sample tests a case where a __new__ method in a generic class
# returns an instance of the class but with different type arguments
# than expected. This is arguably an error case, but pyright needs
# to handle it gracefully.
from __future__ import annotations
from typing import Generic, TypeVar
T = TypeVar("T", contravariant=True)
S = TypeVar("S", contravariant=True)
class ClassA(Generic[T]):
...
class ClassB(Generic[S, T], ClassA[T]):
...
class ClassC(ClassB[S, T]):
def __new__(cls, subcon: ClassA[S]) -> ClassC[S, list[S]]:
...
class ClassD(ClassB[S, T]):
def __new__(cls, subcon: ClassA[S]) -> ClassD[S, list[S]]:
...
c = ClassA[int]()
intermediate = ClassC(c)
v1 = ClassD(intermediate)
reveal_type(v1, expected_text="ClassD[list[int], list[list[int]]]")
v2 = ClassD(ClassC(c))
reveal_type(v2, expected_text="ClassD[list[int], list[list[int]]]")