From d6da783490ca693f0d5ce7be016e59b69d79e76c Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Tue, 18 Jul 2023 13:10:32 -0700 Subject: [PATCH] Fixed a bug that resulted in a false positive when an expression of type `type[Self]` was used as the base type for a member access expression that was then used to call an instance method on that class. This addresses #5530. --- .../src/analyzer/typeEvaluator.ts | 18 ++++++++++++++++-- .../src/tests/samples/memberAccess22.py | 15 +++++++++++++++ .../src/tests/typeEvaluator4.test.ts | 5 +++++ 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 packages/pyright-internal/src/tests/samples/memberAccess22.py diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index 9bc3c5f14f72..552b38ff44ad 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -5602,7 +5602,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions type: ClassType.isClassProperty(lookupClass) ? baseTypeClass : isAccessedThroughObject - ? bindToType || ClassType.cloneAsInstance(baseTypeClass) + ? bindToType ?? ClassType.cloneAsInstance(baseTypeClass) : NoneType.createInstance(), }, }, @@ -5801,6 +5801,20 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions // If this function is an instance member (e.g. a lambda that was // assigned to an instance variable), don't perform any binding. if (!isAccessedThroughObject || (memberInfo && !memberInfo.isInstanceMember)) { + let effectiveBindToType = bindToType; + + if (bindToType && !isInstantiableMetaclass(baseTypeClass)) { + // If bindToType is an instantiable class or TypeVar but we're targeting + // an instance method (in a non-metaclass), we need to convert + // the bindToType to an instance. + const targetMethod = isFunction(concreteSubtype) + ? concreteSubtype + : concreteSubtype.overloads[0]; + if (FunctionType.isInstanceMethod(targetMethod) && !TypeBase.isInstance(bindToType)) { + effectiveBindToType = convertToInstance(bindToType) as ClassType | TypeVarType; + } + } + return bindFunctionToClassOrObject( isAccessedThroughObject ? ClassType.cloneAsInstance(baseTypeClass) : baseTypeClass, concreteSubtype, @@ -5808,7 +5822,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions errorNode, /* recursionCount */ undefined, treatConstructorAsClassMember, - bindToType + effectiveBindToType ); } } diff --git a/packages/pyright-internal/src/tests/samples/memberAccess22.py b/packages/pyright-internal/src/tests/samples/memberAccess22.py new file mode 100644 index 000000000000..ad9289f21f9c --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/memberAccess22.py @@ -0,0 +1,15 @@ +# This sample tests the case where a `type[T]` or `type[Self]` typevar is +# used as the base for a member access but is then used to call an +# instance method on the resulting class. + +from contextlib import contextmanager + + +class A: + @classmethod + def method1(cls) -> None: + cls.method2 + + @contextmanager + def method2(self): + yield diff --git a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts index 0c3da0d7c573..56aa30bd197c 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts @@ -525,6 +525,11 @@ test('MemberAccess21', () => { TestUtils.validateResults(analysisResults, 1); }); +test('MemberAccess22', () => { + const analysisResults = TestUtils.typeAnalyzeSampleFiles(['memberAccess22.py']); + TestUtils.validateResults(analysisResults, 0); +}); + test('DataClassNamedTuple1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['dataclassNamedTuple1.py']);