From d88fe24ab43f6bff887010125c7c1418ebad71a8 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Thu, 3 Jan 2019 22:51:33 -0800 Subject: [PATCH 01/11] Add basic prototype of message id tracking --- mypy/checker.py | 5 +- mypy/errors.py | 12 +++-- mypy/messages.py | 133 ++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 138 insertions(+), 12 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 2b3ba74e8e46..d7aef4146808 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -799,8 +799,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) if (fdef.info and fdef.name() in ('__init__', '__init_subclass__') and not isinstance(typ.ret_type, NoneTyp) and not self.dynamic_funcs[-1]): - self.fail(messages.MUST_HAVE_NONE_RETURN_TYPE.format(fdef.name()), - item) + self.msg.must_have_none_return_type(fdef) self.check_for_missing_annotations(fdef) if self.options.disallow_any_unimported: @@ -2679,7 +2678,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None: # Functions returning a value of type None are allowed to have a None return. if is_lambda or isinstance(typ, NoneTyp): return - self.fail(messages.NO_RETURN_VALUE_EXPECTED, s) + self.msg.no_return_exepected(s) else: self.check_subtype( subtype_label='got', diff --git a/mypy/errors.py b/mypy/errors.py index a177b5d6805a..0bdba053d928 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -60,6 +60,9 @@ class ErrorInfo: # Fine-grained incremental target where this was reported target = None # type: Optional[str] + # Error code identifying the message, for grouping/filtering + id = None # type: Optional[str] + def __init__(self, import_ctx: List[Tuple[str, int]], file: str, @@ -73,7 +76,8 @@ def __init__(self, blocker: bool, only_once: bool, origin: Optional[Tuple[str, int]] = None, - target: Optional[str] = None) -> None: + target: Optional[str] = None, + id: Optional[str] = None) -> None: self.import_ctx = import_ctx self.file = file self.module = module @@ -87,6 +91,7 @@ def __init__(self, self.only_once = only_once self.origin = origin or (file, line) self.target = target + self.id = id class Errors: @@ -230,7 +235,8 @@ def report(self, file: Optional[str] = None, only_once: bool = False, origin_line: Optional[int] = None, - offset: int = 0) -> None: + offset: int = 0, + id: Optional[str] = None) -> None: """Report message at the given line using the current error context. Args: @@ -261,7 +267,7 @@ def report(self, function, line, column, severity, message, blocker, only_once, origin=(self.file, origin_line) if origin_line else None, - target=self.current_target()) + target=self.current_target(), id=id) self.add_error_info(info) def _add_error_info(self, file: str, info: ErrorInfo) -> None: diff --git a/mypy/messages.py b/mypy/messages.py index 7b559bf8f87c..1e606d775cd3 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -10,12 +10,15 @@ checker but we are moving away from this convention. """ -from collections import OrderedDict +from collections import OrderedDict, deque import re import difflib from textwrap import dedent -from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple, Set, Optional, Union +from typing import ( + cast, List, Dict, Any, Sequence, Iterable, Tuple, Set, Optional, Union, ClassVar, TypeVar, + Callable +) from mypy.erasetype import erase_type from mypy.errors import Errors @@ -35,9 +38,10 @@ if MYPY: from typing_extensions import Final +T = TypeVar('T', bound=Callable) + # Type checker error message constants -- -NO_RETURN_VALUE_EXPECTED = 'No return value expected' # type: Final MISSING_RETURN_STATEMENT = 'Missing return statement' # type: Final INVALID_IMPLICIT_RETURN = 'Implicit return in function which does not return' # type: Final INCOMPATIBLE_RETURN_VALUE_TYPE = 'Incompatible return value type' # type: Final @@ -70,7 +74,6 @@ INCOMPATIBLE_TYPES_IN_YIELD_FROM = 'Incompatible types in "yield from"' # type: Final INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION = \ 'Incompatible types in string interpolation' # type: Final -MUST_HAVE_NONE_RETURN_TYPE = 'The return type of "{}" must be None' # type: Final INVALID_TUPLE_INDEX_TYPE = 'Invalid tuple index type' # type: Final TUPLE_INDEX_OUT_OF_RANGE = 'Tuple index out of range' # type: Final INVALID_SLICE_INDEX = 'Slice index must be an integer or None' # type: Final @@ -174,6 +177,24 @@ } # type: Final +message_ids = set() # type: Set[str] + + +def tracked(func: T) -> T: + msg_id = func.__name__ + message_ids.add(msg_id) + + def wrapped(self, *args, **kwargs): + assert len(self.active_msg_id) == 0 or self.active_msg_id[-1] != msg_id + self.active_msg_id.append(msg_id) + try: + return func(self, *args, **kwargs) + finally: + self.active_msg_id.pop() + + return wrapped + + class MessageBuilder: """Helper class for reporting type checker error messages with parameters. @@ -202,6 +223,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile]) -> None: self.modules = modules self.disable_count = 0 self.disable_type_names = 0 + self.active_msg_id = deque() # # Helpers @@ -236,13 +258,14 @@ def is_errors(self) -> bool: def report(self, msg: str, context: Optional[Context], severity: str, file: Optional[str] = None, origin: Optional[Context] = None, - offset: int = 0) -> None: + offset: int = 0, id: Optional[str] = None) -> None: """Report an error or note (unless disabled).""" if self.disable_count <= 0: self.errors.report(context.get_line() if context else -1, context.get_column() if context else -1, msg, severity=severity, file=file, offset=offset, - origin_line=origin.get_line() if origin else None) + origin_line=origin.get_line() if origin else None, + id=id if id is not None else self.active_msg_id) def fail(self, msg: str, context: Optional[Context], file: Optional[str] = None, origin: Optional[Context] = None) -> None: @@ -472,6 +495,15 @@ def format_distinctly(self, type1: Type, type2: Type, bare: bool = False) -> Tup # get some information as arguments, and they build an error message based # on them. + @tracked + def no_return_exepected(self, context: Context): + self.fail('No return value expected', context) + + @tracked + def must_have_none_return_type(self, context: FuncDef): + self.fail('The return type of "{}" must be None'.format(context.name()), context) + + @tracked def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Context) -> Type: """Report a missing or non-accessible member. @@ -563,6 +595,7 @@ def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Cont typ_format, self.format(original_type), member, extra), context) return AnyType(TypeOfAny.from_error) + @tracked def unsupported_operand_types(self, op: str, left_type: Any, right_type: Any, context: Context) -> None: """Report unsupported operand types for a binary operation. @@ -588,6 +621,7 @@ def unsupported_operand_types(self, op: str, left_type: Any, op, left_str, right_str) self.fail(msg, context) + @tracked def unsupported_left_operand(self, op: str, typ: Type, context: Context) -> None: if self.disable_type_names: @@ -597,15 +631,18 @@ def unsupported_left_operand(self, op: str, typ: Type, op, self.format(typ)) self.fail(msg, context) + @tracked def not_callable(self, typ: Type, context: Context) -> Type: self.fail('{} not callable'.format(self.format(typ)), context) return AnyType(TypeOfAny.from_error) + @tracked def untyped_function_call(self, callee: CallableType, context: Context) -> Type: name = callable_name(callee) or '(unknown)' self.fail('Call to untyped function {} in typed context'.format(name), context) return AnyType(TypeOfAny.from_error) + @tracked def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: Type, arg_kind: int, context: Context) -> None: """Report an error about an incompatible argument type. @@ -755,11 +792,13 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: for note_msg in notes: self.note(note_msg, context) + @tracked def invalid_index_type(self, index_type: Type, expected_type: Type, base_str: str, context: Context) -> None: self.fail('Invalid index type {} for {}; expected type {}'.format( self.format(index_type), base_str, self.format(expected_type)), context) + @tracked def too_few_arguments(self, callee: CallableType, context: Context, argument_names: Optional[Sequence[Optional[str]]]) -> None: if (argument_names is not None and not all(k is None for k in argument_names) @@ -777,14 +816,17 @@ def too_few_arguments(self, callee: CallableType, context: Context, msg = 'Too few arguments' + for_function(callee) self.fail(msg, context) + @tracked def missing_named_argument(self, callee: CallableType, context: Context, name: str) -> None: msg = 'Missing named argument "{}"'.format(name) + for_function(callee) self.fail(msg, context) + @tracked def too_many_arguments(self, callee: CallableType, context: Context) -> None: msg = 'Too many arguments' + for_function(callee) self.fail(msg, context) + @tracked def too_many_arguments_from_typed_dict(self, callee: CallableType, arg_type: TypedDictType, @@ -799,11 +841,13 @@ def too_many_arguments_from_typed_dict(self, return self.fail(msg, context) + @tracked def too_many_positional_arguments(self, callee: CallableType, context: Context) -> None: msg = 'Too many positional arguments' + for_function(callee) self.fail(msg, context) + @tracked def unexpected_keyword_argument(self, callee: CallableType, name: str, context: Context) -> None: msg = 'Unexpected keyword argument "{}"'.format(name) + for_function(callee) @@ -817,12 +861,14 @@ def unexpected_keyword_argument(self, callee: CallableType, name: str, self.note('{} defined here'.format(fname), callee.definition, file=module.path, origin=context) + @tracked def duplicate_argument_value(self, callee: CallableType, index: int, context: Context) -> None: self.fail('{} gets multiple values for keyword argument "{}"'. format(callable_name(callee) or 'Function', callee.arg_names[index]), context) + @tracked def does_not_return_value(self, callee_type: Optional[Type], context: Context) -> None: """Report an error about use of an unusable type.""" name = None # type: Optional[str] @@ -833,6 +879,7 @@ def does_not_return_value(self, callee_type: Optional[Type], context: Context) - else: self.fail('Function does not return a value', context) + @tracked def deleted_as_rvalue(self, typ: DeletedType, context: Context) -> None: """Report an error about using an deleted type as an rvalue.""" if typ.source is None: @@ -841,6 +888,7 @@ def deleted_as_rvalue(self, typ: DeletedType, context: Context) -> None: s = " '{}'".format(typ.source) self.fail('Trying to read deleted variable{}'.format(s), context) + @tracked def deleted_as_lvalue(self, typ: DeletedType, context: Context) -> None: """Report an error about using an deleted type as an lvalue. @@ -853,6 +901,7 @@ def deleted_as_lvalue(self, typ: DeletedType, context: Context) -> None: s = " '{}'".format(typ.source) self.fail('Assignment to variable{} outside except: block'.format(s), context) + @tracked def no_variant_matches_arguments(self, plausible_targets: List[CallableType], overload: Overloaded, @@ -877,6 +926,7 @@ def no_variant_matches_arguments(self, self.pretty_overload_matches(plausible_targets, overload, context, offset=2, max_items=2) + @tracked def wrong_number_values_to_unpack(self, provided: int, expected: int, context: Context) -> None: if provided < expected: @@ -890,14 +940,17 @@ def wrong_number_values_to_unpack(self, provided: int, expected: int, self.fail('Too many values to unpack ({} expected, {} provided)'.format( expected, provided), context) + @tracked def type_not_iterable(self, type: Type, context: Context) -> None: self.fail('\'{}\' object is not iterable'.format(type), context) + @tracked def incompatible_operator_assignment(self, op: str, context: Context) -> None: self.fail('Result type of {} incompatible in assignment'.format(op), context) + @tracked def overload_signature_incompatible_with_supertype( self, name: str, name_in_super: str, supertype: str, overload: Overloaded, context: Context) -> None: @@ -908,6 +961,7 @@ def overload_signature_incompatible_with_supertype( note_template = 'Overload variants must be defined in the same order as they are in "{}"' self.note(note_template.format(supertype), context) + @tracked def signature_incompatible_with_supertype( self, name: str, name_in_super: str, supertype: str, context: Context) -> None: @@ -915,6 +969,7 @@ def signature_incompatible_with_supertype( self.fail('Signature of "{}" incompatible with {}'.format( name, target), context) + @tracked def argument_incompatible_with_supertype( self, arg_num: int, name: str, type_name: Optional[str], name_in_supertype: str, supertype: str, context: Context) -> None: @@ -935,6 +990,7 @@ def __eq__(self, other: object) -> bool: return '''.format(class_name=class_name)) + @tracked def return_type_incompatible_with_supertype( self, name: str, name_in_supertype: str, supertype: str, context: Context) -> None: @@ -942,6 +998,7 @@ def return_type_incompatible_with_supertype( self.fail('Return type of "{}" incompatible with {}' .format(name, target), context) + @tracked def override_target(self, name: str, name_in_super: str, supertype: str) -> str: target = 'supertype "{}"'.format(supertype) @@ -949,6 +1006,7 @@ def override_target(self, name: str, name_in_super: str, target = '"{}" of {}'.format(name_in_super, target) return target + @tracked def incompatible_type_application(self, expected_arg_count: int, actual_arg_count: int, context: Context) -> None: @@ -962,6 +1020,7 @@ def incompatible_type_application(self, expected_arg_count: int, self.fail('Type application has too few types ({} expected)' .format(expected_arg_count), context) + @tracked def alias_invalid_in_runtime_context(self, item: Type, ctx: Context) -> None: kind = (' to Callable' if isinstance(item, CallableType) else ' to Tuple' if isinstance(item, TupleType) else @@ -970,6 +1029,7 @@ def alias_invalid_in_runtime_context(self, item: Type, ctx: Context) -> None: '') self.fail('The type alias{} is invalid in runtime context'.format(kind), ctx) + @tracked def could_not_infer_type_arguments(self, callee_type: CallableType, n: int, context: Context) -> None: callee_name = callable_name(callee_type) @@ -978,9 +1038,11 @@ def could_not_infer_type_arguments(self, callee_type: CallableType, n: int, else: self.fail('Cannot infer function type argument', context) + @tracked def invalid_var_arg(self, typ: Type, context: Context) -> None: self.fail('List or tuple expected as variable arguments', context) + @tracked def invalid_keyword_var_arg(self, typ: Type, is_mapping: bool, context: Context) -> None: if isinstance(typ, Instance) and is_mapping: self.fail('Keywords must be strings', context) @@ -992,9 +1054,11 @@ def invalid_keyword_var_arg(self, typ: Type, is_mapping: bool, context: Context) 'Argument after ** must be a mapping{}'.format(suffix), context) + @tracked def undefined_in_superclass(self, member: str, context: Context) -> None: self.fail('"{}" undefined in superclass'.format(member), context) + @tracked def first_argument_for_super_must_be_type(self, actual: Type, context: Context) -> None: if isinstance(actual, Instance): # Don't include type of instance, because it can look confusingly like a type @@ -1004,47 +1068,60 @@ def first_argument_for_super_must_be_type(self, actual: Type, context: Context) type_str = self.format(actual) self.fail('Argument 1 for "super" must be a type object; got {}'.format(type_str), context) + @tracked def too_few_string_formatting_arguments(self, context: Context) -> None: self.fail('Not enough arguments for format string', context) + @tracked def too_many_string_formatting_arguments(self, context: Context) -> None: self.fail('Not all arguments converted during string formatting', context) + @tracked def unsupported_placeholder(self, placeholder: str, context: Context) -> None: self.fail('Unsupported format character \'%s\'' % placeholder, context) + @tracked def string_interpolation_with_star_and_key(self, context: Context) -> None: self.fail('String interpolation contains both stars and mapping keys', context) + @tracked def requires_int_or_char(self, context: Context) -> None: self.fail('%c requires int or char', context) + @tracked def key_not_in_mapping(self, key: str, context: Context) -> None: self.fail('Key \'%s\' not found in mapping' % key, context) + @tracked def string_interpolation_mixing_key_and_non_keys(self, context: Context) -> None: self.fail('String interpolation mixes specifier with and without mapping keys', context) + @tracked def cannot_determine_type(self, name: str, context: Context) -> None: self.fail("Cannot determine type of '%s'" % name, context) + @tracked def cannot_determine_type_in_base(self, name: str, base: str, context: Context) -> None: self.fail("Cannot determine type of '%s' in base class '%s'" % (name, base), context) + @tracked def no_formal_self(self, name: str, item: CallableType, context: Context) -> None: self.fail('Attribute function "%s" with type %s does not accept self argument' % (name, self.format(item)), context) + @tracked def incompatible_self_argument(self, name: str, arg: Type, sig: CallableType, is_classmethod: bool, context: Context) -> None: kind = 'class attribute function' if is_classmethod else 'attribute function' self.fail('Invalid self argument %s to %s "%s" with type %s' % (self.format(arg), kind, name, self.format(sig)), context) + @tracked def incompatible_conditional_function_def(self, defn: FuncDef) -> None: self.fail('All conditional function variants must have identical ' 'signatures', defn) + @tracked def cannot_instantiate_abstract_class(self, class_name: str, abstract_attributes: List[str], context: Context) -> None: @@ -1054,6 +1131,7 @@ def cannot_instantiate_abstract_class(self, class_name: str, attrs), context) + @tracked def base_class_definitions_incompatible(self, name: str, base1: TypeInfo, base2: TypeInfo, context: Context) -> None: @@ -1061,15 +1139,19 @@ def base_class_definitions_incompatible(self, name: str, base1: TypeInfo, 'with definition in base class "{}"'.format( name, base1.name(), base2.name()), context) + @tracked def cant_assign_to_method(self, context: Context) -> None: self.fail(CANNOT_ASSIGN_TO_METHOD, context) + @tracked def cant_assign_to_classvar(self, name: str, context: Context) -> None: self.fail('Cannot assign to class variable "%s" via instance' % name, context) + @tracked def final_cant_override_writable(self, name: str, ctx: Context) -> None: self.fail('Cannot override writable attribute "{}" with a final one'.format(name), ctx) + @tracked def cant_override_final(self, name: str, base_name: str, ctx: Context) -> None: self.fail('Cannot override final attribute "{}"' ' (previously declared in base class "{}")'.format(name, base_name), ctx) @@ -1082,17 +1164,21 @@ def cant_assign_to_final(self, name: str, attr_assign: bool, ctx: Context) -> No kind = "attribute" if attr_assign else "name" self.fail('Cannot assign to final {} "{}"'.format(kind, name), ctx) + @tracked def protocol_members_cant_be_final(self, ctx: Context) -> None: self.fail("Protocol member cannot be final", ctx) + @tracked def final_without_value(self, ctx: Context) -> None: self.fail("Final name must be initialized with a value", ctx) + @tracked def read_only_property(self, name: str, type: TypeInfo, context: Context) -> None: self.fail('Property "{}" defined in "{}" is read-only'.format( name, type.name()), context) + @tracked def incompatible_typevar_value(self, callee: CallableType, typ: Type, @@ -1103,22 +1189,26 @@ def incompatible_typevar_value(self, self.format(typ)), context) + @tracked def overload_inconsistently_applies_decorator(self, decorator: str, context: Context) -> None: self.fail( 'Overload does not consistently use the "@{}" '.format(decorator) + 'decorator on all function signatures.', context) + @tracked def overloaded_signatures_overlap(self, index1: int, index2: int, context: Context) -> None: self.fail('Overloaded function signatures {} and {} overlap with ' 'incompatible return types'.format(index1, index2), context) + @tracked def overloaded_signatures_partial_overlap(self, index1: int, index2: int, context: Context) -> None: self.fail('Overloaded function signatures {} and {} '.format(index1, index2) + 'are partially overlapping: the two signatures may return ' + 'incompatible types given certain calls', context) + @tracked def overloaded_signature_will_never_match(self, index1: int, index2: int, context: Context) -> None: self.fail( @@ -1128,24 +1218,30 @@ def overloaded_signature_will_never_match(self, index1: int, index2: int, index2=index2), context) + @tracked def overloaded_signatures_typevar_specific(self, index: int, context: Context) -> None: self.fail('Overloaded function implementation cannot satisfy signature {} '.format(index) + 'due to inconsistencies in how they use type variables', context) + @tracked def overloaded_signatures_arg_specific(self, index: int, context: Context) -> None: self.fail('Overloaded function implementation does not accept all possible arguments ' 'of signature {}'.format(index), context) + @tracked def overloaded_signatures_ret_specific(self, index: int, context: Context) -> None: self.fail('Overloaded function implementation cannot produce return type ' 'of signature {}'.format(index), context) + @tracked def warn_both_operands_are_from_unions(self, context: Context) -> None: self.note('Both left and right operands are unions', context) + @tracked def warn_operand_was_from_union(self, side: str, original: Type, context: Context) -> None: self.note('{} operand is of type {}'.format(side, self.format(original)), context) + @tracked def operator_method_signatures_overlap( self, reverse_class: TypeInfo, reverse_method: str, forward_class: Type, forward_method: str, context: Context) -> None: @@ -1155,31 +1251,38 @@ def operator_method_signatures_overlap( forward_method, self.format(forward_class)), context) + @tracked def forward_operator_not_callable( self, forward_method: str, context: Context) -> None: self.fail('Forward operator "{}" is not callable'.format( forward_method), context) + @tracked def signatures_incompatible(self, method: str, other_method: str, context: Context) -> None: self.fail('Signatures of "{}" and "{}" are incompatible'.format( method, other_method), context) + @tracked def yield_from_invalid_operand_type(self, expr: Type, context: Context) -> Type: text = self.format(expr) if self.format(expr) != 'object' else expr self.fail('"yield from" can\'t be applied to {}'.format(text), context) return AnyType(TypeOfAny.from_error) + @tracked def invalid_signature(self, func_type: Type, context: Context) -> None: self.fail('Invalid signature "{}"'.format(func_type), context) + @tracked def invalid_signature_for_special_method( self, func_type: Type, context: Context, method_name: str) -> None: self.fail('Invalid signature "{}" for "{}"'.format(func_type, method_name), context) + @tracked def reveal_type(self, typ: Type, context: Context) -> None: self.fail('Revealed type is \'{}\''.format(typ), context) + @tracked def reveal_locals(self, type_map: Dict[str, Optional[Type]], context: Context) -> None: # To ensure that the output is predictable on Python < 3.6, # use an ordered dictionary sorted by variable name @@ -1188,22 +1291,28 @@ def reveal_locals(self, type_map: Dict[str, Optional[Type]], context: Context) - for line in ['{}: {}'.format(k, v) for k, v in sorted_locals.items()]: self.fail(line, context) + @tracked def unsupported_type_type(self, item: Type, context: Context) -> None: self.fail('Unsupported type Type[{}]'.format(self.format(item)), context) + @tracked def redundant_cast(self, typ: Type, context: Context) -> None: self.note('Redundant cast to {}'.format(self.format(typ)), context) + @tracked def unimported_type_becomes_any(self, prefix: str, typ: Type, ctx: Context) -> None: self.fail("{} becomes {} due to an unfollowed import".format(prefix, self.format(typ)), ctx) + @tracked def need_annotation_for_var(self, node: SymbolNode, context: Context) -> None: self.fail("Need type annotation for '{}'".format(node.name()), context) + @tracked def explicit_any(self, ctx: Context) -> None: self.fail('Explicit "Any" is not allowed', ctx) + @tracked def unexpected_typeddict_keys( self, typ: TypedDictType, @@ -1239,6 +1348,7 @@ def unexpected_typeddict_keys( found = 'only {}'.format(found) self.fail('Expected {} but found {}'.format(expected, found), context) + @tracked def typeddict_key_must_be_string_literal( self, typ: TypedDictType, @@ -1247,6 +1357,7 @@ def typeddict_key_must_be_string_literal( 'TypedDict key must be a string literal; expected one of {}'.format( format_item_name_list(typ.items.keys())), context) + @tracked def typeddict_key_not_found( self, typ: TypedDictType, @@ -1258,6 +1369,7 @@ def typeddict_key_not_found( else: self.fail("TypedDict {} has no key '{}'".format(self.format(typ), item_name), context) + @tracked def typeddict_key_cannot_be_deleted( self, typ: TypedDictType, @@ -1270,6 +1382,7 @@ def typeddict_key_cannot_be_deleted( self.fail("Key '{}' of TypedDict {} cannot be deleted".format( item_name, self.format(typ)), context) + @tracked def type_arguments_not_allowed(self, context: Context) -> None: self.fail('Parameterized generics cannot be used with class or instance checks', context) @@ -1280,11 +1393,13 @@ def disallowed_any_type(self, typ: Type, context: Context) -> None: message = 'Expression type contains "Any" (has type {})'.format(self.format(typ)) self.fail(message, context) + @tracked def incorrectly_returning_any(self, typ: Type, context: Context) -> None: message = 'Returning Any from function declared to return {}'.format( self.format(typ)) self.warn(message, context) + @tracked def untyped_decorated_function(self, typ: Type, context: Context) -> None: if isinstance(typ, AnyType): self.fail("Function is untyped after decorator transformation", context) @@ -1292,9 +1407,11 @@ def untyped_decorated_function(self, typ: Type, context: Context) -> None: self.fail('Type of decorated function contains type "Any" ({})'.format( self.format(typ)), context) + @tracked def typed_function_untyped_decorator(self, func_name: str, context: Context) -> None: self.fail('Untyped decorator makes function "{}" untyped'.format(func_name), context) + @tracked def bad_proto_variance(self, actual: int, tvar_name: str, expected: int, context: Context) -> None: msg = capitalize("{} type variable '{}' used in protocol where" @@ -1303,18 +1420,22 @@ def bad_proto_variance(self, actual: int, tvar_name: str, expected: int, variance_string(expected))) self.fail(msg, context) + @tracked def concrete_only_assign(self, typ: Type, context: Context) -> None: self.fail("Can only assign concrete classes to a variable of type {}" .format(self.format(typ)), context) + @tracked def concrete_only_call(self, typ: Type, context: Context) -> None: self.fail("Only concrete class can be given where {} is expected" .format(self.format(typ)), context) + @tracked def cannot_use_function_with_type( self, method_name: str, type_name: str, context: Context) -> None: self.fail("Cannot use {}() with a {} type".format(method_name, type_name), context) + @tracked def report_non_method_protocol(self, tp: TypeInfo, members: List[str], context: Context) -> None: self.fail("Only protocols that don't have non-method members can be" From 51146b0ef72b53244494d527db09b9807695f1dc Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Fri, 4 Jan 2019 22:47:28 -0800 Subject: [PATCH 02/11] Add --show-error-codes option to display known error codes in messages --- mypy/build.py | 3 ++- mypy/errors.py | 53 ++++++++++++++++++++++++++++++------------------ mypy/main.py | 3 +++ mypy/messages.py | 6 +++++- mypy/options.py | 1 + 5 files changed, 44 insertions(+), 22 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 6b23ad981032..20cdad7fbb73 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -189,7 +189,8 @@ def _build(sources: List[BuildSource], reports = Reports(data_dir, options.report_dirs) source_set = BuildSourceSet(sources) - errors = Errors(options.show_error_context, options.show_column_numbers) + errors = Errors(options.show_error_context, options.show_column_numbers, + options.show_error_codes) plugin, snapshot = load_plugins(options, errors) # Construct a build manager object to hold state during the build. diff --git a/mypy/errors.py b/mypy/errors.py index 0bdba053d928..347ba392c0e4 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -136,6 +136,9 @@ class Errors: # Set to True to show column numbers in error messages. show_column_numbers = False # type: bool + # Set to True to show error codes in error messages. + show_error_codes = False + # State for keeping track of the current fine-grained incremental mode target. # (See mypy.server.update for more about targets.) # Current module id. @@ -143,9 +146,11 @@ class Errors: scope = None # type: Optional[Scope] def __init__(self, show_error_context: bool = False, - show_column_numbers: bool = False) -> None: + show_column_numbers: bool = False, + show_error_codes: bool = False) -> None: self.show_error_context = show_error_context self.show_column_numbers = show_column_numbers + self.show_error_codes = show_error_codes self.initialize() def initialize(self) -> None: @@ -165,7 +170,7 @@ def reset(self) -> None: self.initialize() def copy(self) -> 'Errors': - new = Errors(self.show_error_context, self.show_column_numbers) + new = Errors(self.show_error_context, self.show_column_numbers, self.show_error_codes) new.file = self.file new.import_ctx = self.import_ctx[:] new.type_name = self.type_name[:] @@ -359,7 +364,7 @@ def format_messages(self, error_info: List[ErrorInfo]) -> List[str]: a = [] # type: List[str] errors = self.render_messages(self.sort_messages(error_info)) errors = self.remove_duplicates(errors) - for file, line, column, severity, message in errors: + for file, line, column, severity, message, id in errors: s = '' if file is not None: if self.show_column_numbers and line is not None and line >= 0 \ @@ -369,7 +374,11 @@ def format_messages(self, error_info: List[ErrorInfo]) -> List[str]: srcloc = '{}:{}'.format(file, line) else: srcloc = file - s = '{}: {}: {}'.format(srcloc, severity, message) + if self.show_error_codes: + s = '{}: {}: {}: {}'.format(srcloc, id if id is not None else 'unknown', + severity, message) + else: + s = '{}: {}: {}'.format(srcloc, severity, message) else: s = message a.append(s) @@ -407,8 +416,8 @@ def targets(self) -> Set[str]: for info in errs if info.target) - def render_messages(self, errors: List[ErrorInfo]) -> List[Tuple[Optional[str], int, int, - str, str]]: + def render_messages(self, errors: List[ErrorInfo] + ) -> List[Tuple[Optional[str], int, int, str, str, Optional[str]]]: """Translate the messages into a sequence of tuples. Each tuple is of form (path, line, col, severity, message). @@ -416,12 +425,13 @@ def render_messages(self, errors: List[ErrorInfo]) -> List[Tuple[Optional[str], The path item may be None. If the line item is negative, the line number is not defined for the tuple. """ - result = [] # type: List[Tuple[Optional[str], int, int, str, str]] + result = [] # type: List[Tuple[Optional[str], int, int, str, str, Optional[str]]] # (path, line, column, severity, message) prev_import_context = [] # type: List[Tuple[str, int]] prev_function_or_member = None # type: Optional[str] prev_type = None # type: Optional[str] + prev_msg_id = None # type: Optional[str] for e in errors: # Report module import context, if different from previous message. @@ -442,7 +452,7 @@ def render_messages(self, errors: List[ErrorInfo]) -> List[Tuple[Optional[str], # Remove prefix to ignore from path (if present) to # simplify path. path = remove_path_prefix(path, self.ignore_prefix) - result.append((None, -1, -1, 'note', fmt.format(path, line))) + result.append((None, -1, -1, 'note', fmt.format(path, line), prev_msg_id)) i -= 1 file = self.simplify_path(e.file) @@ -454,31 +464,34 @@ def render_messages(self, errors: List[ErrorInfo]) -> List[Tuple[Optional[str], e.type != prev_type): if e.function_or_member is None: if e.type is None: - result.append((file, -1, -1, 'note', 'At top level:')) + result.append((file, -1, -1, 'note', 'At top level:', prev_msg_id)) else: - result.append((file, -1, -1, 'note', 'In class "{}":'.format( - e.type))) + result.append((file, -1, -1, 'note', + 'In class "{}":'.format(e.type), + prev_msg_id)) else: if e.type is None: result.append((file, -1, -1, 'note', - 'In function "{}":'.format( - e.function_or_member))) + 'In function "{}":'.format(e.function_or_member), + prev_msg_id)) else: result.append((file, -1, -1, 'note', 'In member "{}" of class "{}":'.format( - e.function_or_member, e.type))) + e.function_or_member, e.type), + prev_msg_id)) elif e.type != prev_type: if e.type is None: - result.append((file, -1, -1, 'note', 'At top level:')) + result.append((file, -1, -1, 'note', 'At top level:', prev_msg_id)) else: result.append((file, -1, -1, 'note', - 'In class "{}":'.format(e.type))) + 'In class "{}":'.format(e.type), prev_msg_id)) - result.append((file, e.line, e.column, e.severity, e.message)) + result.append((file, e.line, e.column, e.severity, e.message, e.id)) prev_import_context = e.import_ctx prev_function_or_member = e.function_or_member prev_type = e.type + prev_msg_id = e.id return result @@ -505,10 +518,10 @@ def sort_messages(self, errors: List[ErrorInfo]) -> List[ErrorInfo]: result.extend(a) return result - def remove_duplicates(self, errors: List[Tuple[Optional[str], int, int, str, str]] - ) -> List[Tuple[Optional[str], int, int, str, str]]: + def remove_duplicates(self, errors: List[Tuple[Optional[str], int, int, str, str, Optional[str]]] + ) -> List[Tuple[Optional[str], int, int, Optional[str], str, str]]: """Remove duplicates from a sorted error list.""" - res = [] # type: List[Tuple[Optional[str], int, int, str, str]] + res = [] # type: List[Tuple[Optional[str], int, int, str, str, Optional[str]]] i = 0 while i < len(errors): dup = False diff --git a/mypy/main.py b/mypy/main.py index 87051eaa1d0d..006adfdbbebc 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -597,6 +597,9 @@ def add_invertible_flag(flag: str, add_invertible_flag('--show-column-numbers', default=False, help="Show column numbers in error messages", group=error_group) + add_invertible_flag('--show-error-codes', default=False, + help="Show error codes in error messages", + group=error_group) strict_help = "Strict mode; enables the following flags: {}".format( ", ".join(strict_flag_names)) diff --git a/mypy/messages.py b/mypy/messages.py index 1e606d775cd3..29fba71531a8 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -261,11 +261,15 @@ def report(self, msg: str, context: Optional[Context], severity: str, offset: int = 0, id: Optional[str] = None) -> None: """Report an error or note (unless disabled).""" if self.disable_count <= 0: + msg_id = id + if msg_id is None and len(self.active_msg_id): + msg_id = self.active_msg_id[-1] + self.errors.report(context.get_line() if context else -1, context.get_column() if context else -1, msg, severity=severity, file=file, offset=offset, origin_line=origin.get_line() if origin else None, - id=id if id is not None else self.active_msg_id) + id=msg_id) def fail(self, msg: str, context: Optional[Context], file: Optional[str] = None, origin: Optional[Context] = None) -> None: diff --git a/mypy/options.py b/mypy/options.py index f36ef9190e04..dc1e4e9b1ad6 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -206,6 +206,7 @@ def __init__(self) -> None: # -- experimental options -- self.shadow_file = None # type: Optional[List[List[str]]] self.show_column_numbers = False # type: bool + self.show_error_codes = False # type: bool self.dump_graph = False self.dump_deps = False self.logical_deps = False From 90529813d0bb06c7aeaf01ca1f5e98c6b80fadd0 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Fri, 4 Jan 2019 22:58:29 -0800 Subject: [PATCH 03/11] Add --list-error-codes to list known message error codes --- mypy/main.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mypy/main.py b/mypy/main.py index 006adfdbbebc..29731283513a 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -600,6 +600,9 @@ def add_invertible_flag(flag: str, add_invertible_flag('--show-error-codes', default=False, help="Show error codes in error messages", group=error_group) + error_group.add_argument( + '--list-error-codes', action='store_true', + help="List known error codes and exit") strict_help = "Strict mode; enables the following flags: {}".format( ", ".join(strict_flag_names)) @@ -701,6 +704,13 @@ def add_invertible_flag(flag: str, # filename for the config file and know if the user requested all strict options. dummy = argparse.Namespace() parser.parse_args(args, dummy) + + if dummy.list_error_codes: + import mypy.messages + for msg_id in sorted(mypy.messages.message_ids): + print(msg_id) + raise SystemExit(0) + config_file = dummy.config_file if config_file is not None and not os.path.exists(config_file): parser.error("Cannot find config file '%s'" % config_file) From 69d921a855ab935cb2b8d6368fc462ea448842af Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Fri, 4 Jan 2019 23:34:36 -0800 Subject: [PATCH 04/11] get all tests passing --- mypy/errors.py | 5 +++-- mypy/main.py | 1 + mypy/messages.py | 14 ++++++++------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/mypy/errors.py b/mypy/errors.py index 347ba392c0e4..4ddbeaabb3af 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -518,8 +518,9 @@ def sort_messages(self, errors: List[ErrorInfo]) -> List[ErrorInfo]: result.extend(a) return result - def remove_duplicates(self, errors: List[Tuple[Optional[str], int, int, str, str, Optional[str]]] - ) -> List[Tuple[Optional[str], int, int, Optional[str], str, str]]: + def remove_duplicates(self, + errors: List[Tuple[Optional[str], int, int, str, str, Optional[str]]] + ) -> List[Tuple[Optional[str], int, int, str, str, Optional[str]]]: """Remove duplicates from a sorted error list.""" res = [] # type: List[Tuple[Optional[str], int, int, str, str, Optional[str]]] i = 0 diff --git a/mypy/main.py b/mypy/main.py index 29731283513a..e25eaeec135d 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -728,6 +728,7 @@ def add_invertible_flag(flag: str, # Parse command line for real, using a split namespace. special_opts = argparse.Namespace() parser.parse_args(args, SplitNamespace(options, special_opts, 'special-opts:')) + delattr(options, 'list_error_codes') # The python_version is either the default, which can be overridden via a config file, # or stored in special_opts and is passed via the command line. diff --git a/mypy/messages.py b/mypy/messages.py index 29fba71531a8..5cd9011670db 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -16,7 +16,7 @@ from textwrap import dedent from typing import ( - cast, List, Dict, Any, Sequence, Iterable, Tuple, Set, Optional, Union, ClassVar, TypeVar, + cast, List, Dict, Any, Sequence, Iterable, Tuple, Set, Optional, Union, Deque, TypeVar, Callable ) @@ -38,7 +38,7 @@ if MYPY: from typing_extensions import Final -T = TypeVar('T', bound=Callable) +T = TypeVar('T', bound=Callable[..., Any]) # Type checker error message constants -- @@ -184,7 +184,7 @@ def tracked(func: T) -> T: msg_id = func.__name__ message_ids.add(msg_id) - def wrapped(self, *args, **kwargs): + def wrapped(self: 'MessageBuilder', *args: Any, **kwargs: Any) -> Any: assert len(self.active_msg_id) == 0 or self.active_msg_id[-1] != msg_id self.active_msg_id.append(msg_id) try: @@ -192,7 +192,7 @@ def wrapped(self, *args, **kwargs): finally: self.active_msg_id.pop() - return wrapped + return wrapped # type: ignore class MessageBuilder: @@ -218,6 +218,8 @@ class MessageBuilder: # Hack to deduplicate error messages from union types disable_type_names = 0 + active_msg_id = None # type: Deque[str] + def __init__(self, errors: Errors, modules: Dict[str, MypyFile]) -> None: self.errors = errors self.modules = modules @@ -500,11 +502,11 @@ def format_distinctly(self, type1: Type, type2: Type, bare: bool = False) -> Tup # on them. @tracked - def no_return_exepected(self, context: Context): + def no_return_exepected(self, context: Context) -> None: self.fail('No return value expected', context) @tracked - def must_have_none_return_type(self, context: FuncDef): + def must_have_none_return_type(self, context: FuncDef) -> None: self.fail('The return type of "{}" must be None'.format(context.name()), context) @tracked From 3c3b52f2b096732a597c56fa1ab169aa805968c2 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Sat, 5 Jan 2019 17:47:20 -0800 Subject: [PATCH 05/11] cleanup --- mypy/checker.py | 12 +-- mypy/checkexpr.py | 5 +- mypy/checkmember.py | 14 +-- mypy/main.py | 4 +- mypy/messages.py | 245 ++++++++++++++++++++++---------------------- mypy/semanal.py | 4 +- 6 files changed, 145 insertions(+), 139 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index d7aef4146808..6998941da775 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1304,7 +1304,7 @@ def check_method_or_accessor_override_for_base(self, defn: Union[FuncDef, if base_attr: # First, check if we override a final (always an error, even with Any types). if is_final_node(base_attr.node): - self.msg.cant_override_final(name, base.name(), defn) + self.msg.cannot_override_final(name, base.name(), defn) # Second, final can't override anything writeable independently of types. if defn.is_final: self.check_no_writable(name, base_attr.node, defn) @@ -1641,7 +1641,7 @@ def check_compatibility(self, name: str, base1: TypeInfo, # Final attributes can never be overridden, but can override # non-final read-only attributes. if is_final_node(second.node): - self.msg.cant_override_final(name, base2.name(), ctx) + self.msg.cannot_override_final(name, base2.name(), ctx) if is_final_node(first.node): self.check_no_writable(name, second.node, ctx) # __slots__ is special and the type can vary across class hierarchy. @@ -1988,7 +1988,7 @@ def check_compatibility_final_super(self, node: Var, # if we are overriding a final method with variable. # Other override attempts will be flagged as assignment to constant # in `check_final()`. - self.msg.cant_override_final(node.name(), base.name(), node) + self.msg.cannot_override_final(node.name(), base.name(), node) return False if node.is_final: self.check_no_writable(node.name(), base_node, node) @@ -2014,7 +2014,7 @@ class D(C): else: ok = True if not ok: - self.msg.final_cant_override_writable(name, ctx) + self.msg.final_cannot_override_writable(name, ctx) def get_final_context(self) -> bool: """Check whether we a currently checking a final declaration.""" @@ -2067,11 +2067,11 @@ def check_final(self, s: Union[AssignmentStmt, OperatorAssignmentStmt]) -> None: # `check_compatibility_final_super()`. if sym and isinstance(sym.node, Var): if sym.node.is_final and not is_final_decl: - self.msg.cant_assign_to_final(name, sym.node.info is None, s) + self.msg.cannot_assign_to_final(name, sym.node.info is None, s) # ...but only once break if lv.node.is_final and not is_final_decl: - self.msg.cant_assign_to_final(name, lv.node.info is None, s) + self.msg.cannot_assign_to_final(name, lv.node.info is None, s) def check_assignment_to_multiple_lvalues(self, lvalues: List[Lvalue], rvalue: Expression, context: Context, diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index b6bb5f95662d..6e06a06b78f0 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -404,7 +404,7 @@ def check_protocol_issubclass(self, e: CallExpr) -> None: tp.type_object().is_protocol): attr_members = non_method_protocol_members(tp.type_object()) if attr_members: - self.chk.msg.report_non_method_protocol(tp.type_object(), + self.chk.msg.issubclass_on_non_method_protocol(tp.type_object(), attr_members, e) def check_typeddict_call(self, callee: TypedDictType, @@ -720,7 +720,8 @@ def check_call(self, arg_names, callable_node, arg_messages, callable_name, object_type) else: - return self.msg.not_callable(callee, context), AnyType(TypeOfAny.from_error) + return (self.msg.type_has_no_attr(callee, callee, '__call__', context), + AnyType(TypeOfAny.from_error)) def check_callable_call(self, callee: CallableType, diff --git a/mypy/checkmember.py b/mypy/checkmember.py index c0bed7810308..bb86a3fb1a02 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -133,7 +133,7 @@ def _analyze_member_access(name: str, return AnyType(TypeOfAny.from_error) if mx.chk.should_suppress_optional_error([typ]): return AnyType(TypeOfAny.from_error) - return mx.msg.has_no_attr(mx.original_type, typ, name, mx.context) + return mx.msg.type_has_no_attr(mx.original_type, typ, name, mx.context) # The several functions that follow implement analyze_member_access for various @@ -169,7 +169,7 @@ def analyze_instance_member_access(name: str, first_item = cast(Decorator, method.items[0]) return analyze_var(name, first_item.var, typ, info, mx) if mx.is_lvalue: - mx.msg.cant_assign_to_method(mx.context) + mx.msg.cannot_assign_to_method(mx.context) signature = function_type(method, mx.builtin_type('builtins.function')) signature = freshen_function_type_vars(signature) if name == '__new__': @@ -360,7 +360,7 @@ def analyze_member_var_access(name: str, else: if mx.chk and mx.chk.should_suppress_optional_error([itype]): return AnyType(TypeOfAny.from_error) - return mx.msg.has_no_attr(mx.original_type, itype, name, mx.context) + return mx.msg.type_has_no_attr(mx.original_type, itype, name, mx.context) def check_final_member(name: str, info: TypeInfo, msg: MessageBuilder, ctx: Context) -> None: @@ -368,7 +368,7 @@ def check_final_member(name: str, info: TypeInfo, msg: MessageBuilder, ctx: Cont for base in info.mro: sym = base.names.get(name) if sym and is_final_node(sym.node): - msg.cant_assign_to_final(name, attr_assign=True, ctx=ctx) + msg.cannot_assign_to_final(name, attr_assign=True, ctx=ctx) def analyze_descriptor_access(instance_type: Type, @@ -476,7 +476,7 @@ def analyze_var(name: str, # TODO allow setting attributes in subclass (although it is probably an error) mx.msg.read_only_property(name, itype.type, mx.context) if mx.is_lvalue and var.is_classvar: - mx.msg.cant_assign_to_classvar(name, mx.context) + mx.msg.cannot_assign_to_classvar(name, mx.context) result = t if var.is_initialized_in_class and isinstance(t, FunctionLike) and not t.is_type_obj(): if mx.is_lvalue: @@ -484,7 +484,7 @@ def analyze_var(name: str, if not var.is_settable_property: mx.msg.read_only_property(name, itype.type, mx.context) else: - mx.msg.cant_assign_to_method(mx.context) + mx.msg.cannot_assign_to_method(mx.context) if not var.is_staticmethod: # Class-level function objects and classmethods become bound methods: @@ -584,7 +584,7 @@ def analyze_class_attribute_access(itype: Instance, is_method = is_decorated or isinstance(node.node, FuncBase) if mx.is_lvalue: if is_method: - mx.msg.cant_assign_to_method(mx.context) + mx.msg.cannot_assign_to_method(mx.context) if isinstance(node.node, TypeInfo): mx.msg.fail(messages.CANNOT_ASSIGN_TO_TYPE, mx.context) diff --git a/mypy/main.py b/mypy/main.py index e25eaeec135d..7b8837162695 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -707,7 +707,7 @@ def add_invertible_flag(flag: str, if dummy.list_error_codes: import mypy.messages - for msg_id in sorted(mypy.messages.message_ids): + for msg_id in sorted(mypy.messages.MessageBuilder.get_message_ids()): print(msg_id) raise SystemExit(0) @@ -728,6 +728,8 @@ def add_invertible_flag(flag: str, # Parse command line for real, using a split namespace. special_opts = argparse.Namespace() parser.parse_args(args, SplitNamespace(options, special_opts, 'special-opts:')) + + # unneeded attribute transfered from parser delattr(options, 'list_error_codes') # The python_version is either the default, which can be overridden via a config file, diff --git a/mypy/messages.py b/mypy/messages.py index 5cd9011670db..7113f17d85c2 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -11,6 +11,7 @@ """ from collections import OrderedDict, deque +from functools import wraps import re import difflib from textwrap import dedent @@ -177,22 +178,25 @@ } # type: Final -message_ids = set() # type: Set[str] +_message_ids = set() # type: Set[str] -def tracked(func: T) -> T: - msg_id = func.__name__ - message_ids.add(msg_id) +def tracked(id: Optional[str] = None) -> Callable[[T], T]: + def deco(func: T) -> T: + msg_id = id if id is not None else func.__name__ + _message_ids.add(msg_id) - def wrapped(self: 'MessageBuilder', *args: Any, **kwargs: Any) -> Any: - assert len(self.active_msg_id) == 0 or self.active_msg_id[-1] != msg_id - self.active_msg_id.append(msg_id) - try: - return func(self, *args, **kwargs) - finally: - self.active_msg_id.pop() + @wraps(func) + def wrapped(self: 'MessageBuilder', *args: Any, **kwargs: Any) -> Any: + assert self.active_message_id() != msg_id + self.active_msg_ids.append(msg_id) + try: + return func(self, *args, **kwargs) + finally: + self.active_msg_ids.pop() - return wrapped # type: ignore + return wrapped # type: ignore + return deco class MessageBuilder: @@ -218,14 +222,18 @@ class MessageBuilder: # Hack to deduplicate error messages from union types disable_type_names = 0 - active_msg_id = None # type: Deque[str] + active_msg_ids = None # type: Deque[str] def __init__(self, errors: Errors, modules: Dict[str, MypyFile]) -> None: self.errors = errors self.modules = modules self.disable_count = 0 self.disable_type_names = 0 - self.active_msg_id = deque() + self.active_msg_ids = deque() + + @classmethod + def get_message_ids(cls) -> Set[str]: + return _message_ids # # Helpers @@ -264,8 +272,8 @@ def report(self, msg: str, context: Optional[Context], severity: str, """Report an error or note (unless disabled).""" if self.disable_count <= 0: msg_id = id - if msg_id is None and len(self.active_msg_id): - msg_id = self.active_msg_id[-1] + if msg_id is None: + msg_id = self.active_message_id() self.errors.report(context.get_line() if context else -1, context.get_column() if context else -1, @@ -501,16 +509,16 @@ def format_distinctly(self, type1: Type, type2: Type, bare: bool = False) -> Tup # get some information as arguments, and they build an error message based # on them. - @tracked + @tracked() def no_return_exepected(self, context: Context) -> None: self.fail('No return value expected', context) - @tracked + @tracked() def must_have_none_return_type(self, context: FuncDef) -> None: self.fail('The return type of "{}" must be None'.format(context.name()), context) - @tracked - def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Context) -> Type: + @tracked() + def type_has_no_attr(self, original_type: Type, typ: Type, member: str, context: Context) -> Type: """Report a missing or non-accessible member. original_type is the top-level type on which the error occurred. @@ -601,7 +609,7 @@ def has_no_attr(self, original_type: Type, typ: Type, member: str, context: Cont typ_format, self.format(original_type), member, extra), context) return AnyType(TypeOfAny.from_error) - @tracked + @tracked() def unsupported_operand_types(self, op: str, left_type: Any, right_type: Any, context: Context) -> None: """Report unsupported operand types for a binary operation. @@ -627,7 +635,7 @@ def unsupported_operand_types(self, op: str, left_type: Any, op, left_str, right_str) self.fail(msg, context) - @tracked + @tracked() def unsupported_left_operand(self, op: str, typ: Type, context: Context) -> None: if self.disable_type_names: @@ -637,18 +645,13 @@ def unsupported_left_operand(self, op: str, typ: Type, op, self.format(typ)) self.fail(msg, context) - @tracked - def not_callable(self, typ: Type, context: Context) -> Type: - self.fail('{} not callable'.format(self.format(typ)), context) - return AnyType(TypeOfAny.from_error) - - @tracked + @tracked() def untyped_function_call(self, callee: CallableType, context: Context) -> Type: name = callable_name(callee) or '(unknown)' self.fail('Call to untyped function {} in typed context'.format(name), context) return AnyType(TypeOfAny.from_error) - @tracked + @tracked() def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: Type, arg_kind: int, context: Context) -> None: """Report an error about an incompatible argument type. @@ -798,13 +801,13 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: for note_msg in notes: self.note(note_msg, context) - @tracked + @tracked() def invalid_index_type(self, index_type: Type, expected_type: Type, base_str: str, context: Context) -> None: self.fail('Invalid index type {} for {}; expected type {}'.format( self.format(index_type), base_str, self.format(expected_type)), context) - @tracked + @tracked('wrong_number_of_arguments') def too_few_arguments(self, callee: CallableType, context: Context, argument_names: Optional[Sequence[Optional[str]]]) -> None: if (argument_names is not None and not all(k is None for k in argument_names) @@ -822,17 +825,17 @@ def too_few_arguments(self, callee: CallableType, context: Context, msg = 'Too few arguments' + for_function(callee) self.fail(msg, context) - @tracked + @tracked() def missing_named_argument(self, callee: CallableType, context: Context, name: str) -> None: msg = 'Missing named argument "{}"'.format(name) + for_function(callee) self.fail(msg, context) - @tracked + @tracked('wrong_number_of_arguments') def too_many_arguments(self, callee: CallableType, context: Context) -> None: msg = 'Too many arguments' + for_function(callee) self.fail(msg, context) - @tracked + @tracked() def too_many_arguments_from_typed_dict(self, callee: CallableType, arg_type: TypedDictType, @@ -847,13 +850,13 @@ def too_many_arguments_from_typed_dict(self, return self.fail(msg, context) - @tracked + @tracked() def too_many_positional_arguments(self, callee: CallableType, context: Context) -> None: msg = 'Too many positional arguments' + for_function(callee) self.fail(msg, context) - @tracked + @tracked() def unexpected_keyword_argument(self, callee: CallableType, name: str, context: Context) -> None: msg = 'Unexpected keyword argument "{}"'.format(name) + for_function(callee) @@ -867,14 +870,14 @@ def unexpected_keyword_argument(self, callee: CallableType, name: str, self.note('{} defined here'.format(fname), callee.definition, file=module.path, origin=context) - @tracked + @tracked() def duplicate_argument_value(self, callee: CallableType, index: int, context: Context) -> None: self.fail('{} gets multiple values for keyword argument "{}"'. format(callable_name(callee) or 'Function', callee.arg_names[index]), context) - @tracked + @tracked() def does_not_return_value(self, callee_type: Optional[Type], context: Context) -> None: """Report an error about use of an unusable type.""" name = None # type: Optional[str] @@ -885,7 +888,7 @@ def does_not_return_value(self, callee_type: Optional[Type], context: Context) - else: self.fail('Function does not return a value', context) - @tracked + @tracked() def deleted_as_rvalue(self, typ: DeletedType, context: Context) -> None: """Report an error about using an deleted type as an rvalue.""" if typ.source is None: @@ -894,7 +897,7 @@ def deleted_as_rvalue(self, typ: DeletedType, context: Context) -> None: s = " '{}'".format(typ.source) self.fail('Trying to read deleted variable{}'.format(s), context) - @tracked + @tracked() def deleted_as_lvalue(self, typ: DeletedType, context: Context) -> None: """Report an error about using an deleted type as an lvalue. @@ -907,7 +910,7 @@ def deleted_as_lvalue(self, typ: DeletedType, context: Context) -> None: s = " '{}'".format(typ.source) self.fail('Assignment to variable{} outside except: block'.format(s), context) - @tracked + @tracked() def no_variant_matches_arguments(self, plausible_targets: List[CallableType], overload: Overloaded, @@ -932,7 +935,7 @@ def no_variant_matches_arguments(self, self.pretty_overload_matches(plausible_targets, overload, context, offset=2, max_items=2) - @tracked + @tracked() def wrong_number_values_to_unpack(self, provided: int, expected: int, context: Context) -> None: if provided < expected: @@ -946,17 +949,17 @@ def wrong_number_values_to_unpack(self, provided: int, expected: int, self.fail('Too many values to unpack ({} expected, {} provided)'.format( expected, provided), context) - @tracked + @tracked() def type_not_iterable(self, type: Type, context: Context) -> None: self.fail('\'{}\' object is not iterable'.format(type), context) - @tracked + @tracked() def incompatible_operator_assignment(self, op: str, context: Context) -> None: self.fail('Result type of {} incompatible in assignment'.format(op), context) - @tracked + @tracked() def overload_signature_incompatible_with_supertype( self, name: str, name_in_super: str, supertype: str, overload: Overloaded, context: Context) -> None: @@ -967,7 +970,7 @@ def overload_signature_incompatible_with_supertype( note_template = 'Overload variants must be defined in the same order as they are in "{}"' self.note(note_template.format(supertype), context) - @tracked + @tracked() def signature_incompatible_with_supertype( self, name: str, name_in_super: str, supertype: str, context: Context) -> None: @@ -975,7 +978,7 @@ def signature_incompatible_with_supertype( self.fail('Signature of "{}" incompatible with {}'.format( name, target), context) - @tracked + @tracked() def argument_incompatible_with_supertype( self, arg_num: int, name: str, type_name: Optional[str], name_in_supertype: str, supertype: str, context: Context) -> None: @@ -996,7 +999,7 @@ def __eq__(self, other: object) -> bool: return '''.format(class_name=class_name)) - @tracked + @tracked() def return_type_incompatible_with_supertype( self, name: str, name_in_supertype: str, supertype: str, context: Context) -> None: @@ -1004,7 +1007,6 @@ def return_type_incompatible_with_supertype( self.fail('Return type of "{}" incompatible with {}' .format(name, target), context) - @tracked def override_target(self, name: str, name_in_super: str, supertype: str) -> str: target = 'supertype "{}"'.format(supertype) @@ -1012,7 +1014,7 @@ def override_target(self, name: str, name_in_super: str, target = '"{}" of {}'.format(name_in_super, target) return target - @tracked + @tracked() def incompatible_type_application(self, expected_arg_count: int, actual_arg_count: int, context: Context) -> None: @@ -1026,7 +1028,7 @@ def incompatible_type_application(self, expected_arg_count: int, self.fail('Type application has too few types ({} expected)' .format(expected_arg_count), context) - @tracked + @tracked() def alias_invalid_in_runtime_context(self, item: Type, ctx: Context) -> None: kind = (' to Callable' if isinstance(item, CallableType) else ' to Tuple' if isinstance(item, TupleType) else @@ -1035,7 +1037,7 @@ def alias_invalid_in_runtime_context(self, item: Type, ctx: Context) -> None: '') self.fail('The type alias{} is invalid in runtime context'.format(kind), ctx) - @tracked + @tracked() def could_not_infer_type_arguments(self, callee_type: CallableType, n: int, context: Context) -> None: callee_name = callable_name(callee_type) @@ -1044,11 +1046,11 @@ def could_not_infer_type_arguments(self, callee_type: CallableType, n: int, else: self.fail('Cannot infer function type argument', context) - @tracked + @tracked() def invalid_var_arg(self, typ: Type, context: Context) -> None: self.fail('List or tuple expected as variable arguments', context) - @tracked + @tracked() def invalid_keyword_var_arg(self, typ: Type, is_mapping: bool, context: Context) -> None: if isinstance(typ, Instance) and is_mapping: self.fail('Keywords must be strings', context) @@ -1060,11 +1062,11 @@ def invalid_keyword_var_arg(self, typ: Type, is_mapping: bool, context: Context) 'Argument after ** must be a mapping{}'.format(suffix), context) - @tracked + @tracked() def undefined_in_superclass(self, member: str, context: Context) -> None: self.fail('"{}" undefined in superclass'.format(member), context) - @tracked + @tracked() def first_argument_for_super_must_be_type(self, actual: Type, context: Context) -> None: if isinstance(actual, Instance): # Don't include type of instance, because it can look confusingly like a type @@ -1074,60 +1076,60 @@ def first_argument_for_super_must_be_type(self, actual: Type, context: Context) type_str = self.format(actual) self.fail('Argument 1 for "super" must be a type object; got {}'.format(type_str), context) - @tracked + @tracked('wrong_number_of_str_format_args') def too_few_string_formatting_arguments(self, context: Context) -> None: self.fail('Not enough arguments for format string', context) - @tracked + @tracked('wrong_number_of_str_format_args') def too_many_string_formatting_arguments(self, context: Context) -> None: self.fail('Not all arguments converted during string formatting', context) - @tracked + @tracked() def unsupported_placeholder(self, placeholder: str, context: Context) -> None: self.fail('Unsupported format character \'%s\'' % placeholder, context) - @tracked + @tracked() def string_interpolation_with_star_and_key(self, context: Context) -> None: self.fail('String interpolation contains both stars and mapping keys', context) - @tracked + @tracked() def requires_int_or_char(self, context: Context) -> None: self.fail('%c requires int or char', context) - @tracked + @tracked() def key_not_in_mapping(self, key: str, context: Context) -> None: self.fail('Key \'%s\' not found in mapping' % key, context) - @tracked + @tracked() def string_interpolation_mixing_key_and_non_keys(self, context: Context) -> None: self.fail('String interpolation mixes specifier with and without mapping keys', context) - @tracked + @tracked() def cannot_determine_type(self, name: str, context: Context) -> None: self.fail("Cannot determine type of '%s'" % name, context) - @tracked + @tracked() def cannot_determine_type_in_base(self, name: str, base: str, context: Context) -> None: self.fail("Cannot determine type of '%s' in base class '%s'" % (name, base), context) - @tracked + @tracked() def no_formal_self(self, name: str, item: CallableType, context: Context) -> None: self.fail('Attribute function "%s" with type %s does not accept self argument' % (name, self.format(item)), context) - @tracked + @tracked() def incompatible_self_argument(self, name: str, arg: Type, sig: CallableType, is_classmethod: bool, context: Context) -> None: kind = 'class attribute function' if is_classmethod else 'attribute function' self.fail('Invalid self argument %s to %s "%s" with type %s' % (self.format(arg), kind, name, self.format(sig)), context) - @tracked + @tracked() def incompatible_conditional_function_def(self, defn: FuncDef) -> None: self.fail('All conditional function variants must have identical ' 'signatures', defn) - @tracked + @tracked() def cannot_instantiate_abstract_class(self, class_name: str, abstract_attributes: List[str], context: Context) -> None: @@ -1137,7 +1139,7 @@ def cannot_instantiate_abstract_class(self, class_name: str, attrs), context) - @tracked + @tracked() def base_class_definitions_incompatible(self, name: str, base1: TypeInfo, base2: TypeInfo, context: Context) -> None: @@ -1145,24 +1147,24 @@ def base_class_definitions_incompatible(self, name: str, base1: TypeInfo, 'with definition in base class "{}"'.format( name, base1.name(), base2.name()), context) - @tracked - def cant_assign_to_method(self, context: Context) -> None: + @tracked() + def cannot_assign_to_method(self, context: Context) -> None: self.fail(CANNOT_ASSIGN_TO_METHOD, context) - @tracked - def cant_assign_to_classvar(self, name: str, context: Context) -> None: + @tracked() + def cannot_assign_to_classvar(self, name: str, context: Context) -> None: self.fail('Cannot assign to class variable "%s" via instance' % name, context) - @tracked - def final_cant_override_writable(self, name: str, ctx: Context) -> None: + @tracked() + def final_cannot_override_writable(self, name: str, ctx: Context) -> None: self.fail('Cannot override writable attribute "{}" with a final one'.format(name), ctx) - @tracked - def cant_override_final(self, name: str, base_name: str, ctx: Context) -> None: + @tracked() + def cannot_override_final(self, name: str, base_name: str, ctx: Context) -> None: self.fail('Cannot override final attribute "{}"' ' (previously declared in base class "{}")'.format(name, base_name), ctx) - def cant_assign_to_final(self, name: str, attr_assign: bool, ctx: Context) -> None: + def cannot_assign_to_final(self, name: str, attr_assign: bool, ctx: Context) -> None: """Warn about a prohibited assignment to a final attribute. Pass `attr_assign=True` if the assignment assigns to an attribute. @@ -1170,21 +1172,21 @@ def cant_assign_to_final(self, name: str, attr_assign: bool, ctx: Context) -> No kind = "attribute" if attr_assign else "name" self.fail('Cannot assign to final {} "{}"'.format(kind, name), ctx) - @tracked - def protocol_members_cant_be_final(self, ctx: Context) -> None: + @tracked() + def protocol_members_cannot_be_final(self, ctx: Context) -> None: self.fail("Protocol member cannot be final", ctx) - @tracked + @tracked() def final_without_value(self, ctx: Context) -> None: self.fail("Final name must be initialized with a value", ctx) - @tracked + @tracked() def read_only_property(self, name: str, type: TypeInfo, context: Context) -> None: self.fail('Property "{}" defined in "{}" is read-only'.format( name, type.name()), context) - @tracked + @tracked() def incompatible_typevar_value(self, callee: CallableType, typ: Type, @@ -1195,26 +1197,26 @@ def incompatible_typevar_value(self, self.format(typ)), context) - @tracked + @tracked() def overload_inconsistently_applies_decorator(self, decorator: str, context: Context) -> None: self.fail( 'Overload does not consistently use the "@{}" '.format(decorator) + 'decorator on all function signatures.', context) - @tracked + @tracked() def overloaded_signatures_overlap(self, index1: int, index2: int, context: Context) -> None: self.fail('Overloaded function signatures {} and {} overlap with ' 'incompatible return types'.format(index1, index2), context) - @tracked + @tracked() def overloaded_signatures_partial_overlap(self, index1: int, index2: int, context: Context) -> None: self.fail('Overloaded function signatures {} and {} '.format(index1, index2) + 'are partially overlapping: the two signatures may return ' + 'incompatible types given certain calls', context) - @tracked + @tracked() def overloaded_signature_will_never_match(self, index1: int, index2: int, context: Context) -> None: self.fail( @@ -1224,30 +1226,28 @@ def overloaded_signature_will_never_match(self, index1: int, index2: int, index2=index2), context) - @tracked + @tracked() def overloaded_signatures_typevar_specific(self, index: int, context: Context) -> None: self.fail('Overloaded function implementation cannot satisfy signature {} '.format(index) + 'due to inconsistencies in how they use type variables', context) - @tracked + @tracked() def overloaded_signatures_arg_specific(self, index: int, context: Context) -> None: self.fail('Overloaded function implementation does not accept all possible arguments ' 'of signature {}'.format(index), context) - @tracked + @tracked() def overloaded_signatures_ret_specific(self, index: int, context: Context) -> None: self.fail('Overloaded function implementation cannot produce return type ' 'of signature {}'.format(index), context) - @tracked def warn_both_operands_are_from_unions(self, context: Context) -> None: self.note('Both left and right operands are unions', context) - @tracked def warn_operand_was_from_union(self, side: str, original: Type, context: Context) -> None: self.note('{} operand is of type {}'.format(side, self.format(original)), context) - @tracked + @tracked() def operator_method_signatures_overlap( self, reverse_class: TypeInfo, reverse_method: str, forward_class: Type, forward_method: str, context: Context) -> None: @@ -1257,38 +1257,38 @@ def operator_method_signatures_overlap( forward_method, self.format(forward_class)), context) - @tracked + @tracked() def forward_operator_not_callable( self, forward_method: str, context: Context) -> None: self.fail('Forward operator "{}" is not callable'.format( forward_method), context) - @tracked + @tracked() def signatures_incompatible(self, method: str, other_method: str, context: Context) -> None: self.fail('Signatures of "{}" and "{}" are incompatible'.format( method, other_method), context) - @tracked + @tracked() def yield_from_invalid_operand_type(self, expr: Type, context: Context) -> Type: text = self.format(expr) if self.format(expr) != 'object' else expr self.fail('"yield from" can\'t be applied to {}'.format(text), context) return AnyType(TypeOfAny.from_error) - @tracked + @tracked() def invalid_signature(self, func_type: Type, context: Context) -> None: self.fail('Invalid signature "{}"'.format(func_type), context) - @tracked + @tracked() def invalid_signature_for_special_method( self, func_type: Type, context: Context, method_name: str) -> None: self.fail('Invalid signature "{}" for "{}"'.format(func_type, method_name), context) - @tracked + @tracked() def reveal_type(self, typ: Type, context: Context) -> None: self.fail('Revealed type is \'{}\''.format(typ), context) - @tracked + @tracked() def reveal_locals(self, type_map: Dict[str, Optional[Type]], context: Context) -> None: # To ensure that the output is predictable on Python < 3.6, # use an ordered dictionary sorted by variable name @@ -1297,28 +1297,29 @@ def reveal_locals(self, type_map: Dict[str, Optional[Type]], context: Context) - for line in ['{}: {}'.format(k, v) for k, v in sorted_locals.items()]: self.fail(line, context) - @tracked + @tracked() def unsupported_type_type(self, item: Type, context: Context) -> None: self.fail('Unsupported type Type[{}]'.format(self.format(item)), context) - @tracked + @tracked() def redundant_cast(self, typ: Type, context: Context) -> None: self.note('Redundant cast to {}'.format(self.format(typ)), context) - @tracked + # filtered by disallow_any_unimported + @tracked() def unimported_type_becomes_any(self, prefix: str, typ: Type, ctx: Context) -> None: self.fail("{} becomes {} due to an unfollowed import".format(prefix, self.format(typ)), ctx) - @tracked + @tracked() def need_annotation_for_var(self, node: SymbolNode, context: Context) -> None: self.fail("Need type annotation for '{}'".format(node.name()), context) - @tracked + @tracked() def explicit_any(self, ctx: Context) -> None: self.fail('Explicit "Any" is not allowed', ctx) - @tracked + @tracked() def unexpected_typeddict_keys( self, typ: TypedDictType, @@ -1354,7 +1355,7 @@ def unexpected_typeddict_keys( found = 'only {}'.format(found) self.fail('Expected {} but found {}'.format(expected, found), context) - @tracked + @tracked() def typeddict_key_must_be_string_literal( self, typ: TypedDictType, @@ -1363,7 +1364,7 @@ def typeddict_key_must_be_string_literal( 'TypedDict key must be a string literal; expected one of {}'.format( format_item_name_list(typ.items.keys())), context) - @tracked + @tracked() def typeddict_key_not_found( self, typ: TypedDictType, @@ -1375,7 +1376,7 @@ def typeddict_key_not_found( else: self.fail("TypedDict {} has no key '{}'".format(self.format(typ), item_name), context) - @tracked + @tracked() def typeddict_key_cannot_be_deleted( self, typ: TypedDictType, @@ -1388,7 +1389,7 @@ def typeddict_key_cannot_be_deleted( self.fail("Key '{}' of TypedDict {} cannot be deleted".format( item_name, self.format(typ)), context) - @tracked + @tracked() def type_arguments_not_allowed(self, context: Context) -> None: self.fail('Parameterized generics cannot be used with class or instance checks', context) @@ -1399,13 +1400,14 @@ def disallowed_any_type(self, typ: Type, context: Context) -> None: message = 'Expression type contains "Any" (has type {})'.format(self.format(typ)) self.fail(message, context) - @tracked + @tracked() def incorrectly_returning_any(self, typ: Type, context: Context) -> None: message = 'Returning Any from function declared to return {}'.format( self.format(typ)) self.warn(message, context) - @tracked + # filtered by disallow_any_decorated + @tracked() def untyped_decorated_function(self, typ: Type, context: Context) -> None: if isinstance(typ, AnyType): self.fail("Function is untyped after decorator transformation", context) @@ -1413,11 +1415,12 @@ def untyped_decorated_function(self, typ: Type, context: Context) -> None: self.fail('Type of decorated function contains type "Any" ({})'.format( self.format(typ)), context) - @tracked + # filtered by disallow_untyped_decorators + @tracked() def typed_function_untyped_decorator(self, func_name: str, context: Context) -> None: self.fail('Untyped decorator makes function "{}" untyped'.format(func_name), context) - @tracked + @tracked() def bad_proto_variance(self, actual: int, tvar_name: str, expected: int, context: Context) -> None: msg = capitalize("{} type variable '{}' used in protocol where" @@ -1426,24 +1429,24 @@ def bad_proto_variance(self, actual: int, tvar_name: str, expected: int, variance_string(expected))) self.fail(msg, context) - @tracked + @tracked() def concrete_only_assign(self, typ: Type, context: Context) -> None: self.fail("Can only assign concrete classes to a variable of type {}" .format(self.format(typ)), context) - @tracked + @tracked() def concrete_only_call(self, typ: Type, context: Context) -> None: self.fail("Only concrete class can be given where {} is expected" .format(self.format(typ)), context) - @tracked + @tracked() def cannot_use_function_with_type( self, method_name: str, type_name: str, context: Context) -> None: self.fail("Cannot use {}() with a {} type".format(method_name, type_name), context) - @tracked - def report_non_method_protocol(self, tp: TypeInfo, members: List[str], - context: Context) -> None: + @tracked() + def issubclass_on_non_method_protocol(self, tp: TypeInfo, members: List[str], + context: Context) -> None: self.fail("Only protocols that don't have non-method members can be" " used with issubclass()", context) if len(members) < 3: diff --git a/mypy/semanal.py b/mypy/semanal.py index 26df4b357de0..58794decef4e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1827,7 +1827,7 @@ def unwrap_final(self, s: AssignmentStmt) -> None: if self.loop_depth > 0: self.fail("Cannot use Final inside a loop", s) if self.type and self.type.is_protocol: - self.msg.protocol_members_cant_be_final(s) + self.msg.protocol_members_cannot_be_final(s) if (isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs and not self.is_stub_file and not self.is_class_scope()): if not invalid_bare_final: # Skip extra error messages. @@ -2622,7 +2622,7 @@ def visit_decorator(self, dec: Decorator) -> None: if self.is_class_scope(): assert self.type is not None, "No type set at class scope" if self.type.is_protocol: - self.msg.protocol_members_cant_be_final(d) + self.msg.protocol_members_cannot_be_final(d) else: dec.func.is_final = True dec.var.is_final = True From a978e838a0934b171789d1e05f3e7e2c3ff63b88 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Sun, 6 Jan 2019 10:53:03 -0800 Subject: [PATCH 06/11] bugfix --- mypy/messages.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy/messages.py b/mypy/messages.py index 7113f17d85c2..a2c772ebb19f 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -235,6 +235,9 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile]) -> None: def get_message_ids(cls) -> Set[str]: return _message_ids + def active_message_id(self) -> Optional[str]: + return self.active_msg_ids[-1] if len(self.active_msg_ids) else None + # # Helpers # From 8654084f953b9b539b6ce3aca40ed46c2e6355ba Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Sun, 6 Jan 2019 11:52:05 -0800 Subject: [PATCH 07/11] add docs for @tracked --- mypy/messages.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mypy/messages.py b/mypy/messages.py index a2c772ebb19f..723ce5b6c964 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -182,6 +182,11 @@ def tracked(id: Optional[str] = None) -> Callable[[T], T]: + """Identify a MessageBuilder method that represents a message to be tracked. + + Arguments: + id: provide an explicit message id. by default, if this is not provided the message id is the name of the method + """ def deco(func: T) -> T: msg_id = id if id is not None else func.__name__ _message_ids.add(msg_id) From 3cec328e47b259451353e760b9417ba214195df5 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Mon, 7 Jan 2019 22:18:08 -0800 Subject: [PATCH 08/11] rename tracked to register --- mypy/messages.py | 186 ++++++++++++++++++++++++----------------------- 1 file changed, 94 insertions(+), 92 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 723ce5b6c964..d23536d35de3 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -181,11 +181,12 @@ _message_ids = set() # type: Set[str] -def tracked(id: Optional[str] = None) -> Callable[[T], T]: - """Identify a MessageBuilder method that represents a message to be tracked. +def register(id: Optional[str] = None) -> Callable[[T], T]: + """Identify a MessageBuilder method that represents a message to be register. Arguments: - id: provide an explicit message id. by default, if this is not provided the message id is the name of the method + id: provide an explicit message id. by default, if this is not provided the message id is + the name of the method """ def deco(func: T) -> T: msg_id = id if id is not None else func.__name__ @@ -517,16 +518,17 @@ def format_distinctly(self, type1: Type, type2: Type, bare: bool = False) -> Tup # get some information as arguments, and they build an error message based # on them. - @tracked() + @register() def no_return_exepected(self, context: Context) -> None: self.fail('No return value expected', context) - @tracked() + @register() def must_have_none_return_type(self, context: FuncDef) -> None: self.fail('The return type of "{}" must be None'.format(context.name()), context) - @tracked() - def type_has_no_attr(self, original_type: Type, typ: Type, member: str, context: Context) -> Type: + @register() + def type_has_no_attr(self, original_type: Type, typ: Type, member: str, + context: Context) -> Type: """Report a missing or non-accessible member. original_type is the top-level type on which the error occurred. @@ -617,7 +619,7 @@ def type_has_no_attr(self, original_type: Type, typ: Type, member: str, context: typ_format, self.format(original_type), member, extra), context) return AnyType(TypeOfAny.from_error) - @tracked() + @register() def unsupported_operand_types(self, op: str, left_type: Any, right_type: Any, context: Context) -> None: """Report unsupported operand types for a binary operation. @@ -643,7 +645,7 @@ def unsupported_operand_types(self, op: str, left_type: Any, op, left_str, right_str) self.fail(msg, context) - @tracked() + @register() def unsupported_left_operand(self, op: str, typ: Type, context: Context) -> None: if self.disable_type_names: @@ -653,13 +655,13 @@ def unsupported_left_operand(self, op: str, typ: Type, op, self.format(typ)) self.fail(msg, context) - @tracked() + @register() def untyped_function_call(self, callee: CallableType, context: Context) -> Type: name = callable_name(callee) or '(unknown)' self.fail('Call to untyped function {} in typed context'.format(name), context) return AnyType(TypeOfAny.from_error) - @tracked() + @register() def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: Type, arg_kind: int, context: Context) -> None: """Report an error about an incompatible argument type. @@ -809,13 +811,13 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: for note_msg in notes: self.note(note_msg, context) - @tracked() + @register() def invalid_index_type(self, index_type: Type, expected_type: Type, base_str: str, context: Context) -> None: self.fail('Invalid index type {} for {}; expected type {}'.format( self.format(index_type), base_str, self.format(expected_type)), context) - @tracked('wrong_number_of_arguments') + @register('wrong_number_of_arguments') def too_few_arguments(self, callee: CallableType, context: Context, argument_names: Optional[Sequence[Optional[str]]]) -> None: if (argument_names is not None and not all(k is None for k in argument_names) @@ -833,17 +835,17 @@ def too_few_arguments(self, callee: CallableType, context: Context, msg = 'Too few arguments' + for_function(callee) self.fail(msg, context) - @tracked() + @register() def missing_named_argument(self, callee: CallableType, context: Context, name: str) -> None: msg = 'Missing named argument "{}"'.format(name) + for_function(callee) self.fail(msg, context) - @tracked('wrong_number_of_arguments') + @register('wrong_number_of_arguments') def too_many_arguments(self, callee: CallableType, context: Context) -> None: msg = 'Too many arguments' + for_function(callee) self.fail(msg, context) - @tracked() + @register() def too_many_arguments_from_typed_dict(self, callee: CallableType, arg_type: TypedDictType, @@ -858,13 +860,13 @@ def too_many_arguments_from_typed_dict(self, return self.fail(msg, context) - @tracked() + @register() def too_many_positional_arguments(self, callee: CallableType, context: Context) -> None: msg = 'Too many positional arguments' + for_function(callee) self.fail(msg, context) - @tracked() + @register() def unexpected_keyword_argument(self, callee: CallableType, name: str, context: Context) -> None: msg = 'Unexpected keyword argument "{}"'.format(name) + for_function(callee) @@ -878,14 +880,14 @@ def unexpected_keyword_argument(self, callee: CallableType, name: str, self.note('{} defined here'.format(fname), callee.definition, file=module.path, origin=context) - @tracked() + @register() def duplicate_argument_value(self, callee: CallableType, index: int, context: Context) -> None: self.fail('{} gets multiple values for keyword argument "{}"'. format(callable_name(callee) or 'Function', callee.arg_names[index]), context) - @tracked() + @register() def does_not_return_value(self, callee_type: Optional[Type], context: Context) -> None: """Report an error about use of an unusable type.""" name = None # type: Optional[str] @@ -896,7 +898,7 @@ def does_not_return_value(self, callee_type: Optional[Type], context: Context) - else: self.fail('Function does not return a value', context) - @tracked() + @register() def deleted_as_rvalue(self, typ: DeletedType, context: Context) -> None: """Report an error about using an deleted type as an rvalue.""" if typ.source is None: @@ -905,7 +907,7 @@ def deleted_as_rvalue(self, typ: DeletedType, context: Context) -> None: s = " '{}'".format(typ.source) self.fail('Trying to read deleted variable{}'.format(s), context) - @tracked() + @register() def deleted_as_lvalue(self, typ: DeletedType, context: Context) -> None: """Report an error about using an deleted type as an lvalue. @@ -918,7 +920,7 @@ def deleted_as_lvalue(self, typ: DeletedType, context: Context) -> None: s = " '{}'".format(typ.source) self.fail('Assignment to variable{} outside except: block'.format(s), context) - @tracked() + @register() def no_variant_matches_arguments(self, plausible_targets: List[CallableType], overload: Overloaded, @@ -943,7 +945,7 @@ def no_variant_matches_arguments(self, self.pretty_overload_matches(plausible_targets, overload, context, offset=2, max_items=2) - @tracked() + @register() def wrong_number_values_to_unpack(self, provided: int, expected: int, context: Context) -> None: if provided < expected: @@ -957,17 +959,17 @@ def wrong_number_values_to_unpack(self, provided: int, expected: int, self.fail('Too many values to unpack ({} expected, {} provided)'.format( expected, provided), context) - @tracked() + @register() def type_not_iterable(self, type: Type, context: Context) -> None: self.fail('\'{}\' object is not iterable'.format(type), context) - @tracked() + @register() def incompatible_operator_assignment(self, op: str, context: Context) -> None: self.fail('Result type of {} incompatible in assignment'.format(op), context) - @tracked() + @register() def overload_signature_incompatible_with_supertype( self, name: str, name_in_super: str, supertype: str, overload: Overloaded, context: Context) -> None: @@ -978,7 +980,7 @@ def overload_signature_incompatible_with_supertype( note_template = 'Overload variants must be defined in the same order as they are in "{}"' self.note(note_template.format(supertype), context) - @tracked() + @register() def signature_incompatible_with_supertype( self, name: str, name_in_super: str, supertype: str, context: Context) -> None: @@ -986,7 +988,7 @@ def signature_incompatible_with_supertype( self.fail('Signature of "{}" incompatible with {}'.format( name, target), context) - @tracked() + @register() def argument_incompatible_with_supertype( self, arg_num: int, name: str, type_name: Optional[str], name_in_supertype: str, supertype: str, context: Context) -> None: @@ -1007,7 +1009,7 @@ def __eq__(self, other: object) -> bool: return '''.format(class_name=class_name)) - @tracked() + @register() def return_type_incompatible_with_supertype( self, name: str, name_in_supertype: str, supertype: str, context: Context) -> None: @@ -1022,7 +1024,7 @@ def override_target(self, name: str, name_in_super: str, target = '"{}" of {}'.format(name_in_super, target) return target - @tracked() + @register() def incompatible_type_application(self, expected_arg_count: int, actual_arg_count: int, context: Context) -> None: @@ -1036,7 +1038,7 @@ def incompatible_type_application(self, expected_arg_count: int, self.fail('Type application has too few types ({} expected)' .format(expected_arg_count), context) - @tracked() + @register() def alias_invalid_in_runtime_context(self, item: Type, ctx: Context) -> None: kind = (' to Callable' if isinstance(item, CallableType) else ' to Tuple' if isinstance(item, TupleType) else @@ -1045,7 +1047,7 @@ def alias_invalid_in_runtime_context(self, item: Type, ctx: Context) -> None: '') self.fail('The type alias{} is invalid in runtime context'.format(kind), ctx) - @tracked() + @register() def could_not_infer_type_arguments(self, callee_type: CallableType, n: int, context: Context) -> None: callee_name = callable_name(callee_type) @@ -1054,11 +1056,11 @@ def could_not_infer_type_arguments(self, callee_type: CallableType, n: int, else: self.fail('Cannot infer function type argument', context) - @tracked() + @register() def invalid_var_arg(self, typ: Type, context: Context) -> None: self.fail('List or tuple expected as variable arguments', context) - @tracked() + @register() def invalid_keyword_var_arg(self, typ: Type, is_mapping: bool, context: Context) -> None: if isinstance(typ, Instance) and is_mapping: self.fail('Keywords must be strings', context) @@ -1070,11 +1072,11 @@ def invalid_keyword_var_arg(self, typ: Type, is_mapping: bool, context: Context) 'Argument after ** must be a mapping{}'.format(suffix), context) - @tracked() + @register() def undefined_in_superclass(self, member: str, context: Context) -> None: self.fail('"{}" undefined in superclass'.format(member), context) - @tracked() + @register() def first_argument_for_super_must_be_type(self, actual: Type, context: Context) -> None: if isinstance(actual, Instance): # Don't include type of instance, because it can look confusingly like a type @@ -1084,60 +1086,60 @@ def first_argument_for_super_must_be_type(self, actual: Type, context: Context) type_str = self.format(actual) self.fail('Argument 1 for "super" must be a type object; got {}'.format(type_str), context) - @tracked('wrong_number_of_str_format_args') + @register('wrong_number_of_str_format_args') def too_few_string_formatting_arguments(self, context: Context) -> None: self.fail('Not enough arguments for format string', context) - @tracked('wrong_number_of_str_format_args') + @register('wrong_number_of_str_format_args') def too_many_string_formatting_arguments(self, context: Context) -> None: self.fail('Not all arguments converted during string formatting', context) - @tracked() + @register() def unsupported_placeholder(self, placeholder: str, context: Context) -> None: self.fail('Unsupported format character \'%s\'' % placeholder, context) - @tracked() + @register() def string_interpolation_with_star_and_key(self, context: Context) -> None: self.fail('String interpolation contains both stars and mapping keys', context) - @tracked() + @register() def requires_int_or_char(self, context: Context) -> None: self.fail('%c requires int or char', context) - @tracked() + @register() def key_not_in_mapping(self, key: str, context: Context) -> None: self.fail('Key \'%s\' not found in mapping' % key, context) - @tracked() + @register() def string_interpolation_mixing_key_and_non_keys(self, context: Context) -> None: self.fail('String interpolation mixes specifier with and without mapping keys', context) - @tracked() + @register() def cannot_determine_type(self, name: str, context: Context) -> None: self.fail("Cannot determine type of '%s'" % name, context) - @tracked() + @register() def cannot_determine_type_in_base(self, name: str, base: str, context: Context) -> None: self.fail("Cannot determine type of '%s' in base class '%s'" % (name, base), context) - @tracked() + @register() def no_formal_self(self, name: str, item: CallableType, context: Context) -> None: self.fail('Attribute function "%s" with type %s does not accept self argument' % (name, self.format(item)), context) - @tracked() + @register() def incompatible_self_argument(self, name: str, arg: Type, sig: CallableType, is_classmethod: bool, context: Context) -> None: kind = 'class attribute function' if is_classmethod else 'attribute function' self.fail('Invalid self argument %s to %s "%s" with type %s' % (self.format(arg), kind, name, self.format(sig)), context) - @tracked() + @register() def incompatible_conditional_function_def(self, defn: FuncDef) -> None: self.fail('All conditional function variants must have identical ' 'signatures', defn) - @tracked() + @register() def cannot_instantiate_abstract_class(self, class_name: str, abstract_attributes: List[str], context: Context) -> None: @@ -1147,7 +1149,7 @@ def cannot_instantiate_abstract_class(self, class_name: str, attrs), context) - @tracked() + @register() def base_class_definitions_incompatible(self, name: str, base1: TypeInfo, base2: TypeInfo, context: Context) -> None: @@ -1155,19 +1157,19 @@ def base_class_definitions_incompatible(self, name: str, base1: TypeInfo, 'with definition in base class "{}"'.format( name, base1.name(), base2.name()), context) - @tracked() + @register() def cannot_assign_to_method(self, context: Context) -> None: self.fail(CANNOT_ASSIGN_TO_METHOD, context) - @tracked() + @register() def cannot_assign_to_classvar(self, name: str, context: Context) -> None: self.fail('Cannot assign to class variable "%s" via instance' % name, context) - @tracked() + @register() def final_cannot_override_writable(self, name: str, ctx: Context) -> None: self.fail('Cannot override writable attribute "{}" with a final one'.format(name), ctx) - @tracked() + @register() def cannot_override_final(self, name: str, base_name: str, ctx: Context) -> None: self.fail('Cannot override final attribute "{}"' ' (previously declared in base class "{}")'.format(name, base_name), ctx) @@ -1180,21 +1182,21 @@ def cannot_assign_to_final(self, name: str, attr_assign: bool, ctx: Context) -> kind = "attribute" if attr_assign else "name" self.fail('Cannot assign to final {} "{}"'.format(kind, name), ctx) - @tracked() + @register() def protocol_members_cannot_be_final(self, ctx: Context) -> None: self.fail("Protocol member cannot be final", ctx) - @tracked() + @register() def final_without_value(self, ctx: Context) -> None: self.fail("Final name must be initialized with a value", ctx) - @tracked() + @register() def read_only_property(self, name: str, type: TypeInfo, context: Context) -> None: self.fail('Property "{}" defined in "{}" is read-only'.format( name, type.name()), context) - @tracked() + @register() def incompatible_typevar_value(self, callee: CallableType, typ: Type, @@ -1205,26 +1207,26 @@ def incompatible_typevar_value(self, self.format(typ)), context) - @tracked() + @register() def overload_inconsistently_applies_decorator(self, decorator: str, context: Context) -> None: self.fail( 'Overload does not consistently use the "@{}" '.format(decorator) + 'decorator on all function signatures.', context) - @tracked() + @register() def overloaded_signatures_overlap(self, index1: int, index2: int, context: Context) -> None: self.fail('Overloaded function signatures {} and {} overlap with ' 'incompatible return types'.format(index1, index2), context) - @tracked() + @register() def overloaded_signatures_partial_overlap(self, index1: int, index2: int, context: Context) -> None: self.fail('Overloaded function signatures {} and {} '.format(index1, index2) + 'are partially overlapping: the two signatures may return ' + 'incompatible types given certain calls', context) - @tracked() + @register() def overloaded_signature_will_never_match(self, index1: int, index2: int, context: Context) -> None: self.fail( @@ -1234,17 +1236,17 @@ def overloaded_signature_will_never_match(self, index1: int, index2: int, index2=index2), context) - @tracked() + @register() def overloaded_signatures_typevar_specific(self, index: int, context: Context) -> None: self.fail('Overloaded function implementation cannot satisfy signature {} '.format(index) + 'due to inconsistencies in how they use type variables', context) - @tracked() + @register() def overloaded_signatures_arg_specific(self, index: int, context: Context) -> None: self.fail('Overloaded function implementation does not accept all possible arguments ' 'of signature {}'.format(index), context) - @tracked() + @register() def overloaded_signatures_ret_specific(self, index: int, context: Context) -> None: self.fail('Overloaded function implementation cannot produce return type ' 'of signature {}'.format(index), context) @@ -1255,7 +1257,7 @@ def warn_both_operands_are_from_unions(self, context: Context) -> None: def warn_operand_was_from_union(self, side: str, original: Type, context: Context) -> None: self.note('{} operand is of type {}'.format(side, self.format(original)), context) - @tracked() + @register() def operator_method_signatures_overlap( self, reverse_class: TypeInfo, reverse_method: str, forward_class: Type, forward_method: str, context: Context) -> None: @@ -1265,38 +1267,38 @@ def operator_method_signatures_overlap( forward_method, self.format(forward_class)), context) - @tracked() + @register() def forward_operator_not_callable( self, forward_method: str, context: Context) -> None: self.fail('Forward operator "{}" is not callable'.format( forward_method), context) - @tracked() + @register() def signatures_incompatible(self, method: str, other_method: str, context: Context) -> None: self.fail('Signatures of "{}" and "{}" are incompatible'.format( method, other_method), context) - @tracked() + @register() def yield_from_invalid_operand_type(self, expr: Type, context: Context) -> Type: text = self.format(expr) if self.format(expr) != 'object' else expr self.fail('"yield from" can\'t be applied to {}'.format(text), context) return AnyType(TypeOfAny.from_error) - @tracked() + @register() def invalid_signature(self, func_type: Type, context: Context) -> None: self.fail('Invalid signature "{}"'.format(func_type), context) - @tracked() + @register() def invalid_signature_for_special_method( self, func_type: Type, context: Context, method_name: str) -> None: self.fail('Invalid signature "{}" for "{}"'.format(func_type, method_name), context) - @tracked() + @register() def reveal_type(self, typ: Type, context: Context) -> None: self.fail('Revealed type is \'{}\''.format(typ), context) - @tracked() + @register() def reveal_locals(self, type_map: Dict[str, Optional[Type]], context: Context) -> None: # To ensure that the output is predictable on Python < 3.6, # use an ordered dictionary sorted by variable name @@ -1305,29 +1307,29 @@ def reveal_locals(self, type_map: Dict[str, Optional[Type]], context: Context) - for line in ['{}: {}'.format(k, v) for k, v in sorted_locals.items()]: self.fail(line, context) - @tracked() + @register() def unsupported_type_type(self, item: Type, context: Context) -> None: self.fail('Unsupported type Type[{}]'.format(self.format(item)), context) - @tracked() + @register() def redundant_cast(self, typ: Type, context: Context) -> None: self.note('Redundant cast to {}'.format(self.format(typ)), context) # filtered by disallow_any_unimported - @tracked() + @register() def unimported_type_becomes_any(self, prefix: str, typ: Type, ctx: Context) -> None: self.fail("{} becomes {} due to an unfollowed import".format(prefix, self.format(typ)), ctx) - @tracked() + @register() def need_annotation_for_var(self, node: SymbolNode, context: Context) -> None: self.fail("Need type annotation for '{}'".format(node.name()), context) - @tracked() + @register() def explicit_any(self, ctx: Context) -> None: self.fail('Explicit "Any" is not allowed', ctx) - @tracked() + @register() def unexpected_typeddict_keys( self, typ: TypedDictType, @@ -1363,7 +1365,7 @@ def unexpected_typeddict_keys( found = 'only {}'.format(found) self.fail('Expected {} but found {}'.format(expected, found), context) - @tracked() + @register() def typeddict_key_must_be_string_literal( self, typ: TypedDictType, @@ -1372,7 +1374,7 @@ def typeddict_key_must_be_string_literal( 'TypedDict key must be a string literal; expected one of {}'.format( format_item_name_list(typ.items.keys())), context) - @tracked() + @register() def typeddict_key_not_found( self, typ: TypedDictType, @@ -1384,7 +1386,7 @@ def typeddict_key_not_found( else: self.fail("TypedDict {} has no key '{}'".format(self.format(typ), item_name), context) - @tracked() + @register() def typeddict_key_cannot_be_deleted( self, typ: TypedDictType, @@ -1397,7 +1399,7 @@ def typeddict_key_cannot_be_deleted( self.fail("Key '{}' of TypedDict {} cannot be deleted".format( item_name, self.format(typ)), context) - @tracked() + @register() def type_arguments_not_allowed(self, context: Context) -> None: self.fail('Parameterized generics cannot be used with class or instance checks', context) @@ -1408,14 +1410,14 @@ def disallowed_any_type(self, typ: Type, context: Context) -> None: message = 'Expression type contains "Any" (has type {})'.format(self.format(typ)) self.fail(message, context) - @tracked() + @register() def incorrectly_returning_any(self, typ: Type, context: Context) -> None: message = 'Returning Any from function declared to return {}'.format( self.format(typ)) self.warn(message, context) # filtered by disallow_any_decorated - @tracked() + @register() def untyped_decorated_function(self, typ: Type, context: Context) -> None: if isinstance(typ, AnyType): self.fail("Function is untyped after decorator transformation", context) @@ -1424,11 +1426,11 @@ def untyped_decorated_function(self, typ: Type, context: Context) -> None: self.format(typ)), context) # filtered by disallow_untyped_decorators - @tracked() + @register() def typed_function_untyped_decorator(self, func_name: str, context: Context) -> None: self.fail('Untyped decorator makes function "{}" untyped'.format(func_name), context) - @tracked() + @register() def bad_proto_variance(self, actual: int, tvar_name: str, expected: int, context: Context) -> None: msg = capitalize("{} type variable '{}' used in protocol where" @@ -1437,22 +1439,22 @@ def bad_proto_variance(self, actual: int, tvar_name: str, expected: int, variance_string(expected))) self.fail(msg, context) - @tracked() + @register() def concrete_only_assign(self, typ: Type, context: Context) -> None: self.fail("Can only assign concrete classes to a variable of type {}" .format(self.format(typ)), context) - @tracked() + @register() def concrete_only_call(self, typ: Type, context: Context) -> None: self.fail("Only concrete class can be given where {} is expected" .format(self.format(typ)), context) - @tracked() + @register() def cannot_use_function_with_type( self, method_name: str, type_name: str, context: Context) -> None: self.fail("Cannot use {}() with a {} type".format(method_name, type_name), context) - @tracked() + @register() def issubclass_on_non_method_protocol(self, tp: TypeInfo, members: List[str], context: Context) -> None: self.fail("Only protocols that don't have non-method members can be" From cfb191301e02d9db1e31077677d8180947015013 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Tue, 8 Jan 2019 00:00:43 -0800 Subject: [PATCH 09/11] don't raise SystemExit in process_options --- mypy/main.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index 7b8837162695..99758773667a 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -70,6 +70,10 @@ def main(script_path: Optional[str], args: Optional[List[str]] = None) -> None: fscache = FileSystemCache() sources, options = process_options(args, fscache=fscache) + if sources is None: + # indicates early out + sys.exit(0) + messages = [] def flush_errors(new_messages: List[str], serious: bool) -> None: @@ -299,7 +303,7 @@ def process_options(args: List[str], fscache: Optional[FileSystemCache] = None, program: str = 'mypy', header: str = HEADER, - ) -> Tuple[List[BuildSource], Options]: + ) -> Tuple[Optional[List[BuildSource]], Options]: """Parse command line arguments. If a FileSystemCache is passed in, and package_root options are given, @@ -704,19 +708,19 @@ def add_invertible_flag(flag: str, # filename for the config file and know if the user requested all strict options. dummy = argparse.Namespace() parser.parse_args(args, dummy) + options = Options() if dummy.list_error_codes: import mypy.messages for msg_id in sorted(mypy.messages.MessageBuilder.get_message_ids()): print(msg_id) - raise SystemExit(0) + return None, options config_file = dummy.config_file if config_file is not None and not os.path.exists(config_file): parser.error("Cannot find config file '%s'" % config_file) # Parse config file first, so command line can override. - options = Options() parse_config_file(options, config_file) # Set strict flags before parsing (if strict mode enabled), so other command From 38188291f36fd86ed19c9f39cc21764409fe51ee Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Tue, 8 Jan 2019 22:06:17 -0800 Subject: [PATCH 10/11] Prototype of messages and ids as an Enum --- mypy/checker.py | 101 +++++++-------- mypy/checkexpr.py | 58 ++++----- mypy/checkmember.py | 14 +-- mypy/checkstrformat.py | 8 +- mypy/fastparse.py | 8 +- mypy/fastparse2.py | 4 +- mypy/messages.py | 267 +++++++++++++++++++++------------------- mypy/plugins/default.py | 6 +- mypy/semanal.py | 4 +- mypy/semanal_enum.py | 3 +- mypy/semanal_newtype.py | 4 +- mypy/semanal_pass3.py | 6 +- mypy/semanal_shared.py | 3 +- mypy/typeanal.py | 9 +- 14 files changed, 257 insertions(+), 238 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 6998941da775..046cdee292de 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5,7 +5,7 @@ from contextlib import contextmanager from typing import ( - Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator, Iterable, + Any, Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator, Iterable, Sequence ) @@ -294,7 +294,7 @@ def check_first_pass(self) -> None: [self.named_type('builtins.unicode')]) if not is_subtype(all_.type, seq_str): str_seq_s, all_s = self.msg.format_distinctly(seq_str, all_.type) - self.fail(messages.ALL_MUST_BE_SEQ_STR.format(str_seq_s, all_s), + self.fail(messages.ErrorCodes.ALL_MUST_BE_SEQ_STR.format(str_seq_s, all_s), all_node) self.tscope.leave() @@ -432,7 +432,7 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: # valid overloads. return None if len(defn.items) == 1: - self.fail(messages.MULTIPLE_OVERLOADS_REQUIRED, defn) + self.fail(messages.ErrorCodes.MULTIPLE_OVERLOADS_REQUIRED, defn) if defn.is_property: # HACK: Infer the type of the property. @@ -443,7 +443,7 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: if fdef.func.is_abstract: num_abstract += 1 if num_abstract not in (0, len(defn.items)): - self.fail(messages.INCONSISTENT_ABSTRACT_OVERLOAD, defn) + self.fail(messages.ErrorCodes.INCONSISTENT_ABSTRACT_OVERLOAD, defn) if defn.impl: defn.impl.accept(self) if defn.info: @@ -744,11 +744,11 @@ def _visit_func_def(self, defn: FuncDef) -> None: del partial_types[var] else: # Trying to redefine something like partial empty list as function. - self.fail(messages.INCOMPATIBLE_REDEFINITION, defn) + self.fail(messages.ErrorCodes.INCOMPATIBLE_REDEFINITION, defn) else: # TODO: Update conditional type binder. self.check_subtype(new_type, orig_type, defn, - messages.INCOMPATIBLE_REDEFINITION, + messages.ErrorCodes.INCOMPATIBLE_REDEFINITION, 'redefinition with type', 'original type') @@ -825,24 +825,24 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) # Refuse contravariant return type variable if isinstance(typ.ret_type, TypeVarType): if typ.ret_type.variance == CONTRAVARIANT: - self.fail(messages.RETURN_TYPE_CANNOT_BE_CONTRAVARIANT, + self.fail(messages.ErrorCodes.RETURN_TYPE_CANNOT_BE_CONTRAVARIANT, typ.ret_type) # Check that Generator functions have the appropriate return type. if defn.is_generator: if defn.is_async_generator: if not self.is_async_generator_return_type(typ.ret_type): - self.fail(messages.INVALID_RETURN_TYPE_FOR_ASYNC_GENERATOR, typ) + self.fail(messages.ErrorCodes.INVALID_RETURN_TYPE_FOR_ASYNC_GENERATOR, typ) else: if not self.is_generator_return_type(typ.ret_type, defn.is_coroutine): - self.fail(messages.INVALID_RETURN_TYPE_FOR_GENERATOR, typ) + self.fail(messages.ErrorCodes.INVALID_RETURN_TYPE_FOR_GENERATOR, typ) # Python 2 generators aren't allowed to return values. if (self.options.python_version[0] == 2 and isinstance(typ.ret_type, Instance) and typ.ret_type.type.fullname() == 'typing.Generator'): if not isinstance(typ.ret_type.args[2], (NoneTyp, AnyType)): - self.fail(messages.INVALID_GENERATOR_RETURN_ITEM_TYPE, typ) + self.fail(messages.ErrorCodes.INVALID_GENERATOR_RETURN_ITEM_TYPE, typ) # Fix the type if decorated with `@types.coroutine` or `@asyncio.coroutine`. if defn.is_awaitable_coroutine: @@ -881,13 +881,13 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) if typ.arg_names[i] in ['self', 'cls']: if (self.options.python_version[0] < 3 and is_same_type(erased, arg_type) and not isclass): - msg = messages.INVALID_SELF_TYPE_OR_EXTRA_ARG + msg = messages.ErrorCodes.INVALID_SELF_TYPE_OR_EXTRA_ARG note = '(Hint: typically annotations omit the type for self)' else: - msg = messages.ERASED_SELF_TYPE_NOT_SUPERTYPE.format( + msg = messages.ErrorCodes.ERASED_SELF_TYPE_NOT_SUPERTYPE.format( erased, ref_type) else: - msg = messages.MISSING_OR_INVALID_SELF_TYPE + msg = messages.ErrorCodes.MISSING_OR_INVALID_SELF_TYPE self.fail(msg, defn) if note: self.note(note, defn) @@ -901,7 +901,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) ctx = arg_type # type: Context if ctx.line < 0: ctx = typ - self.fail(messages.FUNCTION_PARAMETER_CANNOT_BE_COVARIANT, ctx) + self.fail(messages.ErrorCodes.FUNCTION_PARAMETER_CANNOT_BE_COVARIANT, ctx) if typ.arg_kinds[i] == nodes.ARG_STAR: # builtins.tuple[T] is typing.Tuple[T, ...] arg_type = self.named_generic_type('builtins.tuple', @@ -947,9 +947,9 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) # entirely pass/Ellipsis. if isinstance(return_type, UninhabitedType): # This is a NoReturn function - self.msg.note(messages.INVALID_IMPLICIT_RETURN, defn) + self.msg.note(messages.ErrorCodes.INVALID_IMPLICIT_RETURN, defn) else: - self.msg.fail(messages.MISSING_RETURN_STATEMENT, defn) + self.msg.fail(messages.ErrorCodes.MISSING_RETURN_STATEMENT, defn) self.return_types.pop() @@ -980,20 +980,20 @@ def is_unannotated_any(t: Type) -> bool: check_incomplete_defs = self.options.disallow_incomplete_defs and has_explicit_annotation if show_untyped and (self.options.disallow_untyped_defs or check_incomplete_defs): if fdef.type is None and self.options.disallow_untyped_defs: - self.fail(messages.FUNCTION_TYPE_EXPECTED, fdef) + self.fail(messages.ErrorCodes.FUNCTION_TYPE_EXPECTED, fdef) elif isinstance(fdef.type, CallableType): ret_type = fdef.type.ret_type if is_unannotated_any(ret_type): - self.fail(messages.RETURN_TYPE_EXPECTED, fdef) + self.fail(messages.ErrorCodes.RETURN_TYPE_EXPECTED, fdef) elif fdef.is_generator: if is_unannotated_any(self.get_generator_return_type(ret_type, fdef.is_coroutine)): - self.fail(messages.RETURN_TYPE_EXPECTED, fdef) + self.fail(messages.ErrorCodes.RETURN_TYPE_EXPECTED, fdef) elif fdef.is_coroutine and isinstance(ret_type, Instance): if is_unannotated_any(self.get_coroutine_return_type(ret_type)): - self.fail(messages.RETURN_TYPE_EXPECTED, fdef) + self.fail(messages.ErrorCodes.RETURN_TYPE_EXPECTED, fdef) if any(is_unannotated_any(t) for t in fdef.type.arg_types): - self.fail(messages.ARGUMENT_TYPE_EXPECTED, fdef) + self.fail(messages.ErrorCodes.ARGUMENT_TYPE_EXPECTED, fdef) def is_trivial_body(self, block: Block) -> bool: body = block.body @@ -1217,7 +1217,7 @@ def check_getattr_method(self, typ: Type, context: Context, name: str) -> None: if len(self.scope.stack) == 1: # module scope if name == '__getattribute__': - self.msg.fail(messages.MODULE_LEVEL_GETATTRIBUTE, context) + self.msg.fail(messages.ErrorCodes.MODULE_LEVEL_GETATTRIBUTE, context) return # __getattr__ is fine at the module level as of Python 3.7 (PEP 562). We could # show an error for Python < 3.7, but that would be annoying in code that supports @@ -1526,7 +1526,7 @@ def visit_class_def(self, defn: ClassDef) -> None: self.check_protocol_variance(defn) for base in typ.mro[1:]: if base.is_final: - self.fail(messages.CANNOT_INHERIT_FROM_FINAL.format(base.name()), defn) + self.fail(messages.ErrorCodes.CANNOT_INHERIT_FROM_FINAL.format(base.name()), defn) with self.tscope.class_scope(defn.info), self.enter_partial_types(is_class=True): old_binder = self.binder self.binder = ConditionalTypeBinder() @@ -1667,7 +1667,7 @@ def check_import(self, node: ImportBase) -> None: if lvalue_type is None: # TODO: This is broken. lvalue_type = AnyType(TypeOfAny.special_form) - message = '{} "{}"'.format(messages.INCOMPATIBLE_IMPORT_OF, + message = '{} "{}"'.format(messages.ErrorCodes.INCOMPATIBLE_IMPORT_OF, cast(NameExpr, assign.rvalue).name) self.check_simple_assignment(lvalue_type, assign.rvalue, node, msg=message, lvalue_name='local name', @@ -1719,7 +1719,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: self.check_final(s) if (s.is_final_def and s.type and not has_no_typevars(s.type) and self.scope.active_class() is not None): - self.fail(messages.DEPENDENT_FINAL_IN_CLASS_BODY, s) + self.fail(messages.ErrorCodes.DEPENDENT_FINAL_IN_CLASS_BODY, s) def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type: bool = True, new_syntax: bool = False) -> None: @@ -1913,7 +1913,7 @@ def check_compatibility_super(self, lvalue: RefExpr, lvalue_type: Optional[Type] lvalue_node.is_staticmethod = True return self.check_subtype(compare_type, base_type, lvalue, - messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, + messages.ErrorCodes.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, 'expression has type', 'base class "%s" defined the type as' % base.name()) return True @@ -1963,10 +1963,10 @@ def check_compatibility_classvar_super(self, node: Var, if not isinstance(base_node, Var): return True if node.is_classvar and not base_node.is_classvar: - self.fail(messages.CANNOT_OVERRIDE_INSTANCE_VAR.format(base.name()), node) + self.fail(messages.ErrorCodes.CANNOT_OVERRIDE_INSTANCE_VAR, node, (base.name(),)) return False elif not node.is_classvar and base_node.is_classvar: - self.fail(messages.CANNOT_OVERRIDE_CLASS_VAR.format(base.name()), node) + self.fail(messages.ErrorCodes.CANNOT_OVERRIDE_CLASS_VAR, node, (base.name(),)) return False return True @@ -2468,7 +2468,7 @@ def set_inference_error_fallback_type(self, var: Var, lvalue: Lvalue, type: Type def check_simple_assignment(self, lvalue_type: Optional[Type], rvalue: Expression, context: Context, - msg: str = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, + msg: messages.ErrorCodes = messages.ErrorCodes.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, lvalue_name: str = 'variable', rvalue_name: str = 'expression') -> Type: if self.is_stub and isinstance(rvalue, EllipsisExpr): @@ -2525,7 +2525,8 @@ def check_member_assignment(self, instance_type: Type, attribute_type: Type, dunder_set = attribute_type.type.get_method('__set__') if dunder_set is None: - self.msg.fail(messages.DESCRIPTOR_SET_NOT_CALLABLE.format(attribute_type), context) + self.msg.fail(messages.ErrorCodes.DESCRIPTOR_SET_NOT_CALLABLE, context, + (attribute_type,)) return AnyType(TypeOfAny.from_error), get_type, False function = function_type(dunder_set, self.named_type('builtins.function')) @@ -2636,7 +2637,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None: return_type = self.return_types[-1] if isinstance(return_type, UninhabitedType): - self.fail(messages.NO_RETURN_EXPECTED, s) + self.fail(messages.ErrorCodes.NO_RETURN_EXPECTED, s) return if s.expr: @@ -2657,7 +2658,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None: allow_none_return=allow_none_func_call) if defn.is_async_generator: - self.fail(messages.RETURN_IN_ASYNC_GENERATOR, s) + self.fail(messages.ErrorCodes.RETURN_IN_ASYNC_GENERATOR, s) return # Returning a value of type Any is always fine. if isinstance(typ, AnyType): @@ -2686,7 +2687,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None: supertype_label='expected', supertype=return_type, context=s, - msg=messages.INCOMPATIBLE_RETURN_VALUE_TYPE) + msg=messages.ErrorCodes.INCOMPATIBLE_RETURN_VALUE_TYPE) else: # Empty returns are valid in Generators with Any typed returns, but not in # coroutines. @@ -2698,7 +2699,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None: return if self.in_checked_function(): - self.fail(messages.RETURN_VALUE_EXPECTED, s) + self.fail(messages.ErrorCodes.RETURN_VALUE_EXPECTED, s) def visit_if_stmt(self, s: IfStmt) -> None: """Type check an if statement.""" @@ -2763,7 +2764,7 @@ def visit_assert_stmt(self, s: AssertStmt) -> None: self.expr_checker.accept(s.msg) if isinstance(s.expr, TupleExpr) and len(s.expr.items) > 0: - self.warn(messages.MALFORMED_ASSERT, s) + self.warn(messages.ErrorCodes.MALFORMED_ASSERT, s) # If this is asserting some isinstance check, bind that type in the following code true_map, _ = self.find_isinstance_check(s.expr) @@ -2805,7 +2806,7 @@ def type_check_raise(self, e: Expression, s: RaiseStmt, expected_type = self.named_type('builtins.BaseException') # type: Type if optional: expected_type = UnionType([expected_type, NoneTyp()]) - self.check_subtype(typ, expected_type, s, messages.INVALID_EXCEPTION) + self.check_subtype(typ, expected_type, s, messages.ErrorCodes.INVALID_EXCEPTION) def visit_try_stmt(self, s: TryStmt) -> None: """Type check a try statement.""" @@ -2909,17 +2910,17 @@ def check_except_handler_test(self, n: Expression) -> Type: if isinstance(ttype, FunctionLike): item = ttype.items()[0] if not item.is_type_obj(): - self.fail(messages.INVALID_EXCEPTION_TYPE, n) + self.fail(messages.ErrorCodes.INVALID_EXCEPTION_TYPE, n) return AnyType(TypeOfAny.from_error) exc_type = item.ret_type elif isinstance(ttype, TypeType): exc_type = ttype.item else: - self.fail(messages.INVALID_EXCEPTION_TYPE, n) + self.fail(messages.ErrorCodes.INVALID_EXCEPTION_TYPE, n) return AnyType(TypeOfAny.from_error) if not is_subtype(exc_type, self.named_type('builtins.BaseException')): - self.fail(messages.INVALID_EXCEPTION_TYPE, n) + self.fail(messages.ErrorCodes.INVALID_EXCEPTION_TYPE, n) return AnyType(TypeOfAny.from_error) all_types.append(exc_type) @@ -2960,7 +2961,7 @@ def analyze_async_iterable_item_type(self, expr: Expression) -> Tuple[Type, Type iterator = echk.check_method_call_by_name('__aiter__', iterable, [], [], expr)[0] awaitable = echk.check_method_call_by_name('__anext__', iterator, [], [], expr)[0] item_type = echk.check_awaitable_expr(awaitable, expr, - messages.INCOMPATIBLE_TYPES_IN_ASYNC_FOR) + messages.ErrorCodes.INCOMPATIBLE_TYPES_IN_ASYNC_FOR) return iterator, item_type def analyze_iterable_item_type(self, expr: Expression) -> Tuple[Type, Type]: @@ -3019,7 +3020,7 @@ def visit_decorator(self, e: Decorator) -> None: sig = self.function_type(e.func) # type: Type for d in reversed(e.decorators): if refers_to_fullname(d, 'typing.overload'): - self.fail(messages.MULTIPLE_OVERLOADS_REQUIRED, e) + self.fail(messages.ErrorCodes.MULTIPLE_OVERLOADS_REQUIRED, e) continue dec = self.expr_checker.accept(d) temp = self.temp_node(sig) @@ -3059,7 +3060,7 @@ def check_incompatible_property_override(self, e: Decorator) -> None: base_attr.node.is_property and cast(Decorator, base_attr.node.items[0]).var.is_settable_property): - self.fail(messages.READ_ONLY_PROPERTY_OVERRIDES_READ_WRITE, e) + self.fail(messages.ErrorCodes.READ_ONLY_PROPERTY_OVERRIDES_READ_WRITE, e) def visit_with_stmt(self, s: WithStmt) -> None: for expr, target in zip(s.expr, s.target): @@ -3082,14 +3083,14 @@ def check_async_with_item(self, expr: Expression, target: Optional[Expression], ctx = echk.accept(expr) obj = echk.check_method_call_by_name('__aenter__', ctx, [], [], expr)[0] obj = echk.check_awaitable_expr( - obj, expr, messages.INCOMPATIBLE_TYPES_IN_ASYNC_WITH_AENTER) + obj, expr, messages.ErrorCodes.INCOMPATIBLE_TYPES_IN_ASYNC_WITH_AENTER) if target: self.check_assignment(target, self.temp_node(obj, expr), infer_lvalue_type) arg = self.temp_node(AnyType(TypeOfAny.special_form), expr) res = echk.check_method_call_by_name( '__aexit__', ctx, [arg] * 3, [nodes.ARG_POS] * 3, expr)[0] echk.check_awaitable_expr( - res, expr, messages.INCOMPATIBLE_TYPES_IN_ASYNC_WITH_AEXIT) + res, expr, messages.ErrorCodes.INCOMPATIBLE_TYPES_IN_ASYNC_WITH_AEXIT) def check_with_item(self, expr: Expression, target: Optional[Expression], infer_lvalue_type: bool) -> None: @@ -3423,7 +3424,7 @@ def find_isinstance_check(self, node: Expression # def check_subtype(self, subtype: Type, supertype: Type, context: Context, - msg: str = messages.INCOMPATIBLE_TYPES, + msg: messages.ErrorCodes = messages.ErrorCodes.INCOMPATIBLE_TYPES, subtype_label: Optional[str] = None, supertype_label: Optional[str] = None) -> bool: """Generate an error if the subtype is not compatible with @@ -3443,8 +3444,9 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context, extra_info.append(supertype_label + ' ' + supertype_str) note_msg = make_inferred_type_note(context, subtype, supertype, supertype_str) - if extra_info: - msg += ' (' + ', '.join(extra_info) + ')' + # FIXME: + # if extra_info: + # msg += ' (' + ', '.join(extra_info) + ')' self.fail(msg, context) if note_msg: self.note(note_msg, context) @@ -3694,9 +3696,10 @@ def temp_node(self, t: Type, context: Optional[Context] = None) -> TempNode: temp.set_line(context.get_line()) return temp - def fail(self, msg: str, context: Context) -> None: + def fail(self, msg: messages.ErrorCodes, context: Context, + format: Optional[Tuple[Any, ...]] = None) -> None: """Produce an error message.""" - self.msg.fail(msg, context) + self.msg.fail(msg, context, format) def warn(self, msg: str, context: Context) -> None: """Produce a warning message.""" diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 6e06a06b78f0..e063adaba366 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -395,7 +395,7 @@ def check_runtime_protocol_test(self, e: CallExpr) -> None: if (isinstance(tp, CallableType) and tp.is_type_obj() and tp.type_object().is_protocol and not tp.type_object().runtime_protocol): - self.chk.fail(messages.RUNTIME_PROTOCOL_EXPECTED, e) + self.chk.fail(messages.ErrorCodes.RUNTIME_PROTOCOL_EXPECTED, e) def check_protocol_issubclass(self, e: CallExpr) -> None: for expr in mypy.checker.flatten(e.args[1]): @@ -434,7 +434,7 @@ def check_typeddict_call(self, callee: TypedDictType, return self.check_typeddict_call_with_kwargs( callee, OrderedDict(), context) - self.chk.fail(messages.INVALID_TYPEDDICT_ARGS, context) + self.chk.fail(messages.ErrorCodes.INVALID_TYPEDDICT_ARGS, context) return AnyType(TypeOfAny.from_error) def check_typeddict_call_with_dict(self, callee: TypedDictType, @@ -446,7 +446,7 @@ def check_typeddict_call_with_dict(self, callee: TypedDictType, for item_name_expr, item_arg in kwargs.items: if not isinstance(item_name_expr, StrExpr): key_context = item_name_expr or item_arg - self.chk.fail(messages.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, key_context) + self.chk.fail(messages.ErrorCodes.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, key_context) return AnyType(TypeOfAny.from_error) item_names.append(item_name_expr.value) @@ -472,7 +472,7 @@ def check_typeddict_call_with_kwargs(self, callee: TypedDictType, item_value = kwargs[item_name] self.chk.check_simple_assignment( lvalue_type=item_expected_type, rvalue=item_value, context=item_value, - msg=messages.INCOMPATIBLE_TYPES, + msg=messages.ErrorCodes.INCOMPATIBLE_TYPES, lvalue_name='TypedDict item "{}"'.format(item_name), rvalue_name='expression') @@ -758,7 +758,7 @@ def check_callable_call(self, elif (callee.is_type_obj() and callee.type_object().is_protocol # Exception for Type[...] and not callee.from_type_type): - self.chk.fail(messages.CANNOT_INSTANTIATE_PROTOCOL + self.chk.fail(messages.ErrorCodes.CANNOT_INSTANTIATE_PROTOCOL .format(callee.type_object().name()), context) formal_to_actual = map_actuals_to_formals( @@ -1006,7 +1006,7 @@ def infer_function_type_arguments(self, callee_type: CallableType, if isinstance(first_arg, (NoneTyp, UninhabitedType)): inferred_args[0] = self.named_type('builtins.str') elif not first_arg or not is_subtype(self.named_type('builtins.str'), first_arg): - self.msg.fail(messages.KEYWORD_ARGUMENT_REQUIRES_STR_KEY_TYPE, + self.msg.fail(messages.ErrorCodes.KEYWORD_ARGUMENT_REQUIRES_STR_KEY_TYPE, context) else: # In dynamically typed functions use implicit 'Any' types for @@ -2403,7 +2403,7 @@ def visit_index_expr_helper(self, e: IndexExpr) -> Type: if n >= 0 and n < len(left_type.items): return left_type.items[n] else: - self.chk.fail(messages.TUPLE_INDEX_OUT_OF_RANGE, e) + self.chk.fail(messages.ErrorCodes.TUPLE_INDEX_OUT_OF_RANGE, e) return AnyType(TypeOfAny.from_error) else: return self.nonliteral_tuple_index_helper(left_type, index) @@ -2445,7 +2445,7 @@ def nonliteral_tuple_index_helper(self, left_type: TupleType, index: Expression) expected_type = UnionType.make_union([self.named_type('builtins.int'), self.named_type('builtins.slice')]) if not self.chk.check_subtype(index_type, expected_type, index, - messages.INVALID_TUPLE_INDEX_TYPE, + messages.ErrorCodes.INVALID_TUPLE_INDEX_TYPE, 'actual type', 'expected type'): return AnyType(TypeOfAny.from_error) else: @@ -2549,14 +2549,14 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: tp = type_object_type(item.type, self.named_type) return self.apply_type_arguments_to_callable(tp, item.args, tapp) else: - self.chk.fail(messages.ONLY_CLASS_APPLICATION, tapp) + self.chk.fail(messages.ErrorCodes.ONLY_CLASS_APPLICATION, tapp) return AnyType(TypeOfAny.from_error) # Type application of a normal generic class in runtime context. # This is typically used as `x = G[int]()`. tp = self.accept(tapp.expr) if isinstance(tp, (CallableType, Overloaded)): if not tp.is_type_obj(): - self.chk.fail(messages.ONLY_CLASS_APPLICATION, tapp) + self.chk.fail(messages.ErrorCodes.ONLY_CLASS_APPLICATION, tapp) return self.apply_type_arguments_to_callable(tp, tapp.types, tapp) if isinstance(tp, AnyType): return AnyType(TypeOfAny.from_another_any, source_any=tp) @@ -2884,7 +2884,7 @@ def infer_lambda_type_using_context(self, e: LambdaExpr) -> Tuple[Optional[Calla return callable_ctx, None if callable_ctx.arg_kinds != arg_kinds: # Incompatible context; cannot use it to infer types. - self.chk.fail(messages.CANNOT_INFER_LAMBDA_TYPE, e) + self.chk.fail(messages.ErrorCodes.CANNOT_INFER_LAMBDA_TYPE, e) return None, None return callable_ctx, callable_ctx @@ -2898,15 +2898,15 @@ def visit_super_expr(self, e: SuperExpr) -> Type: def check_super_arguments(self, e: SuperExpr) -> None: """Check arguments in a super(...) call.""" if ARG_STAR in e.call.arg_kinds: - self.chk.fail(messages.SUPER_VARARGS_NOT_SUPPORTED, e) + self.chk.fail(messages.ErrorCodes.SUPER_VARARGS_NOT_SUPPORTED, e) elif e.call.args and set(e.call.arg_kinds) != {ARG_POS}: - self.chk.fail(messages.SUPER_POSITIONAL_ARGS_REQUIRED, e) + self.chk.fail(messages.ErrorCodes.SUPER_POSITIONAL_ARGS_REQUIRED, e) elif len(e.call.args) == 1: - self.chk.fail(messages.SUPER_WITH_SINGLE_ARG_NOT_SUPPORTED, e) + self.chk.fail(messages.ErrorCodes.SUPER_WITH_SINGLE_ARG_NOT_SUPPORTED, e) elif len(e.call.args) > 2: - self.chk.fail(messages.TOO_MANY_ARGS_FOR_SUPER, e) + self.chk.fail(messages.ErrorCodes.TOO_MANY_ARGS_FOR_SUPER, e) elif self.chk.options.python_version[0] == 2 and len(e.call.args) == 0: - self.chk.fail(messages.TOO_FEW_ARGS_FOR_SUPER, e) + self.chk.fail(messages.ErrorCodes.TOO_FEW_ARGS_FOR_SUPER, e) elif len(e.call.args) == 2: type_obj_type = self.accept(e.call.args[0]) instance_type = self.accept(e.call.args[1]) @@ -2922,7 +2922,7 @@ def check_super_arguments(self, e: SuperExpr) -> None: if not isinstance(item, Instance): # A complicated type object type. Too tricky, give up. # TODO: Do something more clever here. - self.chk.fail(messages.UNSUPPORTED_ARG_1_FOR_SUPER, e) + self.chk.fail(messages.ErrorCodes.UNSUPPORTED_ARG_1_FOR_SUPER, e) return type_info = item.type elif isinstance(type_obj_type, AnyType): @@ -2938,19 +2938,19 @@ def check_super_arguments(self, e: SuperExpr) -> None: if not isinstance(instance_type, (Instance, TupleType)): # Too tricky, give up. # TODO: Do something more clever here. - self.chk.fail(messages.UNSUPPORTED_ARG_2_FOR_SUPER, e) + self.chk.fail(messages.ErrorCodes.UNSUPPORTED_ARG_2_FOR_SUPER, e) return if isinstance(instance_type, TupleType): # Needed for named tuples and other Tuple[...] subclasses. instance_type = instance_type.fallback if type_info not in instance_type.type.mro: - self.chk.fail(messages.SUPER_ARG_2_NOT_INSTANCE_OF_ARG_1, e) + self.chk.fail(messages.ErrorCodes.SUPER_ARG_2_NOT_INSTANCE_OF_ARG_1, e) elif isinstance(instance_type, TypeType) or (isinstance(instance_type, FunctionLike) and instance_type.is_type_obj()): # TODO: Check whether this is a valid type object here. pass elif not isinstance(instance_type, AnyType): - self.chk.fail(messages.UNSUPPORTED_ARG_2_FOR_SUPER, e) + self.chk.fail(messages.ErrorCodes.UNSUPPORTED_ARG_2_FOR_SUPER, e) def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: """Type check a super expression.""" @@ -2969,7 +2969,7 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: if not self.chk.in_checked_function(): return AnyType(TypeOfAny.unannotated) if self.chk.scope.active_class() is not None: - self.chk.fail(messages.SUPER_OUTSIDE_OF_METHOD_NOT_SUPPORTED, e) + self.chk.fail(messages.ErrorCodes.SUPER_OUTSIDE_OF_METHOD_NOT_SUPPORTED, e) return AnyType(TypeOfAny.from_error) method = self.chk.scope.top_function() assert method is not None @@ -2977,7 +2977,7 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type: # super() in a function with empty args is an error; we # need something in declared_self. if not args: - self.chk.fail(messages.SUPER_ENCLOSING_POSITIONAL_ARGS_REQUIRED, e) + self.chk.fail(messages.ErrorCodes.SUPER_ENCLOSING_POSITIONAL_ARGS_REQUIRED, e) return AnyType(TypeOfAny.from_error) declared_self = args[0].variable.type or fill_typevars(e.info) return analyze_member_access(name=e.name, @@ -3002,7 +3002,7 @@ def visit_slice_expr(self, e: SliceExpr) -> Type: if index: t = self.accept(index) self.chk.check_subtype(t, expected, - index, messages.INVALID_SLICE_INDEX) + index, messages.ErrorCodes.INVALID_SLICE_INDEX) return self.named_type('builtins.slice') def visit_list_comprehension(self, e: ListComprehension) -> Type: @@ -3273,11 +3273,11 @@ def visit_yield_expr(self, e: YieldExpr) -> Type: if e.expr is None: if (not isinstance(expected_item_type, (NoneTyp, AnyType)) and self.chk.in_checked_function()): - self.chk.fail(messages.YIELD_VALUE_EXPECTED, e) + self.chk.fail(messages.ErrorCodes.YIELD_VALUE_EXPECTED, e) else: actual_item_type = self.accept(e.expr, expected_item_type) self.chk.check_subtype(actual_item_type, expected_item_type, e, - messages.INCOMPATIBLE_TYPES_IN_YIELD, + messages.ErrorCodes.INCOMPATIBLE_TYPES_IN_YIELD, 'actual type', 'expected type') return self.chk.get_generator_receive_type(return_type, False) @@ -3288,9 +3288,9 @@ def visit_await_expr(self, e: AwaitExpr) -> Type: actual_type = self.accept(e.expr, expected_type) if isinstance(actual_type, AnyType): return AnyType(TypeOfAny.from_another_any, source_any=actual_type) - return self.check_awaitable_expr(actual_type, e, messages.INCOMPATIBLE_TYPES_IN_AWAIT) + return self.check_awaitable_expr(actual_type, e, messages.ErrorCodes.INCOMPATIBLE_TYPES_IN_AWAIT) - def check_awaitable_expr(self, t: Type, ctx: Context, msg: str) -> Type: + def check_awaitable_expr(self, t: Type, ctx: Context, msg: messages.ErrorCodes) -> Type: """Check the argument to `await` and extract the type of value. Also used by `async for` and `async with`. @@ -3334,7 +3334,7 @@ def visit_yield_from_expr(self, e: YieldFromExpr, allow_none_return: bool = Fals iter_type = AnyType(TypeOfAny.from_error) else: iter_type = self.check_awaitable_expr(subexpr_type, e, - messages.INCOMPATIBLE_TYPES_IN_YIELD_FROM) + messages.ErrorCodes.INCOMPATIBLE_TYPES_IN_YIELD_FROM) # Check that the iterator's item type matches the type yielded by the Generator function # containing this `yield from` expression. @@ -3342,7 +3342,7 @@ def visit_yield_from_expr(self, e: YieldFromExpr, allow_none_return: bool = Fals actual_item_type = self.chk.get_generator_yield_type(iter_type, False) self.chk.check_subtype(actual_item_type, expected_item_type, e, - messages.INCOMPATIBLE_TYPES_IN_YIELD_FROM, + messages.ErrorCodes.INCOMPATIBLE_TYPES_IN_YIELD_FROM, 'actual type', 'expected type') # Determine the type of the entire yield from expression. diff --git a/mypy/checkmember.py b/mypy/checkmember.py index bb86a3fb1a02..a62c7b58122e 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -147,7 +147,7 @@ def analyze_instance_member_access(name: str, if name == '__init__' and not mx.is_super: # Accessing __init__ in statically typed code would compromise # type safety unless used via super(). - mx.msg.fail(messages.CANNOT_ACCESS_INIT, mx.context) + mx.msg.fail(messages.ErrorCodes.CANNOT_ACCESS_INIT, mx.context) return AnyType(TypeOfAny.from_error) # The base object has an instance type. @@ -405,7 +405,7 @@ def analyze_descriptor_access(instance_type: Type, dunder_get = descriptor_type.type.get_method('__get__') if dunder_get is None: - msg.fail(messages.DESCRIPTOR_GET_NOT_CALLABLE.format(descriptor_type), context) + msg.fail(messages.ErrorCodes.DESCRIPTOR_GET_NOT_CALLABLE.format(descriptor_type), context) return AnyType(TypeOfAny.from_error) function = function_type(dunder_get, builtin_type('builtins.function')) @@ -432,7 +432,7 @@ def analyze_descriptor_access(instance_type: Type, return inferred_dunder_get_type if not isinstance(inferred_dunder_get_type, CallableType): - msg.fail(messages.DESCRIPTOR_GET_NOT_CALLABLE.format(descriptor_type), context) + msg.fail(messages.ErrorCodes.DESCRIPTOR_GET_NOT_CALLABLE.format(descriptor_type), context) return AnyType(TypeOfAny.from_error) return inferred_dunder_get_type.ret_type @@ -586,12 +586,12 @@ def analyze_class_attribute_access(itype: Instance, if is_method: mx.msg.cannot_assign_to_method(mx.context) if isinstance(node.node, TypeInfo): - mx.msg.fail(messages.CANNOT_ASSIGN_TO_TYPE, mx.context) + mx.msg.fail(messages.ErrorCodes.CANNOT_ASSIGN_TO_TYPE, mx.context) # If a final attribute was declared on `self` in `__init__`, then it # can't be accessed on the class object. if node.implicit and isinstance(node.node, Var) and node.node.is_final: - mx.msg.fail(messages.CANNOT_ACCESS_FINAL_INSTANCE_ATTR + mx.msg.fail(messages.ErrorCodes.CANNOT_ACCESS_FINAL_INSTANCE_ATTR .format(node.node.name()), mx.context) # An assignment to final attribute on class object is also always an error, @@ -609,7 +609,7 @@ def analyze_class_attribute_access(itype: Instance, assert isinstance(symnode, Var) return mx.chk.handle_partial_var_type(t, mx.is_lvalue, symnode, mx.context) if not is_method and (isinstance(t, TypeVarType) or get_type_vars(t)): - mx.msg.fail(messages.GENERIC_INSTANCE_VAR_CLASS_ACCESS, mx.context) + mx.msg.fail(messages.ErrorCodes.GENERIC_INSTANCE_VAR_CLASS_ACCESS, mx.context) is_classmethod = ((is_decorated and cast(Decorator, node.node).func.is_class) or (isinstance(node.node, FuncBase) and node.node.is_class)) result = add_class_tvars(t, itype, is_classmethod, mx.builtin_type, mx.original_type) @@ -622,7 +622,7 @@ def analyze_class_attribute_access(itype: Instance, return AnyType(TypeOfAny.special_form) if isinstance(node.node, TypeVarExpr): - mx.msg.fail(messages.CANNOT_USE_TYPEVAR_AS_EXPRESSION.format( + mx.msg.fail(messages.ErrorCodes.CANNOT_USE_TYPEVAR_AS_EXPRESSION.format( itype.type.name(), name), mx.context) return AnyType(TypeOfAny.from_error) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index 6ec0548c87e7..6d6beb29b873 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -192,7 +192,7 @@ def check_mapping_str_interpolation(self, specifiers: List[ConversionSpecifier], if expected_type is None: return self.chk.check_subtype(rep_type, expected_type, replacements, - messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, + messages.ErrorCodes.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, 'expression has type', 'placeholder with key \'%s\' has type' % specifier.key) else: @@ -201,7 +201,7 @@ def check_mapping_str_interpolation(self, specifiers: List[ConversionSpecifier], dict_type = self.chk.named_generic_type('builtins.dict', [any_type, any_type]) self.chk.check_subtype(rep_type, dict_type, replacements, - messages.FORMAT_REQUIRES_MAPPING, + messages.ErrorCodes.FORMAT_REQUIRES_MAPPING, 'expression has type', 'expected type for mapping is') def build_replacement_checkers(self, specifiers: List[ConversionSpecifier], @@ -268,7 +268,7 @@ def checkers_for_regular_type(self, type: str, def check_type(type: Type) -> None: assert expected_type is not None self.chk.check_subtype(type, expected_type, context, - messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, + messages.ErrorCodes.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, 'expression has type', 'placeholder has type') def check_expr(expr: Expression) -> None: @@ -290,7 +290,7 @@ def checkers_for_c_type(self, type: str, def check_type(type: Type) -> None: assert expected_type is not None self.chk.check_subtype(type, expected_type, context, - messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, + messages.ErrorCodes.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, 'expression has type', 'placeholder has type') def check_expr(expr: Expression) -> None: diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 15122012a0dc..81174d243563 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -217,7 +217,7 @@ def __init__(self, def note(self, msg: str, line: int, column: int) -> None: self.errors.report(line, column, msg, severity='note') - def fail(self, msg: str, line: int, column: int) -> None: + def fail(self, msg: messages.ErrorCodes, line: int, column: int) -> None: self.errors.report(line, column, msg, blocker=True) def visit(self, node: Optional[AST]) -> Any: @@ -404,7 +404,7 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef], isinstance(func_type_ast.argtypes[0], ast3_Ellipsis)): if n.returns: # PEP 484 disallows both type annotations and type comments - self.fail(messages.DUPLICATE_TYPE_SIGNATURES, lineno, n.col_offset) + self.fail(messages.ErrorCodes.DUPLICATE_TYPE_SIGNATURES, lineno, n.col_offset) arg_types = [a.type_annotation if a.type_annotation is not None else AnyType(TypeOfAny.unannotated) @@ -412,7 +412,7 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef], else: # PEP 484 disallows both type annotations and type comments if n.returns or any(a.type_annotation is not None for a in args): - self.fail(messages.DUPLICATE_TYPE_SIGNATURES, lineno, n.col_offset) + self.fail(messages.ErrorCodes.DUPLICATE_TYPE_SIGNATURES, lineno, n.col_offset) translated_args = (TypeConverter(self.errors, line=lineno) .translate_expr_list(func_type_ast.argtypes)) arg_types = [a if a is not None else AnyType(TypeOfAny.unannotated) @@ -540,7 +540,7 @@ def make_argument(self, arg: ast3.arg, default: Optional[ast3.expr], kind: int, annotation = arg.annotation type_comment = arg.type_comment if annotation is not None and type_comment is not None: - self.fail(messages.DUPLICATE_TYPE_SIGNATURES, arg.lineno, arg.col_offset) + self.fail(messages.ErrorCodes.DUPLICATE_TYPE_SIGNATURES, arg.lineno, arg.col_offset) arg_type = None if annotation is not None: arg_type = TypeConverter(self.errors, line=arg.lineno).visit(annotation) diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index 876864796ebe..54508687b37b 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -169,7 +169,7 @@ def __init__(self, # Cache of visit_X methods keyed by type of visited object self.visitor_cache = {} # type: Dict[type, Callable[[Optional[AST]], Any]] - def fail(self, msg: str, line: int, column: int) -> None: + def fail(self, msg: messages.ErrorCodes, line: int, column: int) -> None: self.errors.report(line, column, msg, blocker=True) def visit(self, node: Optional[AST]) -> Any: # same as in typed_ast stub @@ -351,7 +351,7 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement: else: # PEP 484 disallows both type annotations and type comments if any(a.type_annotation is not None for a in args): - self.fail(messages.DUPLICATE_TYPE_SIGNATURES, lineno, n.col_offset) + self.fail(messages.ErrorCodes.DUPLICATE_TYPE_SIGNATURES, lineno, n.col_offset) arg_types = [a if a is not None else AnyType(TypeOfAny.unannotated) for a in converter.translate_expr_list(func_type_ast.argtypes)] return_type = converter.visit(func_type_ast.returns) diff --git a/mypy/messages.py b/mypy/messages.py index d23536d35de3..e37861a157d4 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -15,6 +15,7 @@ import re import difflib from textwrap import dedent +from enum import Enum from typing import ( cast, List, Dict, Any, Sequence, Iterable, Tuple, Set, Optional, Union, Deque, TypeVar, @@ -43,129 +44,133 @@ # Type checker error message constants -- -MISSING_RETURN_STATEMENT = 'Missing return statement' # type: Final -INVALID_IMPLICIT_RETURN = 'Implicit return in function which does not return' # type: Final -INCOMPATIBLE_RETURN_VALUE_TYPE = 'Incompatible return value type' # type: Final -RETURN_VALUE_EXPECTED = 'Return value expected' # type: Final -NO_RETURN_EXPECTED = 'Return statement in function which does not return' # type: Final -INVALID_EXCEPTION = 'Exception must be derived from BaseException' # type: Final -INVALID_EXCEPTION_TYPE = 'Exception type must be derived from BaseException' # type: Final -RETURN_IN_ASYNC_GENERATOR = "'return' with value in async generator is not allowed" # type: Final -INVALID_RETURN_TYPE_FOR_GENERATOR = \ - 'The return type of a generator function should be "Generator"' \ - ' or one of its supertypes' # type: Final -INVALID_RETURN_TYPE_FOR_ASYNC_GENERATOR = \ - 'The return type of an async generator function should be "AsyncGenerator" or one of its ' \ - 'supertypes' # type: Final -INVALID_GENERATOR_RETURN_ITEM_TYPE = \ - 'The return type of a generator function must be None in' \ - ' its third type parameter in Python 2' # type: Final -YIELD_VALUE_EXPECTED = 'Yield value expected' # type: Final -INCOMPATIBLE_TYPES = 'Incompatible types' # type: Final -INCOMPATIBLE_TYPES_IN_ASSIGNMENT = 'Incompatible types in assignment' # type: Final -INCOMPATIBLE_REDEFINITION = 'Incompatible redefinition' # type: Final -INCOMPATIBLE_TYPES_IN_AWAIT = 'Incompatible types in "await"' # type: Final -INCOMPATIBLE_TYPES_IN_ASYNC_WITH_AENTER = \ - 'Incompatible types in "async with" for "__aenter__"' # type: Final -INCOMPATIBLE_TYPES_IN_ASYNC_WITH_AEXIT = \ - 'Incompatible types in "async with" for "__aexit__"' # type: Final -INCOMPATIBLE_TYPES_IN_ASYNC_FOR = 'Incompatible types in "async for"' # type: Final - -INCOMPATIBLE_TYPES_IN_YIELD = 'Incompatible types in "yield"' # type: Final -INCOMPATIBLE_TYPES_IN_YIELD_FROM = 'Incompatible types in "yield from"' # type: Final -INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION = \ - 'Incompatible types in string interpolation' # type: Final -INVALID_TUPLE_INDEX_TYPE = 'Invalid tuple index type' # type: Final -TUPLE_INDEX_OUT_OF_RANGE = 'Tuple index out of range' # type: Final -INVALID_SLICE_INDEX = 'Slice index must be an integer or None' # type: Final -CANNOT_INFER_LAMBDA_TYPE = 'Cannot infer type of lambda' # type: Final -CANNOT_INFER_ITEM_TYPE = 'Cannot infer iterable item type' # type: Final -CANNOT_ACCESS_INIT = 'Cannot access "__init__" directly' # type: Final -CANNOT_ASSIGN_TO_METHOD = 'Cannot assign to a method' # type: Final -CANNOT_ASSIGN_TO_TYPE = 'Cannot assign to a type' # type: Final -INCONSISTENT_ABSTRACT_OVERLOAD = \ - 'Overloaded method has both abstract and non-abstract variants' # type: Final -MULTIPLE_OVERLOADS_REQUIRED = 'Single overload definition, multiple required' # type: Final -READ_ONLY_PROPERTY_OVERRIDES_READ_WRITE = \ - 'Read-only property cannot override read-write property' # type: Final -FORMAT_REQUIRES_MAPPING = 'Format requires a mapping' # type: Final -RETURN_TYPE_CANNOT_BE_CONTRAVARIANT = \ - "Cannot use a contravariant type variable as return type" # type: Final -FUNCTION_PARAMETER_CANNOT_BE_COVARIANT = \ - "Cannot use a covariant type variable as a parameter" # type: Final -INCOMPATIBLE_IMPORT_OF = "Incompatible import of" # type: Final -FUNCTION_TYPE_EXPECTED = "Function is missing a type annotation" # type: Final -ONLY_CLASS_APPLICATION = "Type application is only supported for generic classes" # type: Final -RETURN_TYPE_EXPECTED = "Function is missing a return type annotation" # type: Final -ARGUMENT_TYPE_EXPECTED = \ - "Function is missing a type annotation for one or more arguments" # type: Final -KEYWORD_ARGUMENT_REQUIRES_STR_KEY_TYPE = \ - 'Keyword argument only valid with "str" key type in call to "dict"' # type: Final -ALL_MUST_BE_SEQ_STR = 'Type of __all__ must be {}, not {}' # type: Final -INVALID_TYPEDDICT_ARGS = \ - 'Expected keyword arguments, {...}, or dict(...) in TypedDict constructor' # type: Final -TYPEDDICT_KEY_MUST_BE_STRING_LITERAL = \ - 'Expected TypedDict key to be string literal' # type: Final -MALFORMED_ASSERT = 'Assertion is always true, perhaps remove parentheses?' # type: Final -DUPLICATE_TYPE_SIGNATURES = 'Function has duplicate type signatures' # type: Final -DESCRIPTOR_SET_NOT_CALLABLE = "{}.__set__ is not callable" # type: Final -DESCRIPTOR_GET_NOT_CALLABLE = "{}.__get__ is not callable" # type: Final -MODULE_LEVEL_GETATTRIBUTE = '__getattribute__ is not valid at the module level' # type: Final - -# Generic -GENERIC_INSTANCE_VAR_CLASS_ACCESS = \ - 'Access to generic instance variables via class is ambiguous' # type: Final -BARE_GENERIC = 'Missing type parameters for generic type' # type: Final -IMPLICIT_GENERIC_ANY_BUILTIN = \ - 'Implicit generic "Any". Use "{}" and specify generic parameters' # type: Final - -# TypeVar -INCOMPATIBLE_TYPEVAR_VALUE = 'Value of type variable "{}" of {} cannot be {}' # type: Final -CANNOT_USE_TYPEVAR_AS_EXPRESSION = \ - 'Type variable "{}.{}" cannot be used as an expression' # type: Final - -# Super -TOO_MANY_ARGS_FOR_SUPER = 'Too many arguments for "super"' # type: Final -TOO_FEW_ARGS_FOR_SUPER = 'Too few arguments for "super"' # type: Final -SUPER_WITH_SINGLE_ARG_NOT_SUPPORTED = '"super" with a single argument not supported' # type: Final -UNSUPPORTED_ARG_1_FOR_SUPER = 'Unsupported argument 1 for "super"' # type: Final -UNSUPPORTED_ARG_2_FOR_SUPER = 'Unsupported argument 2 for "super"' # type: Final -SUPER_VARARGS_NOT_SUPPORTED = 'Varargs not supported with "super"' # type: Final -SUPER_POSITIONAL_ARGS_REQUIRED = '"super" only accepts positional arguments' # type: Final -SUPER_ARG_2_NOT_INSTANCE_OF_ARG_1 = \ - 'Argument 2 for "super" not an instance of argument 1' # type: Final -SUPER_OUTSIDE_OF_METHOD_NOT_SUPPORTED = \ - 'super() outside of a method is not supported' # type: Final -SUPER_ENCLOSING_POSITIONAL_ARGS_REQUIRED = \ - 'super() requires one or more positional arguments in enclosing function' # type: Final - -# Self-type -MISSING_OR_INVALID_SELF_TYPE = \ - "Self argument missing for a non-static method (or an invalid type for self)" # type: Final -ERASED_SELF_TYPE_NOT_SUPERTYPE = \ - 'The erased type of self "{}" is not a supertype of its class "{}"' # type: Final -INVALID_SELF_TYPE_OR_EXTRA_ARG = \ - "Invalid type for self, or extra argument type in function annotation" # type: Final - -# Final -CANNOT_INHERIT_FROM_FINAL = 'Cannot inherit from final class "{}"' # type: Final -DEPENDENT_FINAL_IN_CLASS_BODY = \ - "Final name declared in class body cannot depend on type variables" # type: Final -CANNOT_ACCESS_FINAL_INSTANCE_ATTR = \ - 'Cannot access final instance attribute "{}" on class object' # type: Final - -# ClassVar -CANNOT_OVERRIDE_INSTANCE_VAR = \ - 'Cannot override instance variable (previously declared on base class "{}") with class ' \ - 'variable' # type: Final -CANNOT_OVERRIDE_CLASS_VAR = \ - 'Cannot override class variable (previously declared on base class "{}") with instance ' \ - 'variable' # type: Final - -# Protocol -RUNTIME_PROTOCOL_EXPECTED = \ - 'Only @runtime protocols can be used with instance and class checks' # type: Final -CANNOT_INSTANTIATE_PROTOCOL = 'Cannot instantiate protocol class "{}"' # type: Final +CANNOT_ASSIGN_TO_METHOD = 'Cannot assign to a method' +INCOMPATIBLE_TYPEVAR_VALUE = 'Value of type variable "{}" of {} cannot be {}' + + +class ErrorCodes(Enum): + MISSING_RETURN_STATEMENT = 'Missing return statement' + INVALID_IMPLICIT_RETURN = 'Implicit return in function which does not return' + + INCOMPATIBLE_RETURN_VALUE_TYPE = 'Incompatible return value type' + RETURN_VALUE_EXPECTED = 'Return value expected' + NO_RETURN_EXPECTED = 'Return statement in function which does not return' + INVALID_EXCEPTION = 'Exception must be derived from BaseException' + INVALID_EXCEPTION_TYPE = 'Exception type must be derived from BaseException' + RETURN_IN_ASYNC_GENERATOR = "'return' with value in async generator is not allowed" + INVALID_RETURN_TYPE_FOR_GENERATOR = \ + 'The return type of a generator function should be "Generator"' \ + ' or one of its supertypes' + INVALID_RETURN_TYPE_FOR_ASYNC_GENERATOR = \ + 'The return type of an async generator function should be "AsyncGenerator" or one of its ' \ + 'supertypes' + INVALID_GENERATOR_RETURN_ITEM_TYPE = \ + 'The return type of a generator function must be None in' \ + ' its third type parameter in Python 2' + YIELD_VALUE_EXPECTED = 'Yield value expected' + INCOMPATIBLE_TYPES = 'Incompatible types' + INCOMPATIBLE_REDEFINITION = 'Incompatible redefinition' + INCOMPATIBLE_TYPES_IN_ASSIGNMENT = 'Incompatible types in assignment' + INCOMPATIBLE_TYPES_IN_AWAIT = 'Incompatible types in "await"' + INCOMPATIBLE_TYPES_IN_ASYNC_WITH_AENTER = \ + 'Incompatible types in "async with" for "__aenter__"' + INCOMPATIBLE_TYPES_IN_ASYNC_WITH_AEXIT = \ + 'Incompatible types in "async with" for "__aexit__"' + INCOMPATIBLE_TYPES_IN_ASYNC_FOR = 'Incompatible types in "async for"' + + INCOMPATIBLE_TYPES_IN_YIELD = 'Incompatible types in "yield"' + INCOMPATIBLE_TYPES_IN_YIELD_FROM = 'Incompatible types in "yield from"' + INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION = \ + 'Incompatible types in string interpolation' + INVALID_TUPLE_INDEX_TYPE = 'Invalid tuple index type' + TUPLE_INDEX_OUT_OF_RANGE = 'Tuple index out of range' + INVALID_SLICE_INDEX = 'Slice index must be an integer or None' + CANNOT_INFER_LAMBDA_TYPE = 'Cannot infer type of lambda' + CANNOT_INFER_ITEM_TYPE = 'Cannot infer iterable item type' + CANNOT_ACCESS_INIT = 'Cannot access "__init__" directly' + CANNOT_ASSIGN_TO_TYPE = 'Cannot assign to a type' + INCONSISTENT_ABSTRACT_OVERLOAD = \ + 'Overloaded method has both abstract and non-abstract variants' + MULTIPLE_OVERLOADS_REQUIRED = 'Single overload definition, multiple required' + READ_ONLY_PROPERTY_OVERRIDES_READ_WRITE = \ + 'Read-only property cannot override read-write property' + FORMAT_REQUIRES_MAPPING = 'Format requires a mapping' + RETURN_TYPE_CANNOT_BE_CONTRAVARIANT = \ + "Cannot use a contravariant type variable as return type" + FUNCTION_PARAMETER_CANNOT_BE_COVARIANT = \ + "Cannot use a covariant type variable as a parameter" + INCOMPATIBLE_IMPORT_OF = "Incompatible import of" + FUNCTION_TYPE_EXPECTED = "Function is missing a type annotation" + ONLY_CLASS_APPLICATION = "Type application is only supported for generic classes" + RETURN_TYPE_EXPECTED = "Function is missing a return type annotation" + ARGUMENT_TYPE_EXPECTED = \ + "Function is missing a type annotation for one or more arguments" + KEYWORD_ARGUMENT_REQUIRES_STR_KEY_TYPE = \ + 'Keyword argument only valid with "str" key type in call to "dict"' + ALL_MUST_BE_SEQ_STR = 'Type of __all__ must be {}, not {}' + INVALID_TYPEDDICT_ARGS = \ + 'Expected keyword arguments, {...}, or dict(...) in TypedDict constructor' + TYPEDDICT_KEY_MUST_BE_STRING_LITERAL = \ + 'Expected TypedDict key to be string literal' + MALFORMED_ASSERT = 'Assertion is always true, perhaps remove parentheses?' + DUPLICATE_TYPE_SIGNATURES = 'Function has duplicate type signatures' + DESCRIPTOR_SET_NOT_CALLABLE = "{}.__set__ is not callable" + DESCRIPTOR_GET_NOT_CALLABLE = "{}.__get__ is not callable" + MODULE_LEVEL_GETATTRIBUTE = '__getattribute__ is not valid at the module level' + + # Generic + GENERIC_INSTANCE_VAR_CLASS_ACCESS = \ + 'Access to generic instance variables via class is ambiguous' + BARE_GENERIC = 'Missing type parameters for generic type' + IMPLICIT_GENERIC_ANY_BUILTIN = \ + 'Implicit generic "Any". Use "{}" and specify generic parameters' + + # TypeVar + CANNOT_USE_TYPEVAR_AS_EXPRESSION = \ + 'Type variable "{}.{}" cannot be used as an expression' + + # Super + TOO_MANY_ARGS_FOR_SUPER = 'Too many arguments for "super"' + TOO_FEW_ARGS_FOR_SUPER = 'Too few arguments for "super"' + SUPER_WITH_SINGLE_ARG_NOT_SUPPORTED = '"super" with a single argument not supported' + UNSUPPORTED_ARG_1_FOR_SUPER = 'Unsupported argument 1 for "super"' + UNSUPPORTED_ARG_2_FOR_SUPER = 'Unsupported argument 2 for "super"' + SUPER_VARARGS_NOT_SUPPORTED = 'Varargs not supported with "super"' + SUPER_POSITIONAL_ARGS_REQUIRED = '"super" only accepts positional arguments' + SUPER_ARG_2_NOT_INSTANCE_OF_ARG_1 = \ + 'Argument 2 for "super" not an instance of argument 1' + SUPER_OUTSIDE_OF_METHOD_NOT_SUPPORTED = \ + 'super() outside of a method is not supported' + SUPER_ENCLOSING_POSITIONAL_ARGS_REQUIRED = \ + 'super() requires one or more positional arguments in enclosing function' + + # Self-type + MISSING_OR_INVALID_SELF_TYPE = \ + "Self argument missing for a non-static method (or an invalid type for self)" + ERASED_SELF_TYPE_NOT_SUPERTYPE = \ + 'The erased type of self "{}" is not a supertype of its class "{}"' + INVALID_SELF_TYPE_OR_EXTRA_ARG = \ + "Invalid type for self, or extra argument type in function annotation" + + # Final + CANNOT_INHERIT_FROM_FINAL = 'Cannot inherit from final class "{}"' + DEPENDENT_FINAL_IN_CLASS_BODY = \ + "Final name declared in class body cannot depend on type variables" + CANNOT_ACCESS_FINAL_INSTANCE_ATTR = \ + 'Cannot access final instance attribute "{}" on class object' + + # ClassVar + CANNOT_OVERRIDE_INSTANCE_VAR = \ + 'Cannot override instance variable (previously declared on base class "{}") with class ' \ + 'variable' + CANNOT_OVERRIDE_CLASS_VAR = \ + 'Cannot override class variable (previously declared on base class "{}") with instance ' \ + 'variable' + + # Protocol + RUNTIME_PROTOCOL_EXPECTED = \ + 'Only @runtime protocols can be used with instance and class checks' + CANNOT_INSTANTIATE_PROTOCOL = 'Cannot instantiate protocol class "{}"' ARG_CONSTRUCTOR_NAMES = { @@ -290,10 +295,18 @@ def report(self, msg: str, context: Optional[Context], severity: str, origin_line=origin.get_line() if origin else None, id=msg_id) - def fail(self, msg: str, context: Optional[Context], file: Optional[str] = None, - origin: Optional[Context] = None) -> None: + def fail(self, msg: ErrorCodes, context: Optional[Context], + format: Optional[Tuple[Any, ...]] = None, + file: Optional[str] = None, origin: Optional[Context] = None) -> None: """Report an error message (unless disabled).""" - self.report(msg, context, 'error', file=file, origin=origin) + # FIXME: remove + if isinstance(msg, str): + val = msg + else: + val = msg.value + if format is not None: + val = val.format(format) + self.report(val, context, 'error', file=file, origin=origin) def note(self, msg: str, context: Context, file: Optional[str] = None, origin: Optional[Context] = None, offset: int = 0) -> None: @@ -704,7 +717,7 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type: msg = '{} (expression has type {}, target has type {})' arg_type_str, callee_type_str = self.format_distinctly(arg_type, callee.arg_types[n - 1]) - self.fail(msg.format(INCOMPATIBLE_TYPES_IN_ASSIGNMENT, + self.fail(msg.format(ErrorCodes.INCOMPATIBLE_TYPES_IN_ASSIGNMENT.value, arg_type_str, callee_type_str), context) return diff --git a/mypy/plugins/default.py b/mypy/plugins/default.py index a4b2eb745c52..98f8fe07e267 100644 --- a/mypy/plugins/default.py +++ b/mypy/plugins/default.py @@ -230,7 +230,7 @@ def typed_dict_pop_callback(ctx: MethodContext) -> Type: and len(ctx.arg_types[0]) == 1): key = try_getting_str_literal(ctx.args[0][0], ctx.arg_types[0][0]) if key is None: - ctx.api.fail(messages.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context) + ctx.api.fail(messages.ErrorCodes.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context) return AnyType(TypeOfAny.from_error) if key in ctx.type.required_keys: @@ -276,7 +276,7 @@ def typed_dict_setdefault_callback(ctx: MethodContext) -> Type: and len(ctx.arg_types[0]) == 1): key = try_getting_str_literal(ctx.args[0][0], ctx.arg_types[0][0]) if key is None: - ctx.api.fail(messages.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context) + ctx.api.fail(messages.ErrorCodes.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context) return AnyType(TypeOfAny.from_error) value_type = ctx.type.items.get(key) @@ -301,7 +301,7 @@ def typed_dict_delitem_callback(ctx: MethodContext) -> Type: and len(ctx.arg_types[0]) == 1): key = try_getting_str_literal(ctx.args[0][0], ctx.arg_types[0][0]) if key is None: - ctx.api.fail(messages.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context) + ctx.api.fail(messages.ErrorCodes.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context) return AnyType(TypeOfAny.from_error) if key in ctx.type.required_keys: diff --git a/mypy/semanal.py b/mypy/semanal.py index 58794decef4e..5bd73c558ed9 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -62,7 +62,7 @@ from mypy.typevars import fill_typevars from mypy.visitor import NodeVisitor from mypy.errors import Errors, report_internal_error -from mypy.messages import CANNOT_ASSIGN_TO_TYPE, MessageBuilder +from mypy.messages import ErrorCodes, MessageBuilder from mypy.types import ( FunctionLike, UnboundType, TypeVarDef, TupleType, UnionType, StarType, function_type, CallableType, Overloaded, Instance, Type, AnyType, LiteralType, LiteralValue, @@ -2254,7 +2254,7 @@ def check_lvalue_validity(self, node: Union[Expression, SymbolNode, None], if isinstance(node, TypeVarExpr): self.fail('Invalid assignment target', ctx) elif isinstance(node, TypeInfo): - self.fail(CANNOT_ASSIGN_TO_TYPE, ctx) + self.fail(ErrorCodes.CANNOT_ASSIGN_TO_TYPE, ctx) def store_declared_types(self, lvalue: Lvalue, typ: Type) -> None: if isinstance(typ, StarType) and not isinstance(lvalue, StarExpr): diff --git a/mypy/semanal_enum.py b/mypy/semanal_enum.py index 89242136d078..1174537a370c 100644 --- a/mypy/semanal_enum.py +++ b/mypy/semanal_enum.py @@ -12,6 +12,7 @@ ) from mypy.semanal_shared import SemanticAnalyzerInterface from mypy.options import Options +from mypy.messages import ErrorCodes class EnumCallAnalyzer: @@ -157,5 +158,5 @@ def fail_enum_call_arg(self, message: str, # Helpers - def fail(self, msg: str, ctx: Context) -> None: + def fail(self, msg: ErrorCodes, ctx: Context) -> None: self.api.fail(msg, ctx) diff --git a/mypy/semanal_newtype.py b/mypy/semanal_newtype.py index 034dea1f6403..d989e0e43275 100644 --- a/mypy/semanal_newtype.py +++ b/mypy/semanal_newtype.py @@ -14,7 +14,7 @@ from mypy.options import Options from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError from mypy.typeanal import check_for_explicit_any, has_any_from_unimported_type -from mypy.messages import MessageBuilder +from mypy.messages import MessageBuilder, ErrorCodes class NewTypeAnalyzer: @@ -152,5 +152,5 @@ def build_newtype_typeinfo(self, name: str, old_type: Type, base_type: Instance) def make_argument(self, name: str, type: Type) -> Argument: return Argument(Var(name), type, None, ARG_POS) - def fail(self, msg: str, ctx: Context) -> None: + def fail(self, msg: ErrorCodes, ctx: Context) -> None: self.api.fail(msg, ctx) diff --git a/mypy/semanal_pass3.py b/mypy/semanal_pass3.py index 3862d84e1039..2d71d8ebfb50 100644 --- a/mypy/semanal_pass3.py +++ b/mypy/semanal_pass3.py @@ -501,7 +501,7 @@ def check_for_omitted_generics(self, typ: Type) -> None: for t in collect_any_types(typ): if t.type_of_any == TypeOfAny.from_omitted_generics: - self.fail(messages.BARE_GENERIC, t) + self.fail(messages.ErrorCodes.BARE_GENERIC, t) def lookup_qualified(self, name: str, ctx: Context, suppress_errors: bool = False) -> Optional[SymbolTableNode]: @@ -514,7 +514,7 @@ def dereference_module_cross_ref( self, node: Optional[SymbolTableNode]) -> Optional[SymbolTableNode]: return self.sem.dereference_module_cross_ref(node) - def fail(self, msg: str, ctx: Context, serious: bool = False, *, + def fail(self, msg: messages.ErrorCodes, ctx: Context, serious: bool = False, *, blocker: bool = False) -> None: self.sem.fail(msg, ctx, serious, blocker=blocker) @@ -780,5 +780,5 @@ def check_type_var_values(self, type: TypeInfo, actuals: List[Type], arg_name: s else: class_name = '"{}"'.format(type.name()) actual_type_name = '"{}"'.format(actual.type.name()) - self.fail(messages.INCOMPATIBLE_TYPEVAR_VALUE.format( + self.fail(messages.ErrorCodes.INCOMPATIBLE_TYPEVAR_VALUE.format( arg_name, class_name, actual_type_name), context) diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index 4d8994e2084e..fb1855a4538f 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -10,6 +10,7 @@ from mypy.util import correct_relative_import from mypy.types import Type, FunctionLike, Instance from mypy.tvar_scope import TypeVarScope +from mypy.messages import ErrorCodes MYPY = False if False: @@ -43,7 +44,7 @@ def lookup_fully_qualified(self, name: str) -> SymbolTableNode: raise NotImplementedError @abstractmethod - def fail(self, msg: str, ctx: Context, serious: bool = False, *, + def fail(self, msg: ErrorCodes, ctx: Context, serious: bool = False, *, blocker: bool = False) -> None: raise NotImplementedError diff --git a/mypy/typeanal.py b/mypy/typeanal.py index f6b36782b231..dfcddf5e4515 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -1,7 +1,7 @@ """Semantic analysis of types""" from collections import OrderedDict -from typing import Callable, List, Optional, Set, Tuple, Iterator, TypeVar, Iterable, Dict +from typing import Any, Callable, List, Optional, Set, Tuple, Iterator, TypeVar, Iterable, Dict from itertools import chain @@ -273,7 +273,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt if len(t.args) == 0 and not t.empty_tuple_index: # Bare 'Tuple' is same as 'tuple' if self.options.disallow_any_generics and not self.is_typeshed_stub: - self.fail(messages.BARE_GENERIC, t) + self.fail(messages.ErrorCodes.BARE_GENERIC, t) return self.named_type('builtins.tuple', line=t.line, column=t.column) if len(t.args) == 2 and isinstance(t.args[1], EllipsisType): # Tuple[T, ...] (uniform, variable-length tuple) @@ -341,7 +341,7 @@ def analyze_unbound_type_with_type_info(self, t: UnboundType, info: TypeInfo) -> # in the third pass. if not self.is_typeshed_stub and info.fullname() in nongen_builtins: alternative = nongen_builtins[info.fullname()] - self.fail(messages.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative), t) + self.fail(messages.ErrorCodes.IMPLICIT_GENERIC_ANY_BUILTIN, t, (alternative,)) any_type = AnyType(TypeOfAny.from_error, line=t.line) else: any_type = AnyType(TypeOfAny.from_omitted_generics, line=t.line) @@ -717,7 +717,8 @@ def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> Optional[L def analyze_type(self, t: Type) -> Type: return t.accept(self) - def fail(self, msg: str, ctx: Context) -> None: + def fail(self, msg: messages.ErrorCodes, ctx: Context, + format: Optional[Tuple[Any, ...]] = None) -> None: self.fail_func(msg, ctx) @contextmanager From f721e2d3d9fcc7b022db1504d433720a87d3fe97 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Tue, 8 Jan 2019 22:16:15 -0800 Subject: [PATCH 11/11] Add id tracking for Enum messages --- mypy/messages.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index e37861a157d4..c61d513595db 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -244,7 +244,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile]) -> None: @classmethod def get_message_ids(cls) -> Set[str]: - return _message_ids + return _message_ids.union(x.name for x in ErrorCodes) def active_message_id(self) -> Optional[str]: return self.active_msg_ids[-1] if len(self.active_msg_ids) else None @@ -299,14 +299,16 @@ def fail(self, msg: ErrorCodes, context: Optional[Context], format: Optional[Tuple[Any, ...]] = None, file: Optional[str] = None, origin: Optional[Context] = None) -> None: """Report an error message (unless disabled).""" - # FIXME: remove + # FIXME: remove once conversion to Enum is complete if isinstance(msg, str): val = msg + msg_id = None else: val = msg.value + msg_id = msg.name if format is not None: val = val.format(format) - self.report(val, context, 'error', file=file, origin=origin) + self.report(val, context, 'error', file=file, origin=origin, id=msg_id) def note(self, msg: str, context: Context, file: Optional[str] = None, origin: Optional[Context] = None, offset: int = 0) -> None: