Fixed bug that results in a false positive error when using a TypeVar with an upper bound of type as a base class in a class statement. This addresses #8313. (#8321)

This commit is contained in:
Eric Traut 2024-07-06 11:47:37 -07:00 committed by GitHub
parent 3ddf0ad705
commit 1c85d650ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 58 additions and 41 deletions

View File

@ -1381,28 +1381,44 @@ export function createTypeEvaluator(
return;
}
if (flags & EvalFlags.NoTypeVarTuple) {
if ((flags & EvalFlags.NoTypeVarTuple) !== 0) {
if (isVariadicTypeVar(typeResult.type) && !typeResult.type.isVariadicInUnion) {
addError(LocMessage.typeVarTupleContext(), node);
typeResult.type = UnknownType.create();
}
}
if (!isEffectivelyInstantiable(typeResult.type)) {
const isEmptyVariadic =
isClassInstance(typeResult.type) &&
ClassType.isTupleClass(typeResult.type) &&
typeResult.type.tupleTypeArguments?.length === 0;
const isEllipsis =
isClassInstance(typeResult.type) && ClassType.isBuiltIn(typeResult.type, ['EllipsisType', 'ellipsis']);
if (!isEmptyVariadic && !isEllipsis) {
addExpectedClassDiagnostic(typeResult.type, node);
typeResult.type = UnknownType.create();
typeResult.typeErrors = true;
}
if (isEffectivelyInstantiable(typeResult.type, { honorTypeVarBounds: true })) {
return;
}
// Exempt ellipses.
if (isClassInstance(typeResult.type) && ClassType.isBuiltIn(typeResult.type, ['EllipsisType', 'ellipsis'])) {
return;
}
// Exempt empty tuples, which can be used for specializing a TypeVarTuple.
if (isClassInstance(typeResult.type) && typeResult.type.tupleTypeArguments?.length === 0) {
return;
}
const diag = new DiagnosticAddendum();
if (isUnion(typeResult.type)) {
doForEachSubtype(typeResult.type, (subtype) => {
if (!isEffectivelyInstantiable(subtype, { honorTypeVarBounds: true })) {
diag.addMessage(LocAddendum.typeNotClass().format({ type: printType(subtype) }));
}
});
}
addDiagnostic(
DiagnosticRule.reportGeneralTypeIssues,
LocMessage.typeExpectedClass().format({ type: printType(typeResult.type) }) + diag.getString(),
node
);
typeResult.type = UnknownType.create();
typeResult.typeErrors = true;
}
function getTypeOfAwaitOperator(node: AwaitNode, flags: EvalFlags, inferenceContext?: InferenceContext) {
@ -3287,23 +3303,6 @@ export function createTypeEvaluator(
return diagnostic;
}
function addExpectedClassDiagnostic(type: Type, node: ParseNode) {
const diag = new DiagnosticAddendum();
if (isUnion(type)) {
doForEachSubtype(type, (subtype) => {
if (!isEffectivelyInstantiable(subtype)) {
diag.addMessage(LocAddendum.typeNotClass().format({ type: printType(subtype) }));
}
});
}
addDiagnostic(
DiagnosticRule.reportGeneralTypeIssues,
LocMessage.typeExpectedClass().format({ type: printType(type) }) + diag.getString(),
node
);
}
function assignTypeToNameNode(
nameNode: NameNode,
typeResult: TypeResult,
@ -14912,9 +14911,6 @@ export function createTypeEvaluator(
let typeArg0Type = typeArgs[0].type;
if (!validateTypeArg(typeArgs[0])) {
typeArg0Type = UnknownType.create();
} else if (!isEffectivelyInstantiable(typeArg0Type)) {
addExpectedClassDiagnostic(typeArg0Type, typeArgs[0].node);
typeArg0Type = UnknownType.create();
}
let optionalType = combineTypes([typeArg0Type, noneTypeClass ?? UnknownType.create()]);
@ -15656,9 +15652,6 @@ export function createTypeEvaluator(
})
) {
typeArgType = UnknownType.create();
} else if (!isEffectivelyInstantiable(typeArgType)) {
addExpectedClassDiagnostic(typeArgType, typeArg.node);
typeArgType = UnknownType.create();
}
// If this is an unpacked tuple, explode out the individual items.

View File

@ -277,6 +277,10 @@ export interface RequiresSpecializationOptions {
ignoreImplicitTypeArgs?: boolean;
}
export interface IsInstantiableOptions {
honorTypeVarBounds?: boolean;
}
// Tracks whether a function signature has been seen before within
// an expression. For example, in the expression "foo(foo, foo)", the
// signature for "foo" will be seen three times at three different
@ -2352,11 +2356,23 @@ export function isMetaclassInstance(type: Type): boolean {
);
}
export function isEffectivelyInstantiable(type: Type): boolean {
export function isEffectivelyInstantiable(type: Type, options?: IsInstantiableOptions, recursionCount = 0): boolean {
if (recursionCount > maxTypeRecursionCount) {
return false;
}
recursionCount++;
if (TypeBase.isInstantiable(type)) {
return true;
}
if (options?.honorTypeVarBounds && isTypeVar(type) && type.details.boundType) {
if (isEffectivelyInstantiable(type.details.boundType, options, recursionCount)) {
return true;
}
}
// Handle the special case of 'type' (or subclasses thereof),
// which are instantiable.
if (isMetaclassInstance(type)) {
@ -2364,7 +2380,7 @@ export function isEffectivelyInstantiable(type: Type): boolean {
}
if (isUnion(type)) {
return type.subtypes.every((subtype) => isEffectivelyInstantiable(subtype));
return type.subtypes.every((subtype) => isEffectivelyInstantiable(subtype, options, recursionCount));
}
return false;

View File

@ -503,7 +503,7 @@
"typedDictSecondArgDict": "Expected dict or keyword parameter as second parameter",
"typedDictSecondArgDictEntry": "Expected simple dictionary entry",
"typedDictSet": "Could not assign item in TypedDict",
"typeExpectedClass": "Expected type expression but received \"{type}\"",
"typeExpectedClass": "Expected class but received \"{type}\"",
"typeGuardArgCount": "Expected a single type argument after \"TypeGuard\" or \"TypeIs\"",
"typeGuardParamCount": "User-defined type guard functions and methods must have at least one input parameter",
"typeIsReturnType": "Return type of TypeIs (\"{returnType}\") is not consistent with value parameter type (\"{type}\")",

View File

@ -6,6 +6,7 @@ from typing import Any, TypeVar
T = TypeVar("T")
T2 = TypeVar("T2", bound=type[Any])
class A:
@ -77,3 +78,10 @@ class L(type[T]):
def func2(cls: type[T]):
class M(cls):
pass
def func3(cls: T2) -> T2:
class M(cls):
pass
return M