diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index d337512574e4..95808457e3f4 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -2616,7 +2616,8 @@ export function createTypeEvaluator( effectiveFlags |= MemberAccessFlags.SkipClassMembers | MemberAccessFlags.SkipAttributeAccessOverride | - MemberAccessFlags.SkipTypeBaseClass; + MemberAccessFlags.SkipTypeBaseClass | + MemberAccessFlags.SkipBaseClasses; effectiveFlags &= ~MemberAccessFlags.SkipInstanceMembers; } diff --git a/packages/pyright-internal/src/analyzer/typeUtils.ts b/packages/pyright-internal/src/analyzer/typeUtils.ts index a396b944af8f..c756a9957f33 100644 --- a/packages/pyright-internal/src/analyzer/typeUtils.ts +++ b/packages/pyright-internal/src/analyzer/typeUtils.ts @@ -1773,7 +1773,11 @@ export function lookUpClassMember( // Skip the "type" class as an optimization because it is known to not // define any instance variables, and it's by far the most common metaclass. if (metaclass && isClass(metaclass) && !ClassType.isBuiltIn(metaclass, 'type')) { - const metaMemberItr = getClassMemberIterator(metaclass, memberName, MemberAccessFlags.SkipClassMembers); + let metaFlags = MemberAccessFlags.SkipClassMembers; + if (isClassInstance(classType) || (flags & MemberAccessFlags.SkipTypeBaseClass) !== 0) { + metaFlags |= MemberAccessFlags.SkipTypeBaseClass; + } + const metaMemberItr = getClassMemberIterator(metaclass, memberName, metaFlags); const metaMember = metaMemberItr.next()?.value; // If the metaclass defines the member and we didn't hit an Unknown @@ -2005,7 +2009,7 @@ export function* getClassIterator(classType: Type, flags = ClassIteratorFlags.De // Should we ignore members on the 'object' base class? if (flags & ClassIteratorFlags.SkipObjectBaseClass) { - if (isInstantiableClass(specializedMroClass)) { + if (isClass(specializedMroClass)) { if (ClassType.isBuiltIn(specializedMroClass, 'object')) { break; } @@ -2014,7 +2018,7 @@ export function* getClassIterator(classType: Type, flags = ClassIteratorFlags.De // Should we ignore members on the 'type' base class? if (flags & ClassIteratorFlags.SkipTypeBaseClass) { - if (isInstantiableClass(specializedMroClass)) { + if (isClass(specializedMroClass)) { if (ClassType.isBuiltIn(specializedMroClass, 'type')) { break; } diff --git a/packages/pyright-internal/src/tests/samples/metaclass12.py b/packages/pyright-internal/src/tests/samples/metaclass12.py new file mode 100644 index 000000000000..f2ba37def5b3 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/metaclass12.py @@ -0,0 +1,27 @@ +# This sample tests that class instances with a custom metaclass +# do not inherit members from the metaclass (such as type.__dict__). + +from typing import Any + + +class Meta(type): + def meta_method(self) -> str: + return "meta" + + +class A(metaclass=Meta): + pass + + +def func(a: A): + # This should resolve to dict[str, Any] from object, not MappingProxyType[str, Any] from type. + reveal_type(a.__dict__, expected_text="dict[str, Any]") + + # This should generate an error because meta_method is on the metaclass, + # not the instance. + a.meta_method() + + +# Access through the class itself should resolve via the metaclass. +reveal_type(A.__dict__, expected_text="MappingProxyType[str, Any]") +reveal_type(A.meta_method(), expected_text="str") diff --git a/packages/pyright-internal/src/tests/typeEvaluator1.test.ts b/packages/pyright-internal/src/tests/typeEvaluator1.test.ts index 4aa0fcdbe069..bb9f4f6c3b24 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator1.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator1.test.ts @@ -1200,6 +1200,11 @@ test('Metaclass11', () => { TestUtils.validateResults(analysisResults, 4); }); +test('Metaclass12', () => { + const analysisResults = TestUtils.typeAnalyzeSampleFiles(['metaclass12.py']); + TestUtils.validateResults(analysisResults, 1); +}); + test('AssignmentExpr1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['assignmentExpr1.py']); TestUtils.validateResults(analysisResults, 7);