diff --git a/mypy/checker.py b/mypy/checker.py index 870c561852b6..59746059a573 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -36,7 +36,7 @@ true_only, false_only, function_type, is_named_instance, union_items ) from mypy.sametypes import is_same_type, is_same_types -from mypy.messages import MessageBuilder +from mypy.messages import MessageBuilder, make_inferred_type_note import mypy.checkexpr from mypy.checkmember import map_type_from_supertype, bind_self, erase_to_bound from mypy import messages @@ -2313,15 +2313,20 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context, if self.should_suppress_optional_error([subtype]): return False extra_info = [] # type: List[str] + note_msg = '' if subtype_label is not None or supertype_label is not None: subtype_str, supertype_str = self.msg.format_distinctly(subtype, supertype) if subtype_label is not None: extra_info.append(subtype_label + ' ' + subtype_str) if supertype_label is not None: 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) + ')' self.fail(msg, context) + if note_msg: + self.note(note_msg, context) return False def contains_none(self, t: Type) -> bool: diff --git a/mypy/messages.py b/mypy/messages.py index 5c2f5d16fdc7..f4e40b680dfa 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -17,7 +17,8 @@ ) from mypy.nodes import ( TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases, - ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2 + ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2, + ReturnStmt, NameExpr, Var ) @@ -977,3 +978,31 @@ def pretty_or(args: List[str]) -> str: if len(quoted) == 2: return "{} or {}".format(quoted[0], quoted[1]) return ", ".join(quoted[:-1]) + ", or " + quoted[-1] + + +def make_inferred_type_note(context: Context, subtype: Type, + supertype: Type, supertype_str: str) -> str: + """Explain that the user may have forgotten to type a variable. + + The user does not expect an error if the inferred container type is the same as the return + type of a function and the argument type(s) are a subtype of the argument type(s) of the + return type. This note suggests that they add a type annotation with the return type instead + of relying on the inferred type. + """ + from mypy.subtypes import is_subtype + if (isinstance(subtype, Instance) and + isinstance(supertype, Instance) and + subtype.type.fullname() == supertype.type.fullname() and + subtype.args and + supertype.args and + isinstance(context, ReturnStmt) and + isinstance(context.expr, NameExpr) and + isinstance(context.expr.node, Var) and + context.expr.node.is_inferred): + for subtype_arg, supertype_arg in zip(subtype.args, supertype.args): + if not is_subtype(subtype_arg, supertype_arg): + return '' + var_name = context.expr.name + return 'Perhaps you need a type annotation for "{}"? Suggestion: {}'.format( + var_name, supertype_str) + return '' diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 030fe788c5e2..6c93a47bf676 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2067,3 +2067,59 @@ def some_method(self: badtype): pass # E: Name 'badtype' is not defined def fn( a: badtype) -> None: # E: Name 'badtype' is not defined pass + +[case testInferredTypeSubTypeOfReturnType] +from typing import Union, Dict, List +def f() -> List[Union[str, int]]: + x = ['a'] + return x # E: Incompatible return value type (got List[str], expected List[Union[str, int]]) \ +# N: Perhaps you need a type annotation for "x"? Suggestion: List[Union[str, int]] + +def g() -> Dict[str, Union[str, int]]: + x = {'a': 'a'} + return x # E: Incompatible return value type (got Dict[str, str], expected Dict[str, Union[str, int]]) \ +# N: Perhaps you need a type annotation for "x"? Suggestion: Dict[str, Union[str, int]] + +def h() -> Dict[Union[str, int], str]: + x = {'a': 'a'} + return x # E: Incompatible return value type (got Dict[str, str], expected Dict[Union[str, int], str]) \ +# N: Perhaps you need a type annotation for "x"? Suggestion: Dict[Union[str, int], str] + +def i() -> List[Union[int, float]]: + x: List[int] = [1] + return x # E: Incompatible return value type (got List[int], expected List[Union[int, float]]) \ +# N: Perhaps you need a type annotation for "x"? Suggestion: List[Union[int, float]] + +[builtins fixtures/dict.pyi] + +[case testInferredTypeNotSubTypeOfReturnType] +from typing import Union, List +def f() -> List[Union[int, float]]: + x = ['a'] + return x # E: Incompatible return value type (got List[str], expected List[Union[int, float]]) + +def g() -> List[Union[str, int]]: + x = ('a', 2) + return x # E: Incompatible return value type (got "Tuple[str, int]", expected List[Union[str, int]]) + +[builtins fixtures/list.pyi] + +[case testInferredTypeIsObjectMismatch] +from typing import Union, Dict, List +def f() -> Dict[str, Union[str, int]]: + x = {'a': 'a', 'b': 2} + return x # E: Incompatible return value type (got Dict[str, object], expected Dict[str, Union[str, int]]) + +def g() -> Dict[str, Union[str, int]]: + x: Dict[str, Union[str, int]] = {'a': 'a', 'b': 2} + return x + +def h() -> List[Union[str, int]]: + x = ['a', 2] + return x # E: Incompatible return value type (got List[object], expected List[Union[str, int]]) + +def i() -> List[Union[str, int]]: + x: List[Union[str, int]] = ['a', 2] + return x + +[builtins fixtures/dict.pyi]