1"""Code and data structures for storing and displaying errors."""
2
3import collections
4import contextlib
5import csv
6import io
7import logging
8import re
9import sys
10from typing import Iterable, Optional, Union
11
12from pytype import abstract
13from pytype import abstract_utils
14from pytype import class_mixin
15from pytype import debug
16from pytype import function
17from pytype import matcher
18from pytype import utils
19from pytype.pytd import escape
20from pytype.pytd import optimize
21from pytype.pytd import pytd_utils
22from pytype.pytd import slots
23from pytype.pytd import visitors
24
25# Usually we call the logger "log" but that name is used quite often here.
26_log = logging.getLogger(__name__)
27
28
29# "Error level" enum for distinguishing between warnings and errors:
30SEVERITY_WARNING = 1
31SEVERITY_ERROR = 2
32
33# The set of known error names.
34_ERROR_NAMES = set()
35
36# The current error name, managed by the error_name decorator.
37_CURRENT_ERROR_NAME = utils.DynamicVar()
38
39# Max number of calls in the traceback string.
40MAX_TRACEBACK_LENGTH = 3
41
42# Max number of tracebacks to show for the same error.
43MAX_TRACEBACKS = 3
44
45# Marker indicating the start of a traceback.
46TRACEBACK_MARKER = "Called from (traceback):"
47
48# Symbol representing an elided portion of the stack.
49_ELLIPSIS = object()
50
51
52def _error_name(name):
53  """Decorate a function so that it binds the current error name."""
54  _ERROR_NAMES.add(name)
55  def wrap(func):
56    def invoke(*args, **kwargs):
57      with _CURRENT_ERROR_NAME.bind(name):
58        return func(*args, **kwargs)
59    return invoke
60  return wrap
61
62
63def _maybe_truncate_traceback(traceback):
64  """Truncate the traceback if it is too long.
65
66  Args:
67    traceback: A list representing an error's traceback. There should be one
68      list item per entry in the traceback (in the right order); beyond that,
69      this function does not care about the item types.
70
71  Returns:
72    The traceback, possibly with some items removed and an _ELLIPSIS inserted.
73    Guaranteed to be no longer than MAX_TRACEBACK_LENGTH.
74  """
75  if len(traceback) > MAX_TRACEBACK_LENGTH:
76    return traceback[:MAX_TRACEBACK_LENGTH-2] + [_ELLIPSIS, traceback[-1]]
77  else:
78    return traceback
79
80
81def _make_traceback_str(frames):
82  """Turn a stack of frames into a traceback string."""
83  if len(frames) < 2 or (
84      frames[-1].f_code and not frames[-1].f_code.get_arg_count()):
85    # A traceback is usually unnecessary when the topmost frame has no
86    # arguments. If this frame ran during module loading, caching prevented it
87    # from running again without a traceback, so we drop the traceback manually.
88    return None
89  frames = frames[:-1]
90  frames = _maybe_truncate_traceback(frames)
91  traceback = []
92  format_line = "line %d, in %s"
93  for frame in frames:
94    if frame is _ELLIPSIS:
95      line = "..."
96    elif frame.current_opcode.code.co_name == "<module>":
97      line = format_line % (frame.current_opcode.line, "current file")
98    else:
99      line = format_line % (frame.current_opcode.line,
100                            frame.current_opcode.code.co_name)
101    traceback.append(line)
102  return TRACEBACK_MARKER + "\n  " + "\n  ".join(traceback)
103
104
105def _dedup_opcodes(stack):
106  """Dedup the opcodes in a stack of frames."""
107  deduped_stack = []
108  for frame in stack:
109    if frame.current_opcode and (
110        not deduped_stack or
111        frame.current_opcode.line != deduped_stack[-1].current_opcode.line):
112      # We can have consecutive opcodes with the same line number due to, e.g.,
113      # a set comprehension. The first opcode we encounter is the one with the
114      # real method name, whereas the second's method name is something like
115      # <setcomp>, so we keep the first.
116      deduped_stack.append(frame)
117  return deduped_stack
118
119
120def _compare_traceback_strings(left, right):
121  """Try to compare two traceback strings.
122
123  Two traceback strings are comparable if they are equal, or if one ends with
124  the other. For example, these two tracebacks are comparable:
125    Traceback:
126      line 1, in <module>
127      line 2, in foo
128    Traceback:
129      line 2, in foo
130  and the first is greater than the second.
131
132  Args:
133    left: A string or None.
134    right: A string or None.
135
136  Returns:
137    None if the inputs aren't comparable, else an integer.
138  """
139  if left == right:
140    return 0
141  left = left[len(TRACEBACK_MARKER):] if left else ""
142  right = right[len(TRACEBACK_MARKER):] if right else ""
143  if left.endswith(right):
144    return 1
145  elif right.endswith(left):
146    return -1
147  else:
148    return None
149
150
151def _function_name(name, capitalize=False):
152  builtin_prefix = "builtins."
153  if name.startswith(builtin_prefix):
154    ret = "built-in function %s" % name[len(builtin_prefix):]
155  else:
156    ret = "function %s" % name
157  if capitalize:
158    return ret[0].upper() + ret[1:]
159  else:
160    return ret
161
162
163class CheckPoint:
164  """Represents a position in an error log."""
165
166  def __init__(self, errors):
167    self._errorlog_errors = errors
168    self._position = len(errors)
169    self.errors = None
170
171  def revert(self):
172    self.errors = self._errorlog_errors[self._position:]
173    self._errorlog_errors[:] = self._errorlog_errors[:self._position]
174
175
176class Error:
177  """Representation of an error in the error log.
178
179  Attributes:
180    name: The error name.
181    bad_call: Optionally, a `pytype.function.BadCall` of details of a bad
182              function call.
183    details: Optionally, a string of message details.
184    filename: The file in which the error occurred.
185    lineno: The line number at which the error occurred.
186    message: The error message string.
187    methodname: The method in which the error occurred.
188    severity: The error level (error or warning), an integer.
189    keyword: Optionally, the culprit keyword in the line where error is.
190             e.g.,
191             message = "No attribute '_submatch' on BasePattern"
192             keyword = _submatch
193    keyword_context: Optionally, a string naming the object on which `keyword`
194                     occurs. e.g. the fully qualified module name that a
195                     non-existent function doesn't exist on.
196    traceback: Optionally, an error traceback.
197  """
198
199  def __init__(self, severity, message, filename=None, lineno=0,
200               methodname=None, details=None, traceback=None, keyword=None,
201               keyword_context=None, bad_call=None):
202    name = _CURRENT_ERROR_NAME.get()
203    assert name, ("Errors must be created from a caller annotated "
204                  "with @error_name.")
205    # Required for every Error.
206    self._severity = severity
207    self._message = message
208    self._name = name
209    # Optional information about the error.
210    self._details = details
211    # Optional information about error position.
212    self._filename = filename
213    self._lineno = lineno or 0
214    self._methodname = methodname
215    self._traceback = traceback
216    self._keyword_context = keyword_context
217    self._keyword = keyword
218    self._bad_call = bad_call
219
220  @classmethod
221  def with_stack(cls, stack, severity, message, **kwargs):
222    """Return an error using a stack for position information.
223
224    Args:
225      stack: A list of state.Frame or state.SimpleFrame objects.
226      severity: The error level (error or warning), an integer.
227      message: The error message string.
228      **kwargs: Additional keyword args to pass onto the class ctor.
229
230    Returns:
231      An Error object.
232    """
233    stack = _dedup_opcodes(stack) if stack else None
234    opcode = stack[-1].current_opcode if stack else None
235    if opcode is None:
236      return cls(severity, message, **kwargs)
237    else:
238      return cls(severity, message, filename=opcode.code.co_filename,
239                 lineno=opcode.line, methodname=opcode.code.co_name,
240                 traceback=_make_traceback_str(stack), **kwargs)
241
242  @classmethod
243  def for_test(cls, severity, message, name, **kwargs):
244    """Create an _Error with the specified name, for use in tests."""
245    with _CURRENT_ERROR_NAME.bind(name):
246      return cls(severity, message, **kwargs)
247
248  @property
249  def name(self):
250    return self._name
251
252  @property
253  def lineno(self):
254    return self._lineno
255
256  @property
257  def filename(self):
258    return self._filename
259
260  @property
261  def message(self):
262    message = self._message
263    if self._details:
264      message += "\n" + self._details
265    if self._traceback:
266      message += "\n" + self._traceback
267    return message
268
269  @property
270  def traceback(self):
271    return self._traceback
272
273  @property
274  def methodname(self):
275    return self._methodname
276
277  @property
278  def bad_call(self):
279    return self._bad_call
280
281  @property
282  def details(self):
283    return self._details
284
285  @property
286  def keyword(self):
287    return self._keyword
288
289  @property
290  def keyword_context(self):
291    return self._keyword_context
292
293  def _position(self):
294    """Return human-readable filename + line number."""
295    method = ", in %s" % self._methodname if self._methodname else ""
296
297    if self._filename:
298      return "File \"%s\", line %d%s" % (self._filename,
299                                         self._lineno,
300                                         method)
301    elif self._lineno:
302      return "Line %d%s" % (self._lineno, method)
303    else:
304      return ""
305
306  def __str__(self):
307    pos = self._position()
308    if pos:
309      pos += ": "
310    text = "%s%s [%s]" % (pos, self._message.replace("\n", "\n  "), self._name)
311    if self._details:
312      text += "\n  " + self._details.replace("\n", "\n  ")
313    if self._traceback:
314      text += "\n" + self._traceback
315    return text
316
317  def drop_traceback(self):
318    with _CURRENT_ERROR_NAME.bind(self._name):
319      return self.__class__(
320          severity=self._severity,
321          message=self._message,
322          filename=self._filename,
323          lineno=self._lineno,
324          methodname=self._methodname,
325          details=self._details,
326          keyword=self._keyword,
327          traceback=None)
328
329
330class ErrorLogBase:
331  """A stream of errors."""
332
333  def __init__(self):
334    self._errors = []
335    # An error filter (initially None)
336    self._filter = None
337
338  def __len__(self):
339    return len(self._errors)
340
341  def __iter__(self):
342    return iter(self._errors)
343
344  def __getitem__(self, index):
345    return self._errors[index]
346
347  def copy_from(self, errors, stack):
348    for e in errors:
349      with _CURRENT_ERROR_NAME.bind(e.name):
350        self.error(stack, e.message, e.details, e.keyword, e.bad_call,
351                   e.keyword_context)
352
353  def is_valid_error_name(self, name):
354    """Return True iff name was defined in an @error_name() decorator."""
355    return name in _ERROR_NAMES
356
357  def set_error_filter(self, filt):
358    """Set the error filter.
359
360    Args:
361      filt: A function or callable object that accepts a single argument of
362          type Error and returns True if that error should be included in the
363          log.  A filter of None will add all errors.
364    """
365    self._filter = filt
366
367  def has_error(self):
368    """Return true iff an Error with SEVERITY_ERROR is present."""
369    # pylint: disable=protected-access
370    return any(e._severity == SEVERITY_ERROR for e in self._errors)
371
372  def _add(self, error):
373    if self._filter is None or self._filter(error):
374      _log.info("Added error to log: %s\n%s", error.name, error)
375      if _log.isEnabledFor(logging.DEBUG):
376        _log.debug(debug.stack_trace(limit=1).rstrip())
377      self._errors.append(error)
378
379  def warn(self, stack, message, *args):
380    self._add(Error.with_stack(stack, SEVERITY_WARNING, message % args))
381
382  def error(self, stack, message, details=None, keyword=None, bad_call=None,
383            keyword_context=None):
384    self._add(Error.with_stack(stack, SEVERITY_ERROR, message, details=details,
385                               keyword=keyword, bad_call=bad_call,
386                               keyword_context=keyword_context))
387
388  @contextlib.contextmanager
389  def checkpoint(self):
390    """Record errors without adding them to the errorlog."""
391    _log.info("Checkpointing errorlog at %d errors", len(self._errors))
392    checkpoint = CheckPoint(self._errors)
393    try:
394      yield checkpoint
395    finally:
396      checkpoint.revert()
397    _log.info("Restored errorlog to checkpoint: %d errors reverted",
398              len(checkpoint.errors))
399
400  def print_to_csv_file(self, filename, open_function=open):
401    """Print the errorlog to a csv file."""
402    with open_function(filename, "w") as f:
403      csv_file = csv.writer(f, delimiter=",")
404      for error in self.unique_sorted_errors():
405        # pylint: disable=protected-access
406        # TODO(b/159038861): Add _methodname
407        if error._details and error._traceback:
408          details = error._details + "\n\n" + error._traceback
409        elif error._traceback:
410          details = error._traceback
411        else:
412          details = error._details
413        csv_file.writerow(
414            [error._filename,
415             error._lineno,
416             error._name,
417             error._message,
418             details])
419
420  def print_to_file(self, fi):
421    for error in self.unique_sorted_errors():
422      print(error, file=fi)
423
424  def unique_sorted_errors(self):
425    """Gets the unique errors in this log, sorted on filename and lineno."""
426    unique_errors = collections.OrderedDict()
427    for error in self._sorted_errors():
428      error_without_traceback = str(error.drop_traceback())
429      if error_without_traceback not in unique_errors:
430        unique_errors[error_without_traceback] = [error]
431        continue
432      errors = unique_errors[error_without_traceback]
433      for previous_error in list(errors):  # make a copy, since we modify errors
434        traceback_cmp = _compare_traceback_strings(error.traceback,
435                                                   previous_error.traceback)
436        if traceback_cmp is None:
437          # We have multiple bad call sites, e.g.,
438          #   def f(x):  x + 42
439          #   f("hello")  # error
440          #   f("world")  # same error, different backtrace
441          # so we'll report this error multiple times with different backtraces.
442          continue
443        elif traceback_cmp < 0:
444          # If the current traceback is shorter, use the current error instead
445          # of the previous one.
446          errors.remove(previous_error)
447        else:
448          # One of the previous errors has a shorter traceback than the current
449          # one, so the latter can be discarded.
450          break
451      else:
452        if len(errors) < MAX_TRACEBACKS:
453          errors.append(error)
454    return sum(unique_errors.values(), [])
455
456  def _sorted_errors(self):
457    return sorted(self._errors, key=lambda x: (x.filename or "", x.lineno))
458
459  def print_to_stderr(self):
460    self.print_to_file(sys.stderr)
461
462  def __str__(self):
463    f = io.StringIO()
464    self.print_to_file(f)
465    return f.getvalue()
466
467
468class ErrorLog(ErrorLogBase):
469  """ErrorLog with convenience functions."""
470
471  def _pytd_print(self, pytd_type):
472    """Print the name of the pytd type."""
473    name = pytd_utils.Print(pytd_utils.CanonicalOrdering(optimize.Optimize(
474        pytd_type.Visit(visitors.RemoveUnknownClasses()))))
475    # Clean up autogenerated namedtuple names, e.g. "namedtuple-X-a-_0-c"
476    # becomes just "X", by extracting out just the type name.
477    if "namedtuple" in name:
478      return escape.unpack_namedtuple(name)
479    nested_class_match = re.search(r"_(?:\w+)_DOT_", name)
480    if nested_class_match:
481      # Pytype doesn't have true support for nested classes. Instead, for
482      #   class Foo:
483      #     class Bar: ...
484      # it outputs:
485      #   class _Foo_DOT_Bar: ...
486      #   class Foo:
487      #     Bar = ...  # type: Type[_Foo_DOT_Bar]
488      # Replace _Foo_DOT_Bar with Foo.Bar in error messages for readability.
489      # TODO(b/35138984): Get rid of this hack.
490      start = nested_class_match.start()
491      return name[:start] + name[start+1:].replace("_DOT_", ".")
492    return name
493
494  def _print_as_expected_type(self, t: abstract.BaseValue, instance=None):
495    """Print abstract value t as a pytd type."""
496    if t.is_late_annotation():
497      return t.expr
498    elif isinstance(t, (abstract.Unknown, abstract.Unsolvable,
499                        class_mixin.Class, abstract.Union)):
500      with t.vm.convert.pytd_convert.set_output_mode(
501          t.vm.convert.pytd_convert.OutputMode.DETAILED):
502        return self._pytd_print(t.get_instance_type(instance=instance))
503    elif abstract_utils.is_concrete(t):
504      return re.sub(r"(\\n|\s)+", " ",
505                    t.str_of_constant(self._print_as_expected_type))
506    elif isinstance(t, abstract.AnnotationClass) or not t.cls:
507      return t.name
508    else:
509      return "<instance of %s>" % self._print_as_expected_type(t.cls, t)
510
511  def _print_as_actual_type(self, t, literal=False):
512    if literal:
513      output_mode = t.vm.convert.pytd_convert.OutputMode.LITERAL
514    else:
515      output_mode = t.vm.convert.pytd_convert.OutputMode.DETAILED
516    with t.vm.convert.pytd_convert.set_output_mode(output_mode):
517      return self._pytd_print(t.to_type())
518
519  def _print_as_generic_type(self, t):
520    generic = pytd_utils.MakeClassOrContainerType(
521        t.get_instance_type().base_type,
522        t.formal_type_parameters.keys(),
523        False)
524    with t.vm.convert.pytd_convert.set_output_mode(
525        t.vm.convert.pytd_convert.OutputMode.DETAILED):
526      return self._pytd_print(generic)
527
528  def _print_as_return_types(self, node, formal, actual, bad):
529    """Print the actual and expected values for a return type."""
530    convert = formal.vm.convert.pytd_convert
531    with convert.set_output_mode(convert.OutputMode.DETAILED):
532      expected = self._pytd_print(formal.get_instance_type(node))
533    if "Literal[" in expected:
534      output_mode = convert.OutputMode.LITERAL
535    else:
536      output_mode = convert.OutputMode.DETAILED
537    with convert.set_output_mode(output_mode):
538      bad_actual = self._pytd_print(pytd_utils.JoinTypes(
539          view[actual].data.to_type(node, view=view) for view, _, _ in bad))
540      if len(actual.bindings) > len(bad):
541        full_actual = self._pytd_print(pytd_utils.JoinTypes(
542            v.to_type(node) for v in actual.data))
543      else:
544        full_actual = bad_actual
545    # typing.NoReturn is a prettier alias for nothing.
546    fmt = lambda ret: "NoReturn" if ret == "nothing" else ret
547    protocol_details, nis_details = self._prepare_errorlog_details(bad)
548    return (fmt(expected), fmt(bad_actual), fmt(full_actual), protocol_details,
549            nis_details)
550
551  def _print_as_function_def(self, fn):
552    assert fn.isinstance_Function()
553    conv = fn.vm.convert.pytd_convert
554    name = fn.name.rsplit(".", 1)[-1]  # We want `def bar()` not `def Foo.bar()`
555    return pytd_utils.Print(conv.value_to_pytd_def(fn.vm.root_node, fn, name))
556
557  def _print_protocol_error(self, error):
558    """Pretty-print the matcher.ProtocolError instance."""
559    left = self._pytd_print(error.left_type.get_instance_type())
560    protocol = self._pytd_print(error.other_type.get_instance_type())
561    if isinstance(error, matcher.ProtocolMissingAttributesError):
562      missing = ", ".join(sorted(error.missing))
563      return (f"Attributes of protocol {protocol} are not implemented on "
564              f"{left}: {missing}")
565    else:
566      assert isinstance(error, matcher.ProtocolTypeError)
567      actual, expected = error.actual_type, error.expected_type
568      if (actual.isinstance_Function() and expected.isinstance_Function()):
569        # TODO(b/196434939): When matching a protocol like Sequence[int] the
570        # protocol name will be Sequence[int] but the method signatures will be
571        # displayed as f(self: Sequence[_T], ...).
572        actual = self._print_as_function_def(actual)
573        expected = self._print_as_function_def(expected)
574        return (f"\nMethod {error.attribute_name} of protocol {protocol} has "
575                f"the wrong signature in {left}:\n\n"
576                f">> {protocol} expects:\n{expected}\n\n"
577                f">> {left} defines:\n{actual}")
578      else:
579        actual = self._pytd_print(error.actual_type.to_type())
580        expected = self._pytd_print(error.expected_type.to_type())
581        return (f"Attribute {error.attribute_name} of protocol {protocol} has "
582                f"wrong type in {left}: expected {expected}, got {actual}")
583
584  def _print_noniterable_str_error(self, error):
585    return (
586        f"Note: {error.left_type.name} does not match iterables by default. "
587        "Learn more: https://github.com/google/pytype/blob/master/docs/faq.md#why-doesnt-str-match-against-string-iterables")
588
589  def _prepare_errorlog_details(self, bad):
590    protocol_details = set()
591    nis_details = set()
592    for _, protocol_err, nis_err in bad:
593      if protocol_err:
594        protocol_details.add("\n" + self._print_protocol_error(protocol_err))
595      if nis_err:
596        nis_details.add("\n" + self._print_noniterable_str_error(nis_err))
597
598    return sorted(protocol_details), sorted(nis_details)
599
600  def _join_printed_types(self, types):
601    """Pretty-print the union of the printed types."""
602    types = sorted(set(types))  # dedup
603    if len(types) == 1:
604      return next(iter(types))
605    elif types:
606      if "None" in types:
607        types.remove("None")
608        return "Optional[%s]" % self._join_printed_types(types)
609      else:
610        return "Union[%s]" % ", ".join(types)
611    else:
612      return "nothing"
613
614  def _iter_sig(self, sig):
615    """Iterate through a function.Signature object. Focus on a bad parameter."""
616    for name in sig.param_names:
617      yield "", name
618    if sig.varargs_name is not None:
619      yield "*", sig.varargs_name
620    elif sig.kwonly_params:
621      yield ("*", "")
622    for name in sorted(sig.kwonly_params):
623      yield "", name
624    if sig.kwargs_name is not None:
625      yield "**", sig.kwargs_name
626
627  def _iter_expected(self, sig, bad_param):
628    """Yield the prefix, name and type information for expected parameters."""
629    for prefix, name in self._iter_sig(sig):
630      suffix = " = ..." if name in sig.defaults else ""
631      if bad_param and name == bad_param.name:
632        type_str = self._print_as_expected_type(bad_param.expected)
633        suffix = ": " + type_str + suffix
634      yield prefix, name, suffix
635
636  def _iter_actual(self, sig, passed_args, bad_param, literal):
637    """Yield the prefix, name and type information for actual parameters."""
638    # We want to display the passed_args in the order they're defined in the
639    # signature, unless there are starargs or starstarargs.
640    # Map param names to their position in the list, then sort the list of
641    # passed args so it's in the same order as the params.
642    keys = {param: n for n, (_, param) in enumerate(self._iter_sig(sig))}
643    def key_f(arg):
644      arg_name = arg[0]
645      # starargs are given anonymous names, which won't be found in the sig.
646      # Instead, use the same name as the varags param itself, if present.
647      if arg_name not in keys and pytd_utils.ANON_PARAM.match(arg_name):
648        return keys.get(sig.varargs_name, len(keys)+1)
649      return keys.get(arg_name, len(keys)+1)
650    for name, arg in sorted(passed_args, key=key_f):
651      if bad_param and name == bad_param.name:
652        suffix = ": " + self._print_as_actual_type(arg, literal=literal)
653      else:
654        suffix = ""
655      yield "", name, suffix
656
657  def _print_args(self, arg_iter, bad_param):
658    """Pretty-print a list of arguments. Focus on a bad parameter."""
659    # (foo, bar, broken : type, ...)
660    printed_params = []
661    found = False
662    for prefix, name, suffix in arg_iter:
663      if bad_param and name == bad_param.name:
664        printed_params.append(prefix + name + suffix)
665        found = True
666      elif found:
667        printed_params.append("...")
668        break
669      elif pytd_utils.ANON_PARAM.match(name):
670        printed_params.append(prefix + "_")
671      else:
672        printed_params.append(prefix + name)
673    return ", ".join(printed_params)
674
675  @_error_name("pyi-error")
676  def pyi_error(self, stack, name, error):
677    self.error(stack, "Couldn't import pyi for %r" % name, str(error),
678               keyword=name)
679
680  @_error_name("attribute-error")
681  def _attribute_error(self, stack, binding, attr_name):
682    """Log an attribute error."""
683    obj_repr = self._print_as_actual_type(binding.data)
684    if len(binding.variable.bindings) > 1:
685      # Joining the printed types rather than merging them before printing
686      # ensures that we print all of the options when 'Any' is among them.
687      details = "In %s" % self._join_printed_types(
688          self._print_as_actual_type(v) for v in binding.variable.data)
689    else:
690      details = None
691    self.error(
692        stack, "No attribute %r on %s" % (attr_name, obj_repr), details=details,
693        keyword=attr_name)
694
695  @_error_name("not-writable")
696  def not_writable(self, stack, obj, attr_name):
697    obj_values = obj.vm.merge_values([obj])
698    obj_repr = self._print_as_actual_type(obj_values)
699    self.error(stack, "Can't assign attribute %r on %s" % (attr_name, obj_repr),
700               keyword=attr_name, keyword_context=obj_repr)
701
702  @_error_name("module-attr")
703  def _module_attr(self, stack, binding, attr_name):
704    module_name = binding.data.name
705    self.error(stack, "No attribute %r on module %r" % (attr_name, module_name),
706               keyword=attr_name, keyword_context=module_name)
707
708  def attribute_error(self, stack, binding, attr_name):
709    if attr_name in slots.SYMBOL_MAPPING:
710      obj = self._print_as_actual_type(binding.data)
711      details = "No attribute %r on %s" % (attr_name, obj)
712      self._unsupported_operands(stack, attr_name, obj, details=details)
713    elif isinstance(binding.data, abstract.Module):
714      self._module_attr(stack, binding, attr_name)
715    else:
716      self._attribute_error(stack, binding, attr_name)
717
718  @_error_name("unbound-type-param")
719  def unbound_type_param(self, stack, obj, attr_name, type_param_name):
720    self.error(
721        stack, "Can't access attribute %r on %s" % (attr_name, obj.name),
722        "No binding for type parameter %s" % type_param_name, keyword=attr_name,
723        keyword_context=obj.name)
724
725  @_error_name("name-error")
726  def name_error(self, stack, name, details=None):
727    self.error(
728        stack, "Name %r is not defined" % name, keyword=name, details=details)
729
730  @_error_name("import-error")
731  def import_error(self, stack, module_name):
732    self.error(stack, "Can't find module %r." % module_name,
733               keyword=module_name)
734
735  def _invalid_parameters(self, stack, message, bad_call):
736    """Log an invalid parameters error."""
737    sig, passed_args, bad_param = bad_call
738    expected = self._print_args(self._iter_expected(sig, bad_param), bad_param)
739    literal = "Literal[" in expected
740    actual = self._print_args(
741        self._iter_actual(sig, passed_args, bad_param, literal), bad_param)
742    details = "".join([
743        "       Expected: (", expected, ")\n",
744        "Actually passed: (", actual,
745        ")"])
746    if bad_param and bad_param.protocol_error:
747      details += "\n" + self._print_protocol_error(bad_param.protocol_error)
748    if bad_param and bad_param.noniterable_str_error:
749      details += "\n" + self._print_noniterable_str_error(
750          bad_param.noniterable_str_error)
751    self.error(stack, message, details, bad_call=bad_call)
752
753  @_error_name("wrong-arg-count")
754  def wrong_arg_count(self, stack, name, bad_call):
755    message = "%s expects %d arg(s), got %d" % (
756        _function_name(name, capitalize=True),
757        bad_call.sig.mandatory_param_count(),
758        len(bad_call.passed_args))
759    self._invalid_parameters(stack, message, bad_call)
760
761  def _get_binary_operation(self, function_name, bad_call):
762    """Return (op, left, right) if the function should be treated as a binop."""
763    maybe_left_operand, _, f = function_name.rpartition(".")
764    # Check that
765    # (1) the function is bound to an object (the left operand),
766    # (2) the function has a pretty representation,
767    # (3) either there are exactly two passed args or the function is one we've
768    #     chosen to treat as a binary operation.
769    if (not maybe_left_operand or f not in slots.SYMBOL_MAPPING or
770        (len(bad_call.passed_args) != 2 and
771         f not in ("__setitem__", "__getslice__"))):
772      return None
773    for arg_name, arg_value in bad_call.passed_args[1:]:
774      if arg_name == bad_call.bad_param.name:
775        # maybe_left_operand is something like `dict`, but we want a more
776        # precise type like `Dict[str, int]`.
777        left_operand = self._print_as_actual_type(bad_call.passed_args[0][1])
778        right_operand = self._print_as_actual_type(arg_value)
779        return f, left_operand, right_operand
780    return None
781
782  def wrong_arg_types(self, stack, name, bad_call):
783    """Log [wrong-arg-types]."""
784    operation = self._get_binary_operation(name, bad_call)
785    if operation:
786      operator, left_operand, right_operand = operation
787      operator_name = _function_name(operator, capitalize=True)
788      expected_right_operand = self._print_as_expected_type(
789          bad_call.bad_param.expected)
790      details = "%s on %s expects %s" % (
791          operator_name, left_operand, expected_right_operand)
792      self._unsupported_operands(
793          stack, operator, left_operand, right_operand, details=details)
794    else:
795      self._wrong_arg_types(stack, name, bad_call)
796
797  @_error_name("wrong-arg-types")
798  def _wrong_arg_types(self, stack, name, bad_call):
799    """A function was called with the wrong parameter types."""
800    message = ("%s was called with the wrong arguments" %
801               _function_name(name, capitalize=True))
802    self._invalid_parameters(stack, message, bad_call)
803
804  @_error_name("wrong-keyword-args")
805  def wrong_keyword_args(self, stack, name, bad_call, extra_keywords):
806    """A function was called with extra keywords."""
807    if len(extra_keywords) == 1:
808      message = "Invalid keyword argument %s to %s" % (
809          extra_keywords[0], _function_name(name))
810    else:
811      message = "Invalid keyword arguments %s to %s" % (
812          "(" + ", ".join(sorted(extra_keywords)) + ")",
813          _function_name(name))
814    self._invalid_parameters(stack, message, bad_call)
815
816  @_error_name("missing-parameter")
817  def missing_parameter(self, stack, name, bad_call, missing_parameter):
818    """A function call is missing parameters."""
819    message = "Missing parameter %r in call to %s" % (
820        missing_parameter, _function_name(name))
821    self._invalid_parameters(stack, message, bad_call)
822
823  @_error_name("not-callable")
824  def not_callable(self, stack, func):
825    """Calling an object that isn't callable."""
826    if isinstance(func, abstract.InterpreterFunction) and func.is_overload:
827      prefix = "@typing.overload-decorated "
828    else:
829      prefix = ""
830    message = "%s%r object is not callable" % (prefix, func.name)
831    self.error(stack, message, keyword=func.name)
832
833  @_error_name("not-indexable")
834  def not_indexable(self, stack, name, generic_warning=False):
835    message = "class %s is not indexable" % name
836    if generic_warning:
837      self.error(stack, message, "(%r does not subclass Generic)" % name,
838                 keyword=name)
839    else:
840      self.error(stack, message, keyword=name)
841
842  @_error_name("not-instantiable")
843  def not_instantiable(self, stack, cls):
844    """Instantiating an abstract class."""
845    message = "Can't instantiate %s with abstract methods %s" % (
846        cls.full_name, ", ".join(sorted(cls.abstract_methods)))
847    self.error(stack, message)
848
849  @_error_name("ignored-abstractmethod")
850  def ignored_abstractmethod(self, stack, cls_name, method_name):
851    message = "Stray abc.abstractmethod decorator on method %s" % method_name
852    self.error(stack, message,
853               details="(%s does not have metaclass abc.ABCMeta)" % cls_name)
854
855  @_error_name("ignored-metaclass")
856  def ignored_metaclass(self, stack, cls, metaclass):
857    message = "Metaclass %s on class %s ignored in Python 3" % (metaclass, cls)
858    self.error(stack, message)
859
860  @_error_name("duplicate-keyword-argument")
861  def duplicate_keyword(self, stack, name, bad_call, duplicate):
862    message = ("%s got multiple values for keyword argument %r" %
863               (_function_name(name), duplicate))
864    self._invalid_parameters(stack, message, bad_call)
865
866  @_error_name("invalid-super-call")
867  def invalid_super_call(self, stack, message, details=None):
868    self.error(stack, message, details)
869
870  def invalid_function_call(self, stack, error):
871    """Log an invalid function call."""
872    if isinstance(error, function.WrongArgCount):
873      self.wrong_arg_count(stack, error.name, error.bad_call)
874    elif isinstance(error, function.WrongArgTypes):
875      self.wrong_arg_types(stack, error.name, error.bad_call)
876    elif isinstance(error, function.WrongKeywordArgs):
877      self.wrong_keyword_args(
878          stack, error.name, error.bad_call, error.extra_keywords)
879    elif isinstance(error, function.MissingParameter):
880      self.missing_parameter(
881          stack, error.name, error.bad_call, error.missing_parameter)
882    elif isinstance(error, function.NotCallable):
883      self.not_callable(stack, error.obj)
884    elif isinstance(error, function.DuplicateKeyword):
885      self.duplicate_keyword(
886          stack, error.name, error.bad_call, error.duplicate)
887    elif isinstance(error, function.UndefinedParameterError):
888      self.name_error(stack, error.name)
889    else:
890      raise AssertionError(error)
891
892  @_error_name("base-class-error")
893  def base_class_error(self, stack, base_var):
894    base_cls = self._join_printed_types(
895        self._print_as_expected_type(t) for t in base_var.data)
896    self.error(stack, "Invalid base class: %s" % base_cls, keyword=base_cls)
897
898  @_error_name("bad-return-type")
899  def bad_return_type(self, stack, node, formal, actual, bad):
900    """Logs a [bad-return-type] error."""
901    expected, bad_actual, full_actual, protocol_details, nis_details = (
902        self._print_as_return_types(node, formal, actual, bad))
903    if full_actual == bad_actual:
904      message = "bad return type"
905    else:
906      message = f"bad option {bad_actual!r} in return type"
907    details = ["         Expected: ", expected, "\n",
908               "Actually returned: ", full_actual]
909    details.extend(protocol_details + nis_details)
910    self.error(stack, message, "".join(details))
911
912  @_error_name("bad-yield-annotation")
913  def bad_yield_annotation(self, stack, name, annot, is_async):
914    func = ("async " if is_async else "") + f"generator function {name}"
915    actual = self._print_as_expected_type(annot)
916    message = f"Bad return type {actual!r} for {func}"
917    if is_async:
918      details = "Expected AsyncGenerator, AsyncIterable or AsyncIterator"
919    else:
920      details = "Expected Generator, Iterable or Iterator"
921    self.error(stack, message, details)
922
923  @_error_name("bad-concrete-type")
924  def bad_concrete_type(self, stack, node, formal, actual, bad):
925    expected, actual, _, protocol_details, nis_details = (
926        self._print_as_return_types(node, formal, actual, bad))
927    details = ["       Expected: ", expected, "\n",
928               "Actually passed: ", actual]
929    details.extend(protocol_details + nis_details)
930    self.error(
931        stack, "Invalid instantiation of generic class", "".join(details))
932
933  def _show_variable(self, var):
934    """Show variable as 'name: typ' or 'pyval: typ' if available."""
935    val = var.data[0]
936    name = val.vm.get_var_name(var)
937    typ = self._join_printed_types(
938        self._print_as_actual_type(t) for t in var.data)
939    if name:
940      return f"'{name}: {typ}'"
941    elif len(var.data) == 1 and hasattr(val, "pyval"):
942      return f"'{val.pyval!r}: {typ}'"
943    else:
944      return f"'{typ}'"
945
946  def unsupported_operands(self, stack, operator, var1, var2):
947    left = self._show_variable(var1)
948    right = self._show_variable(var2)
949    details = "No attribute %r on %s" % (operator, left)
950    if operator in slots.REVERSE_NAME_MAPPING:
951      details += " or %r on %s" % (slots.REVERSE_NAME_MAPPING[operator], right)
952    self._unsupported_operands(stack, operator, left, right, details=details)
953
954  @_error_name("unsupported-operands")
955  def _unsupported_operands(self, stack, operator, *operands, details=None):
956    """Unsupported operands."""
957    args = " and ".join(str(operand) for operand in operands)
958    if operator in slots.COMPARES:
959      symbol = operator
960      details = f"Primitive types {args} are not comparable."
961      self.error(stack, f"unsupported operand types for {symbol}",
962                 details=details)
963    else:
964      symbol = slots.SYMBOL_MAPPING[operator]
965      self.error(stack, f"unsupported operand type(s) for {symbol}: {args}",
966                 details=details)
967
968  def invalid_annotation(self,
969                         stack,
970                         annot: Optional[Union[str, abstract.BaseValue]],
971                         details=None,
972                         name=None):
973    if isinstance(annot, abstract.BaseValue):
974      annot = self._print_as_expected_type(annot)
975    self._invalid_annotation(stack, annot, details, name)
976
977  def invalid_ellipses(self, stack, indices, container_name):
978    if indices:
979      details = "Not allowed at %s %s in %s" % (
980          "index" if len(indices) == 1 else "indices",
981          ", ".join(str(i) for i in sorted(indices)),
982          container_name)
983      self._invalid_annotation(stack, "Ellipsis", details, None)
984
985  def ambiguous_annotation(
986      self,
987      stack,
988      options: Optional[Union[str, Iterable[abstract.BaseValue]]],
989      name=None):
990    """Log an ambiguous annotation."""
991    if isinstance(options, (str, type(None))):
992      desc = options
993    else:
994      desc = " or ".join(
995          sorted(self._print_as_expected_type(o) for o in options))
996    self._invalid_annotation(stack, desc, "Must be constant", name)
997
998  @_error_name("invalid-annotation")
999  def _invalid_annotation(self, stack, annot_string, details, name):
1000    """Log the invalid annotation."""
1001    if name is None:
1002      suffix = ""
1003    else:
1004      suffix = "for " + name
1005    annot_string = "%r " % annot_string if annot_string else ""
1006    self.error(stack, "Invalid type annotation %s%s" % (annot_string, suffix),
1007               details=details)
1008
1009  @_error_name("mro-error")
1010  def mro_error(self, stack, name, mro_seqs, details=None):
1011    seqs = []
1012    for seq in mro_seqs:
1013      seqs.append("[%s]" % ", ".join(cls.name for cls in seq))
1014    suffix = ": %s" % ", ".join(seqs) if seqs else ""
1015    msg = "%s has invalid inheritance%s." % (name, suffix)
1016    self.error(stack, msg, keyword=name, details=details)
1017
1018  @_error_name("invalid-directive")
1019  def invalid_directive(self, filename, lineno, message):
1020    self._add(Error(
1021        SEVERITY_WARNING, message, filename=filename, lineno=lineno))
1022
1023  @_error_name("late-directive")
1024  def late_directive(self, filename, lineno, name):
1025    message = "%s disabled from here to the end of the file" % name
1026    details = ("Consider limiting this directive's scope or moving it to the "
1027               "top of the file.")
1028    self._add(Error(SEVERITY_WARNING, message, details=details,
1029                    filename=filename, lineno=lineno))
1030
1031  @_error_name("not-supported-yet")
1032  def not_supported_yet(self, stack, feature, details=None):
1033    self.error(stack, "%s not supported yet" % feature, details=details)
1034
1035  @_error_name("python-compiler-error")
1036  def python_compiler_error(self, filename, lineno, message):
1037    self._add(Error(
1038        SEVERITY_ERROR, message, filename=filename, lineno=lineno))
1039
1040  @_error_name("recursion-error")
1041  def recursion_error(self, stack, name):
1042    self.error(stack, "Detected recursion in %s" % name, keyword=name)
1043
1044  @_error_name("redundant-function-type-comment")
1045  def redundant_function_type_comment(self, filename, lineno):
1046    self._add(Error(
1047        SEVERITY_ERROR,
1048        "Function type comments cannot be used with annotations",
1049        filename=filename, lineno=lineno))
1050
1051  @_error_name("invalid-function-type-comment")
1052  def invalid_function_type_comment(self, stack, comment, details=None):
1053    self.error(stack, "Invalid function type comment: %s" % comment,
1054               details=details)
1055
1056  @_error_name("ignored-type-comment")
1057  def ignored_type_comment(self, filename, lineno, comment):
1058    self._add(Error(
1059        SEVERITY_WARNING, "Stray type comment: %s" % comment,
1060        filename=filename, lineno=lineno))
1061
1062  @_error_name("invalid-typevar")
1063  def invalid_typevar(self, stack, comment, bad_call=None):
1064    if bad_call:
1065      self._invalid_parameters(stack, comment, bad_call)
1066    else:
1067      self.error(stack, "Invalid TypeVar: %s" % comment)
1068
1069  @_error_name("invalid-namedtuple-arg")
1070  def invalid_namedtuple_arg(self, stack, badname=None, err_msg=None):
1071    if err_msg is None:
1072      msg = ("collections.namedtuple argument %r is not a valid typename or "
1073             "field name.")
1074      self.warn(stack, msg % badname)
1075    else:
1076      self.error(stack, err_msg)
1077
1078  @_error_name("bad-function-defaults")
1079  def bad_function_defaults(self, stack, func_name):
1080    msg = "Attempt to set %s.__defaults__ to a non-tuple value."
1081    self.warn(stack, msg % func_name)
1082
1083  @_error_name("bad-slots")
1084  def bad_slots(self, stack, msg):
1085    self.error(stack, msg)
1086
1087  @_error_name("bad-unpacking")
1088  def bad_unpacking(self, stack, num_vals, num_vars):
1089    prettify = lambda v, label: "%d %s%s" % (v, label, "" if v == 1 else "s")
1090    vals_str = prettify(num_vals, "value")
1091    vars_str = prettify(num_vars, "variable")
1092    msg = "Cannot unpack %s into %s" % (vals_str, vars_str)
1093    self.error(stack, msg, keyword=vals_str)
1094
1095  @_error_name("reveal-type")
1096  def reveal_type(self, stack, node, var):
1097    types = [
1098        self._print_as_actual_type(b.data)
1099        for b in abstract_utils.expand_type_parameter_instances(var.bindings)
1100        if node.HasCombination([b])]
1101    self.error(stack, self._join_printed_types(types))
1102
1103  @_error_name("assert-type")
1104  def assert_type(self, stack, node, var, typ=None):
1105    """Check that a variable type matches its expected value."""
1106
1107    types = [
1108        self._print_as_actual_type(b.data)
1109        for b in abstract_utils.expand_type_parameter_instances(var.bindings)
1110        if node.HasCombination([b])]
1111    actual = self._join_printed_types(types)
1112
1113    # assert_type(x) checks that x is not Any
1114    if typ is None:
1115      if types == ["Any"] or types == ["typing.Any"]:
1116        self.error(stack, f"Asserted type was {actual}")
1117      return
1118
1119    try:
1120      expected = abstract_utils.get_atomic_python_constant(typ, str)
1121    except abstract_utils.ConversionError:
1122      # NOTE: Converting types to strings is provided as a fallback, but is not
1123      # really supported, since there are issues around name resolution.
1124      vm = typ.data[0].vm
1125      typ = vm.annotations_util.extract_annotation(
1126          node, typ, "assert_type", vm.simple_stack())
1127      node, typ = vm.init_class(node, typ)
1128      wanted = [
1129          self._print_as_actual_type(b.data)
1130          for b in abstract_utils.expand_type_parameter_instances(typ.bindings)
1131          if node.HasCombination([b])]
1132      expected = self._join_printed_types(wanted)
1133    if actual != expected:
1134      details = f"Expected: {expected}\n  Actual: {actual}"
1135      self.error(stack, actual, details=details)
1136
1137  @_error_name("annotation-type-mismatch")
1138  def annotation_type_mismatch(self, stack, annot, binding, name):
1139    # TODO(rpalaguachi): Include detailed error message for
1140    # NonIterableStringError when a `str` is assigned to a string iterable.
1141    """Invalid combination of annotation and assignment."""
1142    if annot is None:
1143      return
1144    annot_string = self._print_as_expected_type(annot)
1145    literal = "Literal[" in annot_string
1146    actual_string = self._print_as_actual_type(binding.data, literal=literal)
1147    if actual_string == "None":
1148      annot_string += f" (Did you mean 'typing.Optional[{annot_string}]'?)"
1149    details = ("Annotation: %s\n" % annot_string +
1150               "Assignment: %s" % actual_string)
1151    if len(binding.variable.bindings) > 1:
1152      # Joining the printed types rather than merging them before printing
1153      # ensures that we print all of the options when 'Any' is among them.
1154      # We don't need to print this if there is only 1 unique type.
1155      print_types = set(self._print_as_actual_type(v, literal=literal)
1156                        for v in binding.variable.data)
1157      if len(print_types) > 1:
1158        details += "\nIn assignment of type: %s" % self._join_printed_types(
1159            print_types)
1160    suffix = "" if name is None else " for " + name
1161    err_msg = "Type annotation%s does not match type of assignment" % suffix
1162    self.error(stack, err_msg, details=details)
1163
1164  @_error_name("container-type-mismatch")
1165  def container_type_mismatch(self, stack, obj, mutations, name):
1166    """Invalid combination of annotation and mutation.
1167
1168    Args:
1169      stack: the frame stack
1170      obj: the container instance being mutated
1171      mutations: a dict of {parameter name: (annotated types, new types)}
1172      name: the variable name (or None)
1173    """
1174    for parent in obj.cls.mro:
1175      if isinstance(parent, abstract.ParameterizedClass):
1176        cls = parent
1177        break
1178    else:
1179      assert False, f"{obj.cls.full_name} is not a container"
1180    details = "Container: %s\n" % self._print_as_generic_type(cls)
1181    allowed_contained = ""
1182    new_contained = ""
1183    for formal in cls.formal_type_parameters.keys():
1184      if formal in mutations:
1185        params, values, _ = mutations[formal]
1186        allowed_content = self._print_as_expected_type(
1187            cls.get_formal_type_parameter(formal))
1188        new_content = self._join_printed_types(
1189            sorted(self._print_as_actual_type(v)
1190                   for v in set(values.data) - set(params.data)))
1191        allowed_contained += "  %s: %s\n" % (formal, allowed_content)
1192        new_contained += "  %s: %s\n" % (formal, new_content)
1193    annotation = self._print_as_expected_type(cls)
1194    details += ("Allowed contained types (from annotation %s):\n%s"
1195                "New contained types:\n%s") % (
1196                    annotation, allowed_contained, new_contained)
1197    suffix = "" if name is None else " for " + name
1198    err_msg = "New container type%s does not match type annotation" % suffix
1199    self.error(stack, err_msg, details=details)
1200
1201  @_error_name("invalid-function-definition")
1202  def invalid_function_definition(self, stack, msg):
1203    """Invalid function constructed via metaprogramming."""
1204    self.error(stack, msg)
1205
1206
1207def get_error_names_set():
1208  return _ERROR_NAMES
1209