Skip to content

Commit

Permalink
stubtest: distinguish metaclass attributes from class attributes (#18314
Browse files Browse the repository at this point in the history
)

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.
  • Loading branch information
tungol authored Dec 20, 2024
1 parent f445369 commit d33cef8
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 0 deletions.
8 changes: 8 additions & 0 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,13 @@ def verify_typeinfo(
# Catch all exceptions in case the runtime raises an unexpected exception
# from __getattr__ or similar.
continue

# If it came from the metaclass, consider the runtime_attr to be MISSING
# for a more accurate message
if runtime_attr is not MISSING and type(runtime) is not runtime:
if getattr(runtime_attr, "__objclass__", None) is type(runtime):
runtime_attr = MISSING

# Do not error for an object missing from the stub
# If the runtime object is a types.WrapperDescriptorType object
# and has a non-special dunder name.
Expand Down Expand Up @@ -1519,6 +1526,7 @@ def is_probably_a_function(runtime: Any) -> bool:
isinstance(runtime, (types.FunctionType, types.BuiltinFunctionType))
or isinstance(runtime, (types.MethodType, types.BuiltinMethodType))
or (inspect.ismethoddescriptor(runtime) and callable(runtime))
or (isinstance(runtime, types.MethodWrapperType) and callable(runtime))
)


Expand Down
10 changes: 10 additions & 0 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1460,6 +1460,16 @@ def h(x: str): ...
runtime="__all__ += ['Z']\nclass Z:\n def __reduce__(self): return (Z,)",
error=None,
)
# __call__ exists on type, so it appears to exist on the class.
# This checks that we identify it as missing at runtime anyway.
yield Case(
stub="""
class ClassWithMetaclassOverride:
def __call__(*args, **kwds): ...
""",
runtime="class ClassWithMetaclassOverride: ...",
error="ClassWithMetaclassOverride.__call__",
)

@collect_cases
def test_missing_no_runtime_all(self) -> Iterator[Case]:
Expand Down

0 comments on commit d33cef8

Please # to comment.