From 2916bb2f13a9af41f1f69dfb5ec8e043c380a3ba Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Thu, 12 Oct 2023 08:51:54 -0700 Subject: [PATCH] Changed type evaluation behavior for accesses to attributes on a class that derives from `Any`. Previously, these were evaluated as `Unknown`, but they are now evaluated as `Any`. This is related to #6136. (#6137) Co-authored-by: Eric Traut --- .../src/analyzer/typeEvaluator.ts | 5 +++++ .../src/analyzer/typeEvaluatorTypes.ts | 4 ++-- .../src/analyzer/typeUtils.ts | 12 +++++------ .../src/tests/samples/memberAccess24.py | 20 +++++++++++++++++++ .../src/tests/typeEvaluator4.test.ts | 5 +++++ 5 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 packages/pyright-internal/src/tests/samples/memberAccess24.py diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index e81c6c3bdeac..7761d27e5a8f 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -21519,6 +21519,11 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions isIncomplete: !!typeResult.isIncomplete, }; } + } else if (isAnyOrUnknown(member.classType)) { + return { + type: member.classType, + isIncomplete: false, + }; } return undefined; diff --git a/packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts b/packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts index 76c68bbd05ee..d4507e48de20 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts @@ -180,7 +180,7 @@ export interface TypeResult { // Used for getTypeOfObjectMember to indicate that class // that declares the member. - classType?: ClassType | UnknownType; + classType?: ClassType | UnknownType | AnyType; // Variadic type arguments allow the shorthand "()" to // represent an empty tuple (i.e. Tuple[()]). @@ -390,7 +390,7 @@ export interface ClassMemberLookup { isClassMember: boolean; // The class that declares the accessed member. - classType?: ClassType | UnknownType; + classType?: ClassType | UnknownType | AnyType; // True if the member is explicitly declared as ClassVar // within a Protocol. diff --git a/packages/pyright-internal/src/analyzer/typeUtils.ts b/packages/pyright-internal/src/analyzer/typeUtils.ts index 3725f161a1f0..b09687e0f58a 100644 --- a/packages/pyright-internal/src/analyzer/typeUtils.ts +++ b/packages/pyright-internal/src/analyzer/typeUtils.ts @@ -71,7 +71,7 @@ export interface ClassMember { symbol: Symbol; // Partially-specialized class that contains the class member - classType: ClassType | UnknownType; + classType: ClassType | UnknownType | AnyType; // True if it is an instance or class member; it can be both a class and // an instance member in cases where a class variable is overridden @@ -1483,11 +1483,11 @@ export function* getClassMemberIterator( // The class derives from an unknown type, so all bets are off // when trying to find a member. Return an unknown symbol. const cm: ClassMember = { - symbol: Symbol.createWithType(SymbolFlags.None, UnknownType.create()), + symbol: Symbol.createWithType(SymbolFlags.None, mroClass), isInstanceMember: false, isClassMember: true, isClassVar: false, - classType: UnknownType.create(), + classType: isAnyOrUnknown(mroClass) ? mroClass : UnknownType.create(), isTypeDeclared: false, skippedUndeclaredType: false, }; @@ -1566,13 +1566,13 @@ export function* getClassMemberIterator( } } else if (isAnyOrUnknown(classType)) { // The class derives from an unknown type, so all bets are off - // when trying to find a member. Return an unknown symbol. + // when trying to find a member. Return an Any or Unknown symbol. const cm: ClassMember = { - symbol: Symbol.createWithType(SymbolFlags.None, UnknownType.create()), + symbol: Symbol.createWithType(SymbolFlags.None, classType), isInstanceMember: false, isClassMember: true, isClassVar: false, - classType: UnknownType.create(), + classType, isTypeDeclared: false, skippedUndeclaredType: false, }; diff --git a/packages/pyright-internal/src/tests/samples/memberAccess24.py b/packages/pyright-internal/src/tests/samples/memberAccess24.py new file mode 100644 index 000000000000..4593704092fd --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/memberAccess24.py @@ -0,0 +1,20 @@ +# This sample tests the case where an attribute is accessed from a +# class that derives from an unknown type or Any. + +from typing import Any +from dummy import UnknownX # type: ignore + + +class DerivesFromUnknown(UnknownX): + pass + + +class DerivesFromAny(Any): + pass + + +v1 = DerivesFromUnknown().x +reveal_type(v1, expected_text="Unknown") + +v2 = DerivesFromAny().x +reveal_type(v2, expected_text="Any") diff --git a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts index d3c76a577a00..c16789ca1188 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts @@ -550,6 +550,11 @@ test('MemberAccess23', () => { TestUtils.validateResults(analysisResults, 0); }); +test('MemberAccess24', () => { + const analysisResults = TestUtils.typeAnalyzeSampleFiles(['memberAccess24.py']); + TestUtils.validateResults(analysisResults, 0); +}); + test('DataClassNamedTuple1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['dataclassNamedTuple1.py']);