Skip to content

Commit d33cef8

Browse files
authored
stubtest: distinguish metaclass attributes from class attributes (#18314)
If the runtime attribute of a class is actually from the metaclass, consider it to be MISSING at runtime. This only occurs a couple times in the stdlib: it shows up when a descriptor is present on the metaclass but not the class, and we want to lie in the stub and say it's a thing on the class anyway. I found this after noticing that `enum.auto.__or__` had a comment that said it didn't exist at runtime, but stubtest thought that it actually did. The issue is that on 3.10+, `type.__or__` is defined for the purpose of Union types, and stubtest doesn't know the difference between `type.__or__` and `__or__` on the actual class. Currently this matches on these things in typeshed's stdlib: ``` abc.ABCMeta.__abstractmethods__ builtins.object.__annotations__ enum.auto.__or__ enum.auto.__ror__ types.NotImplementedType.__call__ ``` This MR doesn't resolve any allowlist entries for typeshed, and it doesn't create any new ones either, but should generate more accurate error messages in this particular edge case.
1 parent f445369 commit d33cef8

File tree

2 files changed

+18
-0
lines changed

2 files changed

+18
-0
lines changed

mypy/stubtest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,13 @@ def verify_typeinfo(
568568
# Catch all exceptions in case the runtime raises an unexpected exception
569569
# from __getattr__ or similar.
570570
continue
571+
572+
# If it came from the metaclass, consider the runtime_attr to be MISSING
573+
# for a more accurate message
574+
if runtime_attr is not MISSING and type(runtime) is not runtime:
575+
if getattr(runtime_attr, "__objclass__", None) is type(runtime):
576+
runtime_attr = MISSING
577+
571578
# Do not error for an object missing from the stub
572579
# If the runtime object is a types.WrapperDescriptorType object
573580
# and has a non-special dunder name.
@@ -1519,6 +1526,7 @@ def is_probably_a_function(runtime: Any) -> bool:
15191526
isinstance(runtime, (types.FunctionType, types.BuiltinFunctionType))
15201527
or isinstance(runtime, (types.MethodType, types.BuiltinMethodType))
15211528
or (inspect.ismethoddescriptor(runtime) and callable(runtime))
1529+
or (isinstance(runtime, types.MethodWrapperType) and callable(runtime))
15221530
)
15231531

15241532

mypy/test/teststubtest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,6 +1460,16 @@ def h(x: str): ...
14601460
runtime="__all__ += ['Z']\nclass Z:\n def __reduce__(self): return (Z,)",
14611461
error=None,
14621462
)
1463+
# __call__ exists on type, so it appears to exist on the class.
1464+
# This checks that we identify it as missing at runtime anyway.
1465+
yield Case(
1466+
stub="""
1467+
class ClassWithMetaclassOverride:
1468+
def __call__(*args, **kwds): ...
1469+
""",
1470+
runtime="class ClassWithMetaclassOverride: ...",
1471+
error="ClassWithMetaclassOverride.__call__",
1472+
)
14631473

14641474
@collect_cases
14651475
def test_missing_no_runtime_all(self) -> Iterator[Case]:

0 commit comments

Comments
 (0)