Repository: python/mypy

Issue:
Better message when inferred variable type conflicts with later use
If you can come up with a better name for this issue, please let me know, this one is poor. The naming difficulty also lead to difficulty searching to see if this already existed, so apologies in advance if this is a duplicate issue.

When running this code through the typechecker:
```python
from typing import Union, Dict, List
def g() -> List[Union[str, int]]:
    x = ['a']
    x += [1]
    return x

def f() -> Dict[str, Union[str, int]]:
    d = {'a': 'a'}
    d['b'] = 1
    return d
```

I get this output:
```
test.py: note: In function "g":
test.py:4: error: List item 0 has incompatible type "int"
test.py:5: error: Incompatible return value type (got List[str], expected List[Union[str, int]])
test.py: note: In function "f":
test.py:9: error: Incompatible types in assignment (expression has type "int", target has type "str")
test.py:10: error: Incompatible return value type (got Dict[str, str], expected Dict[str, Union[str, int]])
```

With both these functions, mypy is assuming the type on assignment, then having type errors due to conflicts with the explicitly declared return type.

Surely there should be a way to correctly infer the original type based on the return type?


Discussion:
Mypy infers types roughly from top to bottom. This usually makes reasoning about inferred types easier and the implementation is also relatively straightforward. However, there are cases like your example this has undesirable results. 

You can add a type annotation to make the error go away:

```py
from typing import Union, Dict, List
def g() -> List[Union[str, int]]:
    x = ['a']  # type: List[Union[str, int]]
    x += [1]
    return x

def f() -> Dict[str, Union[str, int]]:
    d = {'a': 'a'}  # type: Dict[str, Union[str, int]]
    d['b'] = 1
    return d
```

Modifying type inference to take the return type into consideration in cases like this would be hard and would complicate the relatively simple and predictable (once you understand a few principles) model that mypy currently uses. However, it might be useful to detect some cases like this and give a better error message. Potential better message:

```
test.py: note: In function "g":
test.py:3: note: Perhaps a type annotation is missing here? Suggestion: List[Union[str, int]]
test.py:4: error: List item 0 has incompatible type "int"
test.py:5: error: Incompatible return value type (got List[str], expected List[Union[str, int]])
```
As it's unlikely that type inference will change for now, I'll update the title to consider a better error message.
If #2542 and #2543 get merged I've got a not-so-nice PR to make that includes this kind of error message (without the suggestion, though, I'm still working on that)
I will take a look at this.
Should this note only cover the case when the object type conflicts with the return type or should it also cover the following:

```
from typing import List, Any
def g() -> List[Any]:
    x = ['a']
    x += [1]
    return x
```

which currently only outputs

```
test.py:4: error: List item 0 has incompatible type "int"
```

I am planning on proceeding with only enhancing the message when it conflicts with the return and not cover the above case.

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- 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
--- 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 ''


Test patch:
diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test
--- 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]