Skip to content

Commit e9dc660

Browse files
committed
stubtest: analyze metaclass of types, refs #13327
1 parent 43476aa commit e9dc660

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed

mypy/stubtest.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,40 @@ class SubClass(runtime): # type: ignore
375375
# Examples: ctypes.Array, ctypes._SimpleCData
376376
pass
377377

378+
# Check metaclass. We exclude protocols, because of how complex
379+
# their implementation is in different versions of python.
380+
# Enums are also hard, ignoring.
381+
if not stub.is_protocol and not stub.is_enum:
382+
runtime_metaclass = type(runtime)
383+
if runtime_metaclass is not type and stub.metaclass_type is None:
384+
# This means that runtime has a custom metaclass, but a stub does not.
385+
yield Error(
386+
object_path,
387+
"metaclass missmatch",
388+
stub,
389+
runtime,
390+
stub_desc="Missing metaclass",
391+
runtime_desc=f"Exiting metaclass: {runtime_metaclass}",
392+
)
393+
elif (
394+
runtime_metaclass is type
395+
and stub.metaclass_type is not None
396+
# We ignore extra `ABCMeta` metaclass on stubs, this might be typing hack.
397+
# We also ignore `builtins.type` metaclass as an implementation detail in mypy.
398+
and not mypy.types.is_named_instance(
399+
stub.metaclass_type, ("abc.ABCMeta", "builtins.type")
400+
)
401+
):
402+
# This means that our stub has a metaclass that is not present at runtime.
403+
yield Error(
404+
object_path,
405+
"metaclass missmatch",
406+
stub,
407+
runtime,
408+
stub_desc=f"Existing metaclass: {stub.metaclass_type.type.fullname}",
409+
runtime_desc="Missing metaclass",
410+
)
411+
378412
# Check everything already defined on the stub class itself (i.e. not inherited)
379413
to_check = set(stub.names)
380414
# Check all public things on the runtime class

mypy/test/teststubtest.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,56 @@ def test_type_var(self) -> Iterator[Case]:
12641264
)
12651265
yield Case(stub="C = ParamSpec('C')", runtime="C = ParamSpec('C')", error=None)
12661266

1267+
@collect_cases
1268+
def test_metaclass_match(self) -> Iterator[Case]:
1269+
yield Case(stub="class Meta(type): ...", runtime="class Meta(type): ...", error=None)
1270+
yield Case(stub="class A0: ...", runtime="class A0: ...", error=None)
1271+
yield Case(
1272+
stub="class A1(metaclass=Meta): ...",
1273+
runtime="class A1(metaclass=Meta): ...",
1274+
error=None,
1275+
)
1276+
yield Case(stub="class A2: ...", runtime="class A2(metaclass=Meta): ...", error="A2")
1277+
yield Case(stub="class A3(metaclass=Meta): ...", runtime="class A3: ...", error="A3")
1278+
1279+
# With inheritance:
1280+
yield Case(
1281+
stub="""
1282+
class I1(metaclass=Meta): ...
1283+
class S1(I1): ...
1284+
""",
1285+
runtime="""
1286+
class I1(metaclass=Meta): ...
1287+
class S1(I1): ...
1288+
""",
1289+
error=None,
1290+
)
1291+
yield Case(
1292+
stub="""
1293+
class I2(metaclass=Meta): ...
1294+
class S2: ... # missing inheritance
1295+
""",
1296+
runtime="""
1297+
class I2(metaclass=Meta): ...
1298+
class S2(I2): ...
1299+
""",
1300+
error="S2",
1301+
)
1302+
1303+
@collect_cases
1304+
def test_metaclass_abcmeta(self) -> Iterator[Case]:
1305+
# Handling abstract metaclasses is special:
1306+
yield Case(stub="from abc import ABCMeta", runtime="from abc import ABCMeta", error=None)
1307+
yield Case(
1308+
stub="class A1(metaclass=ABCMeta): ...",
1309+
runtime="class A1(metaclass=ABCMeta): ...",
1310+
error=None,
1311+
)
1312+
# Stubs cannot miss abstract metaclass:
1313+
yield Case(stub="class A2: ...", runtime="class A2(metaclass=ABCMeta): ...", error="A2")
1314+
# But, stubs can add extra abstract metaclass, this might be a typing hack:
1315+
yield Case(stub="class A3(metaclass=ABCMeta): ...", runtime="class A3: ...", error=None)
1316+
12671317

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

0 commit comments

Comments
 (0)