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