Skip to content

Commit

Permalink
[stubtest] Respect @final runtime decorator, enforce it in stubs (#…
Browse files Browse the repository at this point in the history
…14922)

Now, runtime code like

```python
from typing import final

@Final
class A: ...
``` 

Will require `@final` decorator in stubs as well.

Closes #14921
  • Loading branch information
sobolevn committed Mar 19, 2023
1 parent fccaab0 commit e07ccde
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 1 deletion.
17 changes: 16 additions & 1 deletion mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,21 @@ class SubClass(runtime): # type: ignore[misc]
# Examples: ctypes.Array, ctypes._SimpleCData
pass

# Runtime class might be annotated with `@final`:
try:
runtime_final = getattr(runtime, "__final__", False)
except Exception:
runtime_final = False

if runtime_final and not stub.is_final:
yield Error(
object_path,
"has `__final__` attribute, but isn't marked with @final in the stub",
stub,
runtime,
stub_desc=repr(stub),
)


def _verify_metaclass(
stub: nodes.TypeInfo, runtime: type[Any], object_path: list[str]
Expand Down Expand Up @@ -1339,7 +1354,7 @@ def verify_typealias(
"__origin__",
"__args__",
"__orig_bases__",
"__final__",
"__final__", # Has a specialized check
# Consider removing __slots__?
"__slots__",
}
Expand Down
39 changes: 39 additions & 0 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,45 @@ def test_not_subclassable(self) -> Iterator[Case]:
error="CannotBeSubclassed",
)

@collect_cases
def test_has_runtime_final_decorator(self) -> Iterator[Case]:
yield Case(
stub="from typing_extensions import final",
runtime="from typing_extensions import final",
error=None,
)
yield Case(
stub="""
@final
class A: ...
""",
runtime="""
@final
class A: ...
""",
error=None,
)
yield Case( # Runtime can miss `@final` decorator
stub="""
@final
class B: ...
""",
runtime="""
class B: ...
""",
error=None,
)
yield Case( # Stub cannot miss `@final` decorator
stub="""
class C: ...
""",
runtime="""
@final
class C: ...
""",
error="C",
)

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

0 comments on commit e07ccde

Please sign in to comment.