Skip to content

stubtest: analyze metaclass of types, refs #13327 #13331

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,40 @@ class SubClass(runtime): # type: ignore
# Examples: ctypes.Array, ctypes._SimpleCData
pass

# Check metaclass. We exclude protocols, because of how complex
# their implementation is in different versions of python.
# Enums are also hard, ignoring.
if not stub.is_protocol and not stub.is_enum:
runtime_metaclass = type(runtime)
if runtime_metaclass is not type and stub.metaclass_type is None:
# This means that runtime has a custom metaclass, but a stub does not.
yield Error(
object_path,
"metaclass missmatch",
stub,
runtime,
stub_desc="Missing metaclass",
runtime_desc=f"Exiting metaclass: {runtime_metaclass}",
)
elif (
runtime_metaclass is type
and stub.metaclass_type is not None
# We ignore extra `ABCMeta` metaclass on stubs, this might be typing hack.
# We also ignore `builtins.type` metaclass as an implementation detail in mypy.
and not mypy.types.is_named_instance(
stub.metaclass_type, ("abc.ABCMeta", "builtins.type")
)
):
# This means that our stub has a metaclass that is not present at runtime.
yield Error(
object_path,
"metaclass missmatch",
stub,
runtime,
stub_desc=f"Existing metaclass: {stub.metaclass_type.type.fullname}",
runtime_desc="Missing metaclass",
)

# Check everything already defined on the stub class itself (i.e. not inherited)
to_check = set(stub.names)
# Check all public things on the runtime class
Expand Down
50 changes: 50 additions & 0 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,56 @@ def test_type_var(self) -> Iterator[Case]:
)
yield Case(stub="C = ParamSpec('C')", runtime="C = ParamSpec('C')", error=None)

@collect_cases
def test_metaclass_match(self) -> Iterator[Case]:
yield Case(stub="class Meta(type): ...", runtime="class Meta(type): ...", error=None)
yield Case(stub="class A0: ...", runtime="class A0: ...", error=None)
yield Case(
stub="class A1(metaclass=Meta): ...",
runtime="class A1(metaclass=Meta): ...",
error=None,
)
yield Case(stub="class A2: ...", runtime="class A2(metaclass=Meta): ...", error="A2")
yield Case(stub="class A3(metaclass=Meta): ...", runtime="class A3: ...", error="A3")

# With inheritance:
yield Case(
stub="""
class I1(metaclass=Meta): ...
class S1(I1): ...
""",
runtime="""
class I1(metaclass=Meta): ...
class S1(I1): ...
""",
error=None,
)
yield Case(
stub="""
class I2(metaclass=Meta): ...
class S2: ... # missing inheritance
""",
runtime="""
class I2(metaclass=Meta): ...
class S2(I2): ...
""",
error="S2",
)

@collect_cases
def test_metaclass_abcmeta(self) -> Iterator[Case]:
# Handling abstract metaclasses is special:
yield Case(stub="from abc import ABCMeta", runtime="from abc import ABCMeta", error=None)
yield Case(
stub="class A1(metaclass=ABCMeta): ...",
runtime="class A1(metaclass=ABCMeta): ...",
error=None,
)
# Stubs cannot miss abstract metaclass:
yield Case(stub="class A2: ...", runtime="class A2(metaclass=ABCMeta): ...", error="A2")
# But, stubs can add extra abstract metaclass, this might be a typing hack:
yield Case(stub="class A3(metaclass=ABCMeta): ...", runtime="class A3: ...", error=None)


def remove_color_code(s: str) -> str:
return re.sub("\\x1b.*?m", "", s) # this works!
Expand Down