Skip to content

Commit

Permalink
errors: speedup for large error counts and improve error filtering (#…
Browse files Browse the repository at this point in the history
…12631)

* errors: speedup for large error counts

We have a legacy codebase with many errors across many files
```
Found 7995 errors in 2218 files (checked 21364 source files)
```

For historical reasons, it hasn't been practical to fix all of
these yet, and we've been slowly chipping at them over time.

Profiling shows that `is_blockers` is the biggest single hotspot,
taking roughly 1min, and `total_errors` account for another 11s.

Instead of computing those values on read by iterating over all
errors, update auxiliary variables appropriately every time a
new error is recorded.

* errors: unify and optimize error filtering

Instead of maintaining two separate mechanism to filter out errors (boolean flag
in MessageBuilder and explicit copy of MessageBuilder/Errors) expand ErrorWatcher
to support all relevant usage patterns and update all usages accordingly.

This is both cleaner and more robust than the previous approach, and should also
offer a slight performance improvement by reducing allocations.
  • Loading branch information
hugues-aff authored Apr 29, 2022
1 parent 3460717 commit a3abd36
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 240 deletions.
51 changes: 23 additions & 28 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2196,7 +2196,7 @@ def is_raising_or_empty(self, s: Statement) -> bool:
if isinstance(s.expr, EllipsisExpr):
return True
elif isinstance(s.expr, CallExpr):
with self.expr_checker.msg.disable_errors():
with self.expr_checker.msg.filter_errors():
typ = get_proper_type(self.expr_checker.accept(
s.expr, allow_none_return=True, always_allow_any=True))

Expand Down Expand Up @@ -3377,7 +3377,7 @@ def check_member_assignment(self, instance_type: Type, attribute_type: Type,
# For non-overloaded setters, the result should be type-checked like a regular assignment.
# Hence, we first only try to infer the type by using the rvalue as type context.
type_context = rvalue
with self.msg.disable_errors():
with self.msg.filter_errors():
_, inferred_dunder_set_type = self.expr_checker.check_call(
dunder_set_type,
[TempNode(instance_type, context=context), type_context],
Expand Down Expand Up @@ -4312,34 +4312,29 @@ def _make_fake_typeinfo_and_full_name(
)
return info_, full_name_

old_msg = self.msg
new_msg = old_msg.clean_copy()
self.msg = new_msg
base_classes = _get_base_classes(instances)
# We use the pretty_names_list for error messages but can't
# use it for the real name that goes into the symbol table
# because it can have dots in it.
pretty_names_list = pretty_seq(format_type_distinctly(*base_classes, bare=True), "and")
try:
info, full_name = _make_fake_typeinfo_and_full_name(base_classes, curr_module)
self.check_multiple_inheritance(info)
if new_msg.is_errors():
with self.msg.filter_errors() as local_errors:
self.check_multiple_inheritance(info)
if local_errors.has_new_errors():
# "class A(B, C)" unsafe, now check "class A(C, B)":
new_msg = new_msg.clean_copy()
self.msg = new_msg
base_classes = _get_base_classes(instances[::-1])
info, full_name = _make_fake_typeinfo_and_full_name(base_classes, curr_module)
self.check_multiple_inheritance(info)
with self.msg.filter_errors() as local_errors:
self.check_multiple_inheritance(info)
info.is_intersection = True
except MroError:
if self.should_report_unreachable_issues():
old_msg.impossible_intersection(
self.msg.impossible_intersection(
pretty_names_list, "inconsistent method resolution order", ctx)
return None
finally:
self.msg = old_msg

if new_msg.is_errors():
if local_errors.has_new_errors():
if self.should_report_unreachable_issues():
self.msg.impossible_intersection(
pretty_names_list, "incompatible method signatures", ctx)
Expand Down Expand Up @@ -4974,20 +4969,20 @@ def refine_parent_types(self,
member_name = expr.name

def replay_lookup(new_parent_type: ProperType) -> Optional[Type]:
msg_copy = self.msg.clean_copy()
member_type = analyze_member_access(
name=member_name,
typ=new_parent_type,
context=parent_expr,
is_lvalue=False,
is_super=False,
is_operator=False,
msg=msg_copy,
original_type=new_parent_type,
chk=self,
in_literal_context=False,
)
if msg_copy.is_errors():
with self.msg.filter_errors() as w:
member_type = analyze_member_access(
name=member_name,
typ=new_parent_type,
context=parent_expr,
is_lvalue=False,
is_super=False,
is_operator=False,
msg=self.msg,
original_type=new_parent_type,
chk=self,
in_literal_context=False,
)
if w.has_new_errors():
return None
else:
return member_type
Expand Down
Loading

0 comments on commit a3abd36

Please sign in to comment.