Skip to content

Skip more method bodies in third-party libraries #19586

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

Merged
merged 1 commit into from
Aug 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,8 +832,10 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
# At this point we should have set the impl already, and all remaining
# items are decorators

if self.msg.errors.file in self.msg.errors.ignored_files or (
self.is_typeshed_stub and self.options.test_env
if (
self.options.ignore_errors
or self.msg.errors.file in self.msg.errors.ignored_files
or (self.is_typeshed_stub and self.options.test_env)
):
# This is a little hacky, however, the quadratic check here is really expensive, this
# method has no side effects, so we should skip it if we aren't going to report
Expand Down Expand Up @@ -1444,7 +1446,19 @@ def check_func_def(
# TODO: Find a way of working around this limitation
if _is_empty_generator_function(item) or len(expanded) >= 2:
self.binder.suppress_unreachable_warnings()
self.accept(item.body)
# When checking a third-party library, we can skip function body,
# if during semantic analysis we found that there are no attributes
# defined via self here.
if (
not (
self.options.ignore_errors
or self.msg.errors.file in self.msg.errors.ignored_files
)
or self.options.preserve_asts
or not isinstance(defn, FuncDef)
or defn.has_self_attr_def
):
self.accept(item.body)
unreachable = self.binder.is_unreachable()
if new_frame is not None:
self.binder.pop_frame(True, 0)
Expand Down Expand Up @@ -2127,6 +2141,9 @@ def check_method_override(

Return a list of base classes which contain an attribute with the method name.
"""
if self.options.ignore_errors or self.msg.errors.file in self.msg.errors.ignored_files:
# Method override checks may be expensive, so skip them in third-party libraries.
return None
# Check against definitions in base classes.
check_override_compatibility = (
defn.name not in ("__init__", "__new__", "__init_subclass__", "__post_init__")
Expand Down
2 changes: 1 addition & 1 deletion mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -2234,7 +2234,7 @@ def visit_index_expr(self, e: IndexExpr) -> None:
pass

def visit_member_expr(self, e: MemberExpr) -> None:
if self.lvalue:
if self.lvalue and isinstance(e.expr, NameExpr):
self.found = True


Expand Down
3 changes: 3 additions & 0 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,7 @@ class FuncDef(FuncItem, SymbolNode, Statement):
"original_def",
"is_trivial_body",
"is_trivial_self",
"has_self_attr_def",
"is_mypy_only",
# Present only when a function is decorated with @typing.dataclass_transform or similar
"dataclass_transform_spec",
Expand Down Expand Up @@ -856,6 +857,8 @@ def __init__(
# the majority). In cases where self is not annotated and there are no Self
# in the signature we can simply drop the first argument.
self.is_trivial_self = False
# Keep track of functions where self attributes are defined.
self.has_self_attr_def = False
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wow, I love this. Can we use this flag later to fix overly optimistic narrowing like in #17537? (even just "this method has an explicit assignment to self" would be better than nothing, but even tracking nested method calls should be doable)

Copy link
Member Author

Choose a reason for hiding this comment

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

FWIW we can, but with great care (it is one thing to do something as an optimization, and another thing to do the same for correctness).

# This is needed because for positional-only arguments the name is set to None,
# but we sometimes still want to show it in error messages.
if arguments:
Expand Down
3 changes: 3 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4570,6 +4570,9 @@ def analyze_member_lvalue(
lval.node = v
# TODO: should we also set lval.kind = MDEF?
self.type.names[lval.name] = SymbolTableNode(MDEF, v, implicit=True)
for func in self.scope.functions:
if isinstance(func, FuncDef):
func.has_self_attr_def = True
self.check_lvalue_validity(lval.node, lval)

def is_self_member_ref(self, memberexpr: MemberExpr) -> bool:
Expand Down
Loading