diff --git a/mypy/stubtest.py b/mypy/stubtest.py index d16e491fb1ab..ef8c8dc318e1 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -506,9 +506,13 @@ def _verify_metaclass( @verify.register(nodes.TypeInfo) def verify_typeinfo( - stub: nodes.TypeInfo, runtime: MaybeMissing[type[Any]], object_path: list[str] + stub: nodes.TypeInfo, + runtime: MaybeMissing[type[Any]], + object_path: list[str], + *, + is_alias_target: bool = False, ) -> Iterator[Error]: - if stub.is_type_check_only: + if stub.is_type_check_only and not is_alias_target: # This type only exists in stubs, we only check that the runtime part # is missing. Other checks are not required. if not isinstance(runtime, Missing): @@ -1449,7 +1453,7 @@ def verify_typealias( # Okay, either we couldn't construct a fullname # or the fullname of the stub didn't match the fullname of the runtime. # Fallback to a full structural check of the runtime vis-a-vis the stub. - yield from verify(stub_origin, runtime_origin, object_path) + yield from verify_typeinfo(stub_origin, runtime_origin, object_path, is_alias_target=True) return if isinstance(stub_target, mypy.types.UnionType): # complain if runtime is not a Union or UnionType diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 7925f2a6bd3e..e18a35197070 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -2468,6 +2468,17 @@ def func2() -> None: ... runtime="def func2() -> None: ...", error="func2", ) + # A type that exists at runtime is allowed to alias a type marked + # as '@type_check_only' in the stubs. + yield Case( + stub=""" + @type_check_only + class _X1: ... + X2 = _X1 + """, + runtime="class X2: ...", + error=None, + ) def remove_color_code(s: str) -> str: