Skip to content

Commit

Permalink
stubtest: error if a class should be decorated with @Final (#12091)
Browse files Browse the repository at this point in the history
  • Loading branch information
Akuli authored Jan 30, 2022
1 parent 8f9ebf0 commit 080bb0e
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 2 deletions.
18 changes: 18 additions & 0 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,24 @@ def verify_typeinfo(
yield Error(object_path, "is not a type", stub, runtime, stub_desc=repr(stub))
return

try:
class SubClass(runtime): # type: ignore
pass
except TypeError:
# Enum classes are implicitly @final
if not stub.is_final and not issubclass(runtime, enum.Enum):
yield Error(
object_path,
"cannot be subclassed at runtime, but isn't marked with @final in the stub",
stub,
runtime,
stub_desc=repr(stub),
)
except Exception:
# The class probably wants its subclasses to do something special.
# Examples: ctypes.Array, ctypes._SimpleCData
pass

# Check everything already defined in the stub
to_check = set(stub.names)
# There's a reasonable case to be made that we should always check all dunders, but it's
Expand Down
31 changes: 29 additions & 2 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,8 +691,13 @@ def test_special_dunders(self) -> Iterator[Case]:
)
if sys.version_info >= (3, 6):
yield Case(
stub="class C:\n def __init_subclass__(cls, e: int, **kwargs: int) -> None: ...",
runtime="class C:\n def __init_subclass__(cls, e, **kwargs): pass",
stub=(
"class C:\n"
" def __init_subclass__(\n"
" cls, e: int = ..., **kwargs: int\n"
" ) -> None: ...\n"
),
runtime="class C:\n def __init_subclass__(cls, e=1, **kwargs): pass",
error=None,
)
if sys.version_info >= (3, 9):
Expand All @@ -702,6 +707,28 @@ def test_special_dunders(self) -> Iterator[Case]:
error=None,
)

def test_not_subclassable(self) -> None:
output = run_stubtest(
stub=(
"class CanBeSubclassed: ...\n"
"class CanNotBeSubclassed:\n"
" def __init_subclass__(cls) -> None: ...\n"
),
runtime=(
"class CanNotBeSubclassed:\n"
" def __init_subclass__(cls): raise TypeError('nope')\n"
# ctypes.Array can be subclassed, but subclasses must define a few
# special attributes, e.g. _length_
"from ctypes import Array as CanBeSubclassed\n"
),
options=[],
)
assert (
"CanNotBeSubclassed cannot be subclassed at runtime,"
" but isn't marked with @final in the stub"
) in output
assert "CanBeSubclassed cannot be subclassed" not in output

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

0 comments on commit 080bb0e

Please sign in to comment.