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

Avoid slow error message logic if errors not shown to user #14336

Merged
merged 4 commits into from
Dec 26, 2022
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
4 changes: 4 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -5936,6 +5936,10 @@ def check_subtype(
if isinstance(msg, str):
msg = ErrorMessage(msg, code=code)

if self.msg.prefer_simple_messages():
self.fail(msg, context) # Fast path -- skip all fancy logic
return False

orig_subtype = subtype
subtype = get_proper_type(subtype)
orig_supertype = supertype
Expand Down
3 changes: 2 additions & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2157,7 +2157,8 @@ def check_arg(
self.msg.incompatible_argument_note(
original_caller_type, callee_type, context, code=code
)
self.chk.check_possible_missing_await(caller_type, callee_type, context)
if not self.msg.prefer_simple_messages():
self.chk.check_possible_missing_await(caller_type, callee_type, context)

def check_overload_call(
self,
Expand Down
5 changes: 3 additions & 2 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,9 @@ def report_missing_attribute(
override_info: TypeInfo | None = None,
) -> Type:
res_type = mx.msg.has_no_attr(original_type, typ, name, mx.context, mx.module_symbol_table)
if may_be_awaitable_attribute(name, typ, mx, override_info):
mx.msg.possible_missing_await(mx.context)
if not mx.msg.prefer_simple_messages():
if may_be_awaitable_attribute(name, typ, mx, override_info):
mx.msg.possible_missing_await(mx.context)
return res_type


Expand Down
18 changes: 18 additions & 0 deletions mypy/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,24 @@ def is_errors_for_file(self, file: str) -> bool:
"""Are there any errors for the given file?"""
return file in self.error_info_map

def prefer_simple_messages(self) -> bool:
"""Should we generate simple/fast error messages?
Return True if errors are not shown to user, i.e. errors are ignored
or they are collected for internal use only.
If True, we should prefer to generate a simple message quickly.
All normal errors should still be reported.
"""
if self.file in self.ignored_files:
# Errors ignored, so no point generating fancy messages
return True
for _watcher in self._watchers:
if _watcher._filter is True and _watcher._filtered is None:
# Errors are filtered
return True
return False

def raise_error(self, use_stdout: bool = True) -> NoReturn:
"""Raise a CompileError with the generated messages.
Expand Down
133 changes: 79 additions & 54 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@ def disable_type_names(self) -> Iterator[None]:
def are_type_names_disabled(self) -> bool:
return len(self._disable_type_names) > 0 and self._disable_type_names[-1]

def prefer_simple_messages(self) -> bool:
"""Should we generate simple/fast error messages?
If errors aren't shown to the user, we don't want to waste cyles producing
complex error messages.
"""
return self.errors.prefer_simple_messages()

def report(
self,
msg: str,
Expand Down Expand Up @@ -685,64 +693,69 @@ def incompatible_argument(
actual_type_str, expected_type_str
)
else:
try:
expected_type = callee.arg_types[m - 1]
except IndexError: # Varargs callees
expected_type = callee.arg_types[-1]
arg_type_str, expected_type_str = format_type_distinctly(
arg_type, expected_type, bare=True
)
if arg_kind == ARG_STAR:
arg_type_str = "*" + arg_type_str
elif arg_kind == ARG_STAR2:
arg_type_str = "**" + arg_type_str

# For function calls with keyword arguments, display the argument name rather than the
# number.
arg_label = str(n)
if isinstance(outer_context, CallExpr) and len(outer_context.arg_names) >= n:
arg_name = outer_context.arg_names[n - 1]
if arg_name is not None:
arg_label = f'"{arg_name}"'
if (
arg_kind == ARG_STAR2
and isinstance(arg_type, TypedDictType)
and m <= len(callee.arg_names)
and callee.arg_names[m - 1] is not None
and callee.arg_kinds[m - 1] != ARG_STAR2
):
arg_name = callee.arg_names[m - 1]
assert arg_name is not None
arg_type_str, expected_type_str = format_type_distinctly(
arg_type.items[arg_name], expected_type, bare=True
)
arg_label = f'"{arg_name}"'
if isinstance(outer_context, IndexExpr) and isinstance(outer_context.index, StrExpr):
msg = 'Value of "{}" has incompatible type {}; expected {}'.format(
outer_context.index.value,
quote_type_string(arg_type_str),
quote_type_string(expected_type_str),
)
if self.prefer_simple_messages():
msg = "Argument has incompatible type"
else:
msg = "Argument {} {}has incompatible type {}; expected {}".format(
arg_label,
target,
quote_type_string(arg_type_str),
quote_type_string(expected_type_str),
try:
expected_type = callee.arg_types[m - 1]
except IndexError: # Varargs callees
expected_type = callee.arg_types[-1]
arg_type_str, expected_type_str = format_type_distinctly(
arg_type, expected_type, bare=True
)
if arg_kind == ARG_STAR:
arg_type_str = "*" + arg_type_str
elif arg_kind == ARG_STAR2:
arg_type_str = "**" + arg_type_str

# For function calls with keyword arguments, display the argument name rather
# than the number.
arg_label = str(n)
if isinstance(outer_context, CallExpr) and len(outer_context.arg_names) >= n:
arg_name = outer_context.arg_names[n - 1]
if arg_name is not None:
arg_label = f'"{arg_name}"'
if (
arg_kind == ARG_STAR2
and isinstance(arg_type, TypedDictType)
and m <= len(callee.arg_names)
and callee.arg_names[m - 1] is not None
and callee.arg_kinds[m - 1] != ARG_STAR2
):
arg_name = callee.arg_names[m - 1]
assert arg_name is not None
arg_type_str, expected_type_str = format_type_distinctly(
arg_type.items[arg_name], expected_type, bare=True
)
arg_label = f'"{arg_name}"'
if isinstance(outer_context, IndexExpr) and isinstance(
outer_context.index, StrExpr
):
msg = 'Value of "{}" has incompatible type {}; expected {}'.format(
outer_context.index.value,
quote_type_string(arg_type_str),
quote_type_string(expected_type_str),
)
else:
msg = "Argument {} {}has incompatible type {}; expected {}".format(
arg_label,
target,
quote_type_string(arg_type_str),
quote_type_string(expected_type_str),
)
expected_type = get_proper_type(expected_type)
if isinstance(expected_type, UnionType):
expected_types = list(expected_type.items)
else:
expected_types = [expected_type]
for type in get_proper_types(expected_types):
if isinstance(arg_type, Instance) and isinstance(type, Instance):
notes = append_invariance_notes(notes, arg_type, type)
object_type = get_proper_type(object_type)
if isinstance(object_type, TypedDictType):
code = codes.TYPEDDICT_ITEM
else:
code = codes.ARG_TYPE
expected_type = get_proper_type(expected_type)
if isinstance(expected_type, UnionType):
expected_types = list(expected_type.items)
else:
expected_types = [expected_type]
for type in get_proper_types(expected_types):
if isinstance(arg_type, Instance) and isinstance(type, Instance):
notes = append_invariance_notes(notes, arg_type, type)
self.fail(msg, context, code=code)
if notes:
for note_msg in notes:
Expand All @@ -756,6 +769,8 @@ def incompatible_argument_note(
context: Context,
code: ErrorCode | None,
) -> None:
if self.prefer_simple_messages():
return
if isinstance(
original_caller_type, (Instance, TupleType, TypedDictType, TypeType, CallableType)
):
Expand Down Expand Up @@ -832,7 +847,9 @@ def invalid_index_type(
def too_few_arguments(
self, callee: CallableType, context: Context, argument_names: Sequence[str | None] | None
) -> None:
if argument_names is not None:
if self.prefer_simple_messages():
msg = "Too few arguments"
elif argument_names is not None:
num_positional_args = sum(k is None for k in argument_names)
arguments_left = callee.arg_names[num_positional_args : callee.min_args]
diff = [k for k in arguments_left if k not in argument_names]
Expand All @@ -856,7 +873,10 @@ def missing_named_argument(self, callee: CallableType, context: Context, name: s
self.fail(msg, context, code=codes.CALL_ARG)

def too_many_arguments(self, callee: CallableType, context: Context) -> None:
msg = "Too many arguments" + for_function(callee)
if self.prefer_simple_messages():
msg = "Too many arguments"
else:
msg = "Too many arguments" + for_function(callee)
self.fail(msg, context, code=codes.CALL_ARG)
self.maybe_note_about_special_args(callee, context)

Expand All @@ -874,11 +894,16 @@ def too_many_arguments_from_typed_dict(
self.fail(msg, context)

def too_many_positional_arguments(self, callee: CallableType, context: Context) -> None:
msg = "Too many positional arguments" + for_function(callee)
if self.prefer_simple_messages():
msg = "Too many positional arguments"
else:
msg = "Too many positional arguments" + for_function(callee)
self.fail(msg, context)
self.maybe_note_about_special_args(callee, context)

def maybe_note_about_special_args(self, callee: CallableType, context: Context) -> None:
if self.prefer_simple_messages():
return
# https://github.com/python/mypy/issues/11309
first_arg = callee.def_extras.get("first_arg")
if first_arg and first_arg not in {"self", "cls", "mcs"}:
Expand Down