Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor class decorator: this enables type_check_only support for TypedDict and NamedTuple #16469

Merged
merged 3 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 16 additions & 9 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1743,9 +1743,8 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool:
if is_typeddict:
for decorator in defn.decorators:
decorator.accept(self)
if isinstance(decorator, RefExpr):
if decorator.fullname in FINAL_DECORATOR_NAMES and info is not None:
info.is_final = True
if info is not None:
self.analyze_class_decorator_common(defn, info, decorator)
if info is None:
self.mark_incomplete(defn.name, defn)
else:
Expand Down Expand Up @@ -1781,8 +1780,7 @@ def analyze_namedtuple_classdef(
with self.scope.class_scope(defn.info):
for deco in defn.decorators:
deco.accept(self)
if isinstance(deco, RefExpr) and deco.fullname in FINAL_DECORATOR_NAMES:
info.is_final = True
self.analyze_class_decorator_common(defn, defn.info, deco)
with self.named_tuple_analyzer.save_namedtuple_body(info):
self.analyze_class_body_common(defn)
return True
Expand Down Expand Up @@ -1864,21 +1862,30 @@ def leave_class(self) -> None:

def analyze_class_decorator(self, defn: ClassDef, decorator: Expression) -> None:
decorator.accept(self)
self.analyze_class_decorator_common(defn, defn.info, decorator)
if isinstance(decorator, RefExpr):
if decorator.fullname in RUNTIME_PROTOCOL_DECOS:
if defn.info.is_protocol:
defn.info.runtime_protocol = True
else:
self.fail("@runtime_checkable can only be used with protocol classes", defn)
elif decorator.fullname in FINAL_DECORATOR_NAMES:
defn.info.is_final = True
elif refers_to_fullname(decorator, TYPE_CHECK_ONLY_NAMES):
defn.info.is_type_check_only = True
elif isinstance(decorator, CallExpr) and refers_to_fullname(
decorator.callee, DATACLASS_TRANSFORM_NAMES
):
defn.info.dataclass_transform_spec = self.parse_dataclass_transform_spec(decorator)

def analyze_class_decorator_common(
self, defn: ClassDef, info: TypeInfo, decorator: Expression
) -> None:
"""Common part for all class decorators.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Common part for all class decorators.
"""Common hook for applying class decorators.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Thanks, except it is not a hook. By hooks we often mean things like self.plugin.get_customize_class_mro_hook(defn.fullname)


Including classes, typeddicts, and namedtuples.
sobolevn marked this conversation as resolved.
Show resolved Hide resolved
"""
if refers_to_fullname(decorator, FINAL_DECORATOR_NAMES):
info.is_final = True
elif refers_to_fullname(decorator, TYPE_CHECK_ONLY_NAMES):
info.is_type_check_only = True

def clean_up_bases_and_infer_type_variables(
self, defn: ClassDef, base_type_exprs: list[Expression], context: Context
) -> tuple[list[Expression], list[TypeVarLikeType], bool]:
Expand Down
22 changes: 22 additions & 0 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2073,6 +2073,28 @@ class A2: ...
runtime="class A2: ...",
error="A2",
)
# The same is true for NamedTuples and TypedDicts:
yield Case(
stub="from typing_extensions import NamedTuple, TypedDict",
runtime="from typing_extensions import NamedTuple, TypedDict",
error=None,
)
yield Case(
stub="""
@type_check_only
class NT1(NamedTuple): ...
""",
runtime="class NT1(NamedTuple): ...",
error="NT1",
)
yield Case(
stub="""
@type_check_only
class TD1(TypedDict): ...
""",
runtime="class TD1(TypedDict): ...",
error="TD1",
)
# The same is true for functions:
yield Case(
stub="""
Expand Down