From 29b9759e1af2ba888cd5e77b508ce955b83c2302 Mon Sep 17 00:00:00 2001 From: detachhead Date: Mon, 7 Oct 2024 14:33:22 +1000 Subject: [PATCH] workaround for fake typevars in `__init__` methods when determining whether to use variance to specialize type vars when narrowing --- .../src/analyzer/patternMatching.ts | 5 ++--- .../pyright-internal/src/analyzer/typeGuards.ts | 8 ++------ .../pyright-internal/src/analyzer/typeUtils.ts | 11 +++++++++++ .../src/tests/samples/typeNarrowingUsingBounds.py | 14 ++++++++++++-- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/pyright-internal/src/analyzer/patternMatching.ts b/packages/pyright-internal/src/analyzer/patternMatching.ts index fb0df045e6..46cf9e195b 100644 --- a/packages/pyright-internal/src/analyzer/patternMatching.ts +++ b/packages/pyright-internal/src/analyzer/patternMatching.ts @@ -84,6 +84,7 @@ import { mapSubtypes, partiallySpecializeType, preserveUnknown, + shouldUseVarianceForSpecialization, specializeTupleClass, specializeWithUnknownTypeArgs, transformPossibleRecursiveTypeAlias, @@ -778,9 +779,7 @@ function narrowTypeBasedOnClassPattern( exprType = specializeWithUnknownTypeArgs( exprType, evaluator.getTupleClassType(), - // for backwards compatibility with bacly typed code, we don't specialize using variance if the type we're - // narrowing is Any/Unknown - isAnyOrUnknown(type) || isPartlyUnknown(type) ? undefined : evaluator.getObjectType() + shouldUseVarianceForSpecialization(type) ? evaluator.getObjectType() : undefined ); } diff --git a/packages/pyright-internal/src/analyzer/typeGuards.ts b/packages/pyright-internal/src/analyzer/typeGuards.ts index 9f62e2fcf2..afbaca9cfe 100644 --- a/packages/pyright-internal/src/analyzer/typeGuards.ts +++ b/packages/pyright-internal/src/analyzer/typeGuards.ts @@ -82,7 +82,6 @@ import { isMetaclassInstance, isNoneInstance, isNoneTypeClass, - isPartlyUnknown, isProperty, isTupleClass, isTupleGradualForm, @@ -92,6 +91,7 @@ import { makeTypeVarsFree, mapSubtypes, MemberAccessFlags, + shouldUseVarianceForSpecialization, specializeTupleClass, specializeWithUnknownTypeArgs, stripTypeForm, @@ -1135,11 +1135,7 @@ export function getIsInstanceClassTypes( ): (ClassType | TypeVarType | FunctionType)[] | undefined { let foundNonClassType = false; const classTypeList: (ClassType | TypeVarType | FunctionType)[] = []; - /** - * if the type we're narrowing is Any or Unknown, we don't want to specialize using the - * variance/bound for compatibility with less strictly typed code (cringe) - */ - const useVarianceForSpecialization = !isAnyOrUnknown(typeToNarrow) && !isPartlyUnknown(typeToNarrow); + const useVarianceForSpecialization = shouldUseVarianceForSpecialization(typeToNarrow); // Create a helper function that returns a list of class types or // undefined if any of the types are not valid. const addClassTypesToList = (types: Type[]) => { diff --git a/packages/pyright-internal/src/analyzer/typeUtils.ts b/packages/pyright-internal/src/analyzer/typeUtils.ts index 6299f21d14..e8ed9ea538 100644 --- a/packages/pyright-internal/src/analyzer/typeUtils.ts +++ b/packages/pyright-internal/src/analyzer/typeUtils.ts @@ -1081,6 +1081,17 @@ export function getTypeVarScopeIds(type: Type): TypeVarScopeId[] { return scopeIds; } +/** + * if the type we're narrowing is Any or Unknown, we don't want to specialize using the + * variance/bound for compatibility with less strictly typed code (cringe) + */ +export const shouldUseVarianceForSpecialization = (type: Type) => + !isAnyOrUnknown(type) && + !isPartlyUnknown(type) && + // TODO: this logic should probably be moved into `isAny`/`isUnknown` or something, + // to fix issues like https://github.com/DetachHead/basedpyright/issues/746 + (type.category !== TypeCategory.TypeVar || !type.shared.isSynthesized); + /** * Specializes the class with "Unknown" type args (or the equivalent for ParamSpecs or TypeVarTuples), or its * widest possible type if its variance is known and {@link objectTypeForVarianceCheck} is provided (`object` if diff --git a/packages/pyright-internal/src/tests/samples/typeNarrowingUsingBounds.py b/packages/pyright-internal/src/tests/samples/typeNarrowingUsingBounds.py index c7e2f68a29..90b476b6dc 100644 --- a/packages/pyright-internal/src/tests/samples/typeNarrowingUsingBounds.py +++ b/packages/pyright-internal/src/tests/samples/typeNarrowingUsingBounds.py @@ -44,15 +44,25 @@ def foo(value: object): class AnyOrUnknown: """for backwards compatibility with badly typed code we keep the old functionality when narrowing `Any`/Unknown""" - def foo(self, value: Any): + def __init__(self, value): + """arguments in `__init__` get turned into fake type vars if they're untyped, so we need to handle this case. + see https://github.com/DetachHead/basedpyright/issues/746""" if isinstance(value, Iterable): assert_type(value, Iterable[Any]) - def bar(self, value: Any): + def any(self, value: Any): + if isinstance(value, Iterable): + assert_type(value, Iterable[Any]) + + def match_case(self, value: Any): match value: case Iterable(): assert_type(value, Iterable[Any]) + def unknown(self, value): + if isinstance(value, Iterable): + assert_type(value, Iterable[Any]) + def partially_unknown(self, value=None): if isinstance(value, Iterable): assert_type(value, Iterable[Any])