Skip to content

Commit 83fed5a

Browse files
authored
Skip more method bodies in third-party libraries (#19586)
A while ago we started stripping function bodies when checking third-party libraries. This PR pushes this idea further: * Tighten the check in `fastparse.py` to only consider `foo.bar` as possible self attribute definition. * Do not type-check bodies where we didn't find any `self` attribute _definitions_ during semantic analysis. * Skip method override checks in third-party libraries. In total this makes e.g. `mypy -c 'import torch'` ~10% faster. Surprisingly, this also has some visible impact on self-check.
1 parent 3387d6f commit 83fed5a

File tree

4 files changed

+27
-4
lines changed

4 files changed

+27
-4
lines changed

mypy/checker.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -832,8 +832,10 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
832832
# At this point we should have set the impl already, and all remaining
833833
# items are decorators
834834

835-
if self.msg.errors.file in self.msg.errors.ignored_files or (
836-
self.is_typeshed_stub and self.options.test_env
835+
if (
836+
self.options.ignore_errors
837+
or self.msg.errors.file in self.msg.errors.ignored_files
838+
or (self.is_typeshed_stub and self.options.test_env)
837839
):
838840
# This is a little hacky, however, the quadratic check here is really expensive, this
839841
# method has no side effects, so we should skip it if we aren't going to report
@@ -1444,7 +1446,19 @@ def check_func_def(
14441446
# TODO: Find a way of working around this limitation
14451447
if _is_empty_generator_function(item) or len(expanded) >= 2:
14461448
self.binder.suppress_unreachable_warnings()
1447-
self.accept(item.body)
1449+
# When checking a third-party library, we can skip function body,
1450+
# if during semantic analysis we found that there are no attributes
1451+
# defined via self here.
1452+
if (
1453+
not (
1454+
self.options.ignore_errors
1455+
or self.msg.errors.file in self.msg.errors.ignored_files
1456+
)
1457+
or self.options.preserve_asts
1458+
or not isinstance(defn, FuncDef)
1459+
or defn.has_self_attr_def
1460+
):
1461+
self.accept(item.body)
14481462
unreachable = self.binder.is_unreachable()
14491463
if new_frame is not None:
14501464
self.binder.pop_frame(True, 0)
@@ -2127,6 +2141,9 @@ def check_method_override(
21272141
21282142
Return a list of base classes which contain an attribute with the method name.
21292143
"""
2144+
if self.options.ignore_errors or self.msg.errors.file in self.msg.errors.ignored_files:
2145+
# Method override checks may be expensive, so skip them in third-party libraries.
2146+
return None
21302147
# Check against definitions in base classes.
21312148
check_override_compatibility = (
21322149
defn.name not in ("__init__", "__new__", "__init_subclass__", "__post_init__")

mypy/fastparse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2234,7 +2234,7 @@ def visit_index_expr(self, e: IndexExpr) -> None:
22342234
pass
22352235

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

22402240

mypy/nodes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,7 @@ class FuncDef(FuncItem, SymbolNode, Statement):
818818
"original_def",
819819
"is_trivial_body",
820820
"is_trivial_self",
821+
"has_self_attr_def",
821822
"is_mypy_only",
822823
# Present only when a function is decorated with @typing.dataclass_transform or similar
823824
"dataclass_transform_spec",
@@ -856,6 +857,8 @@ def __init__(
856857
# the majority). In cases where self is not annotated and there are no Self
857858
# in the signature we can simply drop the first argument.
858859
self.is_trivial_self = False
860+
# Keep track of functions where self attributes are defined.
861+
self.has_self_attr_def = False
859862
# This is needed because for positional-only arguments the name is set to None,
860863
# but we sometimes still want to show it in error messages.
861864
if arguments:

mypy/semanal.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4570,6 +4570,9 @@ def analyze_member_lvalue(
45704570
lval.node = v
45714571
# TODO: should we also set lval.kind = MDEF?
45724572
self.type.names[lval.name] = SymbolTableNode(MDEF, v, implicit=True)
4573+
for func in self.scope.functions:
4574+
if isinstance(func, FuncDef):
4575+
func.has_self_attr_def = True
45734576
self.check_lvalue_validity(lval.node, lval)
45744577

45754578
def is_self_member_ref(self, memberexpr: MemberExpr) -> bool:

0 commit comments

Comments
 (0)