Skip to content

Commit e07ccde

Browse files
authored
[stubtest] Respect @final runtime decorator, enforce it in stubs (#14922)
Now, runtime code like ```python from typing import final @Final class A: ... ``` Will require `@final` decorator in stubs as well. Closes #14921
1 parent fccaab0 commit e07ccde

File tree

2 files changed

+55
-1
lines changed

2 files changed

+55
-1
lines changed

mypy/stubtest.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,21 @@ class SubClass(runtime): # type: ignore[misc]
419419
# Examples: ctypes.Array, ctypes._SimpleCData
420420
pass
421421

422+
# Runtime class might be annotated with `@final`:
423+
try:
424+
runtime_final = getattr(runtime, "__final__", False)
425+
except Exception:
426+
runtime_final = False
427+
428+
if runtime_final and not stub.is_final:
429+
yield Error(
430+
object_path,
431+
"has `__final__` attribute, but isn't marked with @final in the stub",
432+
stub,
433+
runtime,
434+
stub_desc=repr(stub),
435+
)
436+
422437

423438
def _verify_metaclass(
424439
stub: nodes.TypeInfo, runtime: type[Any], object_path: list[str]
@@ -1339,7 +1354,7 @@ def verify_typealias(
13391354
"__origin__",
13401355
"__args__",
13411356
"__orig_bases__",
1342-
"__final__",
1357+
"__final__", # Has a specialized check
13431358
# Consider removing __slots__?
13441359
"__slots__",
13451360
}

mypy/test/teststubtest.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,45 @@ def test_not_subclassable(self) -> Iterator[Case]:
11391139
error="CannotBeSubclassed",
11401140
)
11411141

1142+
@collect_cases
1143+
def test_has_runtime_final_decorator(self) -> Iterator[Case]:
1144+
yield Case(
1145+
stub="from typing_extensions import final",
1146+
runtime="from typing_extensions import final",
1147+
error=None,
1148+
)
1149+
yield Case(
1150+
stub="""
1151+
@final
1152+
class A: ...
1153+
""",
1154+
runtime="""
1155+
@final
1156+
class A: ...
1157+
""",
1158+
error=None,
1159+
)
1160+
yield Case( # Runtime can miss `@final` decorator
1161+
stub="""
1162+
@final
1163+
class B: ...
1164+
""",
1165+
runtime="""
1166+
class B: ...
1167+
""",
1168+
error=None,
1169+
)
1170+
yield Case( # Stub cannot miss `@final` decorator
1171+
stub="""
1172+
class C: ...
1173+
""",
1174+
runtime="""
1175+
@final
1176+
class C: ...
1177+
""",
1178+
error="C",
1179+
)
1180+
11421181
@collect_cases
11431182
def test_name_mangling(self) -> Iterator[Case]:
11441183
yield Case(

0 commit comments

Comments
 (0)