Repository: python/mypy
Pull request: Add variable name to "Need type annotation for variable" error
fixes #4431 by changing "`need annotation for variable`" messages to "`need to annotation for 'x'`".

Problem statement:
Add variable name to "Need type annotation for variable" error
It would be nice if the error message "Need type annotation for variable" showed the variable name. This helps the user in case the mypy output was from a slightly outdated version of the file, and in general seems helpful/reassuring.

Hints from issue discussion:
This is also useful because if one has:
`a, b = [], []`
mypy only gives
`test.py:1: error: Need type annotation for variable`
#2463 is related.

Commit message: add tests

Patch to review:
diff --git a/mypy/checker.py b/mypy/checker.py
index ba3cb543e70b..8e4af18c17c3 100644
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -1940,13 +1940,13 @@ def infer_variable_type(self, name: Var, lvalue: Lvalue,
             # partial type which will be made more specific later. A partial type
             # gets generated in assignment like 'x = []' where item type is not known.
             if not self.infer_partial_type(name, lvalue, init_type):
-                self.fail(messages.NEED_ANNOTATION_FOR_VAR, context)
+                self.msg.need_annotation_for_var(name, context)
                 self.set_inference_error_fallback_type(name, lvalue, init_type, context)
         elif (isinstance(lvalue, MemberExpr) and self.inferred_attribute_types is not None
               and lvalue.def_var and lvalue.def_var in self.inferred_attribute_types
               and not is_same_type(self.inferred_attribute_types[lvalue.def_var], init_type)):
             # Multiple, inconsistent types inferred for an attribute.
-            self.fail(messages.NEED_ANNOTATION_FOR_VAR, context)
+            self.msg.need_annotation_for_var(name, context)
             name.type = AnyType(TypeOfAny.from_error)
         else:
             # Infer type of the target.
@@ -3101,7 +3101,7 @@ def enter_partial_types(self) -> Iterator[None]:
                     var.type = NoneTyp()
                 else:
                     if var not in self.partial_reported:
-                        self.msg.fail(messages.NEED_ANNOTATION_FOR_VAR, context)
+                        self.msg.need_annotation_for_var(var, context)
                         self.partial_reported.add(var)
                     var.type = AnyType(TypeOfAny.from_error)
 
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
index 833ddf2829aa..1cbf611beca7 100644
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -145,7 +145,7 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type:
                     partial_types = self.chk.find_partial_types(node)
                     if partial_types is not None and not self.chk.current_node_deferred:
                         context = partial_types[node]
-                        self.msg.fail(messages.NEED_ANNOTATION_FOR_VAR, context)
+                        self.msg.need_annotation_for_var(node, context)
                     result = AnyType(TypeOfAny.special_form)
         elif isinstance(node, FuncDef):
             # Reference to a global function.
diff --git a/mypy/checkmember.py b/mypy/checkmember.py
index af2d142335e4..18412c1c2ffe 100644
--- a/mypy/checkmember.py
+++ b/mypy/checkmember.py
@@ -366,7 +366,7 @@ def freeze_type_vars(member_type: Type) -> None:
 
 
 def handle_partial_attribute_type(typ: PartialType, is_lvalue: bool, msg: MessageBuilder,
-                                  context: Context) -> Type:
+								  node: SymbolNode) -> Type:
     if typ.type is None:
         # 'None' partial type. It has a well-defined type -- 'None'.
         # In an lvalue context we want to preserver the knowledge of
@@ -375,7 +375,7 @@ def handle_partial_attribute_type(typ: PartialType, is_lvalue: bool, msg: Messag
             return NoneTyp()
         return typ
     else:
-        msg.fail(messages.NEED_ANNOTATION_FOR_VAR, context)
+        msg.need_annotation_for_var(node, node)
         return AnyType(TypeOfAny.from_error)
 
 
diff --git a/mypy/messages.py b/mypy/messages.py
index 3c13a2c41db1..dd47e5456247 100644
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -25,7 +25,7 @@
 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,
-    ReturnStmt, NameExpr, Var, CONTRAVARIANT, COVARIANT,
+    ReturnStmt, NameExpr, Var, CONTRAVARIANT, COVARIANT, SymbolNode
 )
 
 
@@ -62,7 +62,7 @@
 MUST_HAVE_NONE_RETURN_TYPE = 'The return type of "{}" must be None'
 INVALID_TUPLE_INDEX_TYPE = 'Invalid tuple index type'
 TUPLE_INDEX_OUT_OF_RANGE = 'Tuple index out of range'
-NEED_ANNOTATION_FOR_VAR = 'Need type annotation for variable'
+NEED_ANNOTATION_FOR_VAR = 'Need type annotation for variable \"{}\"'
 ITERABLE_EXPECTED = 'Iterable expected'
 ASYNC_ITERABLE_EXPECTED = 'AsyncIterable expected'
 INVALID_SLICE_INDEX = 'Slice index must be an integer or None'
@@ -962,6 +962,9 @@ def unimported_type_becomes_any(self, prefix: str, typ: Type, ctx: Context) -> N
         self.fail("{} becomes {} due to an unfollowed import".format(prefix, self.format(typ)),
                   ctx)
 
+    def need_annotation_for_var(self, node: SymbolNode, context: Context) -> None:
+        self.fail("Need type annotation for \'{}\'".format(node.name()), context)
+
     def explicit_any(self, ctx: Context) -> None:
         self.fail('Explicit "Any" is not allowed', ctx)
 
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
index 54ecbb523ca3..e656eb2696dc 100644
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -767,7 +767,7 @@ class C:
 x = C.x
 [builtins fixtures/list.pyi]
 [out]
-main:2: error: Need type annotation for variable
+main:2: error: Need type annotation for 'x'
 
 [case testAccessingGenericClassAttribute]
 from typing import Generic, TypeVar
diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test
index 06d3b45601d0..b01e7aa2079e 100644
--- a/test-data/unit/check-expressions.test
+++ b/test-data/unit/check-expressions.test
@@ -1562,7 +1562,7 @@ d5 = dict(a=1, b='') # type: Dict[str, Any]
 
 [case testDictWithoutKeywordArgs]
 from typing import Dict
-d = dict() # E: Need type annotation for variable
+d = dict() # E: Need type annotation for 'd'
 d2 = dict() # type: Dict[int, str]
 dict(undefined) # E: Name 'undefined' is not defined
 [builtins fixtures/dict.pyi]
diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test
index 608752b4f0a1..f5669b58fa5a 100644
--- a/test-data/unit/check-functions.test
+++ b/test-data/unit/check-functions.test
@@ -1391,7 +1391,7 @@ if g(C()):
     def f(x: B) -> B: pass
 
 [case testRedefineFunctionDefinedAsVariableInitializedToEmptyList]
-f = [] # E: Need type annotation for variable
+f = [] # E: Need type annotation for 'f'
 if object():
     def f(): pass # E: Incompatible redefinition
 f()
@@ -2200,7 +2200,7 @@ def make_list() -> List[T]: pass
 
 l: List[int] = make_list()
 
-bad = make_list()  # E: Need type annotation for variable
+bad = make_list()  # E: Need type annotation for 'bad'
 [builtins fixtures/list.pyi]
 
 [case testAnonymousArgumentError]
diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test
index 8b9b4d52afc9..8bcf13828506 100644
--- a/test-data/unit/check-generics.test
+++ b/test-data/unit/check-generics.test
@@ -1000,7 +1000,7 @@ IntNode[int](1, 1)
 IntNode[int](1, 'a')  # E: Argument 2 to "Node" has incompatible type "str"; expected "int"
 
 SameNode = Node[T, T]
-ff = SameNode[T](1, 1)  # E: Need type annotation for variable
+ff = SameNode[T](1, 1)  # E: Need type annotation for 'ff'
 a = SameNode(1, 'x')
 reveal_type(a) # E: Revealed type is '__main__.Node[Any, Any]'
 b = SameNode[int](1, 1)
diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test
index cbfd940d89a4..318e90938790 100644
--- a/test-data/unit/check-inference-context.test
+++ b/test-data/unit/check-inference-context.test
@@ -90,7 +90,7 @@ class B: pass
 from typing import TypeVar, Generic
 T = TypeVar('T')
 def g() -> None:
-    x = f() # E: Need type annotation for variable
+    x = f() # E: Need type annotation for 'x'
 
 def f() -> 'A[T]': pass
 class A(Generic[T]): pass
@@ -384,7 +384,7 @@ class B(A): pass
 [case testLocalVariableInferenceFromEmptyList]
 import typing
 def f() -> None:
-    a = []     # E: Need type annotation for variable
+    a = []     # E: Need type annotation for 'a'
     b = [None]
     c = [B()]
     c = [object()] # E: List item 0 has incompatible type "object"; expected "B"
diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test
index c9bb0fdb05c9..3bcdf2d6d276 100644
--- a/test-data/unit/check-inference.test
+++ b/test-data/unit/check-inference.test
@@ -420,7 +420,7 @@ class A: pass
 a = None # type: A
 
 def ff() -> None:
-    x = f() # E: Need type annotation for variable
+    x = f() # E: Need type annotation for 'x'
     reveal_type(x) # E: Revealed type is 'Any'
 
 g(None) # Ok
@@ -875,7 +875,7 @@ for x in [A()]:
     b = x # E: Incompatible types in assignment (expression has type "A", variable has type "B")
     a = x
 
-for y in []: # E: Need type annotation for variable
+for y in []: # E: Need type annotation for 'y'
     a = y # E: Cannot determine type of 'y'
     reveal_type(y)  # E: Revealed type is 'Any' \
                     # E: Cannot determine type of 'y'
@@ -922,7 +922,8 @@ for x, y in [[A()]]:
     a = x
     a = y
 
-for e, f in [[]]:  # E: Need type annotation for variable
+for e, f in [[]]:  # E: Need type annotation for 'e' \
+                   # E: Need type annotation for 'f'
     reveal_type(e)  # E: Revealed type is 'Any' \
                     # E: Cannot determine type of 'e'
     reveal_type(f)  # E: Revealed type is 'Any' \
@@ -1237,12 +1238,12 @@ a.append(0)  # E: Argument 1 to "append" of "list" has incompatible type "int";
 [out]
 
 [case testInferListInitializedToEmptyAndNotAnnotated]
-a = []  # E: Need type annotation for variable
+a = []  # E: Need type annotation for 'a'
 [builtins fixtures/list.pyi]
 [out]
 
 [case testInferListInitializedToEmptyAndReadBeforeAppend]
-a = []  # E: Need type annotation for variable
+a = []  # E: Need type annotation for 'a'
 if a: pass
 a.xyz
 a.append('')
@@ -1250,7 +1251,7 @@ a.append('')
 [out]
 
 [case testInferListInitializedToEmptyAndIncompleteTypeInAppend]
-a = [] # E: Need type annotation for variable
+a = [] # E: Need type annotation for 'a'
 a.append([])
 a()
 [builtins fixtures/list.pyi]
@@ -1275,7 +1276,7 @@ def f() -> None:
 
 [case testInferListInitializedToEmptyAndNotAnnotatedInFunction]
 def f() -> None:
-    a = []  # E: Need type annotation for variable
+    a = []  # E: Need type annotation for 'a'
 
 def g() -> None: pass
 
@@ -1286,7 +1287,7 @@ a.append(1)
 
 [case testInferListInitializedToEmptyAndReadBeforeAppendInFunction]
 def f() -> None:
-    a = []  # E: Need type annotation for variable
+    a = []  # E: Need type annotation for 'a'
     if a: pass
     a.xyz
     a.append('')
@@ -1303,7 +1304,7 @@ class A:
 
 [case testInferListInitializedToEmptyAndNotAnnotatedInClassBody]
 class A:
-    a = []  # E: Need type annotation for variable
+    a = []  # E: Need type annotation for 'a'
 
 class B:
     a = []
@@ -1323,7 +1324,7 @@ class A:
 [case testInferListInitializedToEmptyAndNotAnnotatedInMethod]
 class A:
     def f(self) -> None:
-        a = []  # E: Need type annotation for variable
+        a = []  # E: Need type annotation for 'a'
 [builtins fixtures/list.pyi]
 [out]
 
@@ -1331,7 +1332,7 @@ class A:
 class A:
     def f(self) -> None:
         # Attributes aren't supported right now.
-        self.a = [] # E: Need type annotation for variable
+        self.a = [] # E: Need type annotation for 'a'
         self.a.append(1)
         self.a.append('')
 [builtins fixtures/list.pyi]
@@ -1342,7 +1343,7 @@ from typing import List
 
 class A:
     def __init__(self) -> None:
-        self.x = [] # E: Need type annotation for variable
+        self.x = [] # E: Need type annotation for 'x'
 
 class B(A):
     @property
@@ -1387,16 +1388,16 @@ a() # E: "Dict[str, int]" not callable
 [out]
 
 [case testInferDictInitializedToEmptyUsingUpdateError]
-a = {}  # E: Need type annotation for variable
+a = {}  # E: Need type annotation for 'a'
 a.update([1, 2])
 a()
 [builtins fixtures/dict.pyi]
 [out]
 
 [case testInferDictInitializedToEmptyAndIncompleteTypeInUpdate]
-a = {} # E: Need type annotation for variable
+a = {} # E: Need type annotation for 'a'
 a[1] = {}
-b = {} # E: Need type annotation for variable
+b = {} # E: Need type annotation for 'b'
 b[{}] = 1
 [builtins fixtures/dict.pyi]
 [out]
@@ -1415,14 +1416,14 @@ def add():
 
 [case testSpecialCaseEmptyListInitialization]
 def f(blocks: Any): # E: Name 'Any' is not defined
-    to_process = [] # E: Need type annotation for variable
+    to_process = [] # E: Need type annotation for 'to_process'
     to_process = list(blocks)
 [builtins fixtures/list.pyi]
 [out]
 
 [case testSpecialCaseEmptyListInitialization2]
 def f(blocks: object):
-    to_process = [] # E: Need type annotation for variable
+    to_process = [] # E: Need type annotation for 'to_process'
     to_process = list(blocks) # E: No overload variant of "list" matches argument types [builtins.object]
 [builtins fixtures/list.pyi]
 [out]
@@ -1478,7 +1479,7 @@ x.append('') # E: Argument 1 to "append" of "list" has incompatible type "str";
 x = None
 if object():
     # Promote from partial None to partial list.
-    x = []  # E: Need type annotation for variable
+    x = []  # E: Need type annotation for 'x'
     x
 [builtins fixtures/list.pyi]
 
@@ -1487,7 +1488,7 @@ def f() -> None:
     x = None
     if object():
         # Promote from partial None to partial list.
-        x = []  # E: Need type annotation for variable
+        x = []  # E: Need type annotation for 'x'
 [builtins fixtures/list.pyi]
 [out]
 
@@ -1496,7 +1497,7 @@ def f() -> None:
 from typing import TypeVar,  Dict
 T = TypeVar('T')
 def f(*x: T) -> Dict[int, T]: pass
-x = None  # E: Need type annotation for variable
+x = None  # E: Need type annotation for 'x'
 if object():
     x = f()
 [builtins fixtures/dict.pyi]
@@ -1573,7 +1574,7 @@ class A:
             pass
 [builtins fixtures/for.pyi]
 [out]
-main:3: error: Need type annotation for variable
+main:3: error: Need type annotation for 'x'
 
 [case testPartialTypeErrorSpecialCase3]
 class A:
@@ -1739,9 +1740,9 @@ o = 1
 
 [case testMultipassAndPartialTypesSpecialCase3]
 def f() -> None:
-    x = {} # E: Need type annotation for variable
+    x = {} # E: Need type annotation for 'x'
     y = o
-    z = {} # E: Need type annotation for variable
+    z = {} # E: Need type annotation for 'z'
 o = 1
 [builtins fixtures/dict.pyi]
 [out]
@@ -1901,7 +1902,7 @@ main:4: error: Unsupported target for indexed assignment
 class C:
     x = None
     def __init__(self) -> None:
-        self.x = []  # E: Need type annotation for variable
+        self.x = []  # E: Need type annotation for 'x'
 [builtins fixtures/list.pyi]
 [out]
 
@@ -1958,7 +1959,7 @@ T = TypeVar('T')
 def f() -> T: pass
 
 class C:
-    x = f() # E: Need type annotation for variable
+    x = f() # E: Need type annotation for 'x'
     def m(self) -> str:
         return 42 # E: Incompatible return value type (got "int", expected "str")
 
@@ -1975,7 +1976,7 @@ T = TypeVar('T')
 def f(x: Optional[T] = None) -> T: pass
 
 class C:
-    x = f() # E: Need type annotation for variable
+    x = f() # E: Need type annotation for 'x'
     def m(self) -> str:
         return 42 # E: Incompatible return value type (got "int", expected "str")
 
@@ -1991,7 +1992,7 @@ T = TypeVar('T')
 def f(x: List[T]) -> T: pass
 
 class C:
-    x = f([]) # E: Need type annotation for variable
+    x = f([]) # E: Need type annotation for 'x'
     def m(self) -> str:
         return 42 # E: Incompatible return value type (got "int", expected "str")
 
diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test
index 5dc4082c216c..8992d3c22379 100644
--- a/test-data/unit/check-tuples.test
+++ b/test-data/unit/check-tuples.test
@@ -380,8 +380,8 @@ d, e = f, g, h = 1, 1 # E: Need more than 2 values to unpack (3 expected)
 [case testAssignmentToStarMissingAnnotation]
 from typing import List
 t = 1, 2
-a, b, *c = 1, 2  # E: Need type annotation for variable
-aa, bb, *cc = t  # E: Need type annotation for variable
+a, b, *c = 1, 2  # E: Need type annotation for 'c'
+aa, bb, *cc = t  # E: Need type annotation for 'cc'
 [builtins fixtures/list.pyi]
 
 [case testAssignmentToStarAnnotation]
@@ -633,7 +633,7 @@ for x in t:
 [case testForLoopOverEmptyTuple]
 import typing
 t = ()
-for x in t: pass  # E: Need type annotation for variable
+for x in t: pass  # E: Need type annotation for 'x'
 [builtins fixtures/for.pyi]
 
 [case testForLoopOverNoneValuedTuple]
@@ -891,7 +891,7 @@ a = (1, [])  # E: Incompatible types in assignment (expression has type "Tuple[i
 [builtins fixtures/tuple.pyi]
 
 [case testTupleWithoutContext]
-a = (1, [])  # E: Need type annotation for variable
+a = (1, [])  # E: Need type annotation for 'a'
 [builtins fixtures/tuple.pyi]
 
 [case testTupleWithUnionContext]
diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test
index 09aef746529d..43ab8f85a7eb 100644
--- a/test-data/unit/check-typevar-values.test
+++ b/test-data/unit/check-typevar-values.test
@@ -328,7 +328,7 @@ from typing import TypeVar, Generic
 X = TypeVar('X', int, str)
 class C(Generic[X]):
     def f(self, x: X) -> None:
-        self.x = x # E: Need type annotation for variable
+        self.x = x # E: Need type annotation for 'x'
 ci: C[int]
 cs: C[str]
 reveal_type(ci.x) # E: Revealed type is 'Any'
@@ -352,7 +352,7 @@ X = TypeVar('X', int, str)
 class C(Generic[X]):
     x: X
     def f(self) -> None:
-        self.y = self.x # E: Need type annotation for variable
+        self.y = self.x # E: Need type annotation for 'y'
 ci: C[int]
 cs: C[str]
 reveal_type(ci.y) # E: Revealed type is 'Any'
@@ -454,8 +454,8 @@ from typing import TypeVar
 T = TypeVar('T', int, str)
 class A:
     def f(self, x: T) -> None:
-        self.x = x # E: Need type annotation for variable
-        self.y = [x] # E: Need type annotation for variable
+        self.x = x # E: Need type annotation for 'x'
+        self.y = [x] # E: Need type annotation for 'y'
         self.z = 1
 reveal_type(A().x)  # E: Revealed type is 'Any'
 reveal_type(A().y)  # E: Revealed type is 'Any'
diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test
index b892757123a3..83e2b68be17b 100644
--- a/test-data/unit/cmdline.test
+++ b/test-data/unit/cmdline.test
@@ -894,7 +894,7 @@ y: List = []  # error
 m.py:3: error: Missing type parameters for generic type
 m.py:5: error: Missing type parameters for generic type
 m.py:6: error: Missing type parameters for generic type
-m.py:8: error: Need type annotation for variable
+m.py:8: error: Need type annotation for 'x'
 m.py:9: error: Missing type parameters for generic type
 
 [case testDisallowAnyGenericsCustomGenericClass]
diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test
index 15da475e4a87..b9c5deb14705 100644
--- a/test-data/unit/fine-grained.test
+++ b/test-data/unit/fine-grained.test
@@ -1293,7 +1293,7 @@ class C(Generic[T]): pass
 [out]
 main:4: error: "object" has no attribute "C"
 ==
-main:4: error: Need type annotation for variable
+main:4: error: Need type annotation for 'x'
 
 [case testPartialTypeInNestedClass]
 import a
@@ -1310,9 +1310,9 @@ def g() -> None: pass
 def g() -> int: pass
 [builtins fixtures/dict.pyi]
 [out]
-main:7: error: Need type annotation for variable
+main:7: error: Need type annotation for 'x'
 ==
-main:7: error: Need type annotation for variable
+main:7: error: Need type annotation for 'x'
 
 [case testRefreshPartialTypeInClass]
 import a
@@ -1327,9 +1327,9 @@ def g() -> None: pass
 def g() -> int: pass
 [builtins fixtures/dict.pyi]
 [out]
-main:5: error: Need type annotation for variable
+main:5: error: Need type annotation for 'x'
 ==
-main:5: error: Need type annotation for variable
+main:5: error: Need type annotation for 'x'
 
 [case testRefreshTryExcept]
 import a


Code review comments:
diff_hunk: @@ -62,7 +62,7 @@
 MUST_HAVE_NONE_RETURN_TYPE = 'The return type of "{}" must be None'
 INVALID_TUPLE_INDEX_TYPE = 'Invalid tuple index type'
 TUPLE_INDEX_OUT_OF_RANGE = 'Tuple index out of range'
-NEED_ANNOTATION_FOR_VAR = 'Need type annotation for variable'
+NEED_ANNOTATION_FOR_VAR = 'Need type annotation for variable \"{}\"'
path: mypy/messages.py
text: @user1:
This is now never used, IIUC and should be removed.

diff_hunk: @@ -962,6 +962,9 @@ def unimported_type_becomes_any(self, prefix: str, typ: Type, ctx: Context) -> N
         self.fail("{} becomes {} due to an unfollowed import".format(prefix, self.format(typ)),
                   ctx)
 
+    def need_annotation_for_var(self, node: SymbolNode, context: Context) -> None:
+        self.fail("Need type annotation for \'{}\'".format(node.name()), context)
path: mypy/messages.py
text: @user1:
You don't need to escape quotes here.