Repository: python/mypy

Issue:
# type: ignore doesn't fully work
In a code like this:

``` python
logger.info("xxx: %s", pprint.pformat(retval, compact=True))
```

I get this type check error.

```
$ mypy -s -p somepackage
somepackage/somemodule.py: note: In function "foobar":
somepackage/somemodule:438: error: Unexpected keyword argument "compact" for "pformat"
env/lib/mypy/typeshed/stdlib/3/pprint.pyi:7: note: "pformat" defined here
$
```

Yes, I know, a bug in typeshed.

However, even if I want to suppress it, I can't:

``` python
logger.info("xxx: %s", pprint.pformat(retval, compact=True))  # type: ignore
```

then:

```
$ mypy -s -p somepackage
somepackage/somemodule.py: note: In function "foobar":
env/lib/mypy/typeshed/stdlib/3/pprint.pyi:7: note: "pformat" defined here
$
```

So, I still get printed errors and still it returns exit code 1 denoting an error.   I should be able to ignore this "error", but mypy (0.4.4) doesn't let me.



Discussion:
Oh, good one! This shouldn't be too hard to suppress.


Resolving patch:
diff --git a/mypy/errors.py b/mypy/errors.py
--- a/mypy/errors.py
+++ b/mypy/errors.py
@@ -47,7 +47,8 @@ class ErrorInfo:
 
     def __init__(self, import_ctx: List[Tuple[str, int]], file: str, typ: str,
                  function_or_member: str, line: int, column: int, severity: str,
-                 message: str, blocker: bool, only_once: bool) -> None:
+                 message: str, blocker: bool, only_once: bool,
+                 origin: Tuple[str, int] = None) -> None:
         self.import_ctx = import_ctx
         self.file = file
         self.type = typ
@@ -58,6 +59,7 @@ def __init__(self, import_ctx: List[Tuple[str, int]], file: str, typ: str,
         self.message = message
         self.blocker = blocker
         self.only_once = only_once
+        self.origin = origin or (file, line)
 
 
 class Errors:
@@ -168,7 +170,8 @@ def set_import_context(self, ctx: List[Tuple[str, int]]) -> None:
         self.import_ctx = ctx[:]
 
     def report(self, line: int, column: int, message: str, blocker: bool = False,
-               severity: str = 'error', file: str = None, only_once: bool = False) -> None:
+               severity: str = 'error', file: str = None, only_once: bool = False,
+               origin_line: int = None) -> None:
         """Report message at the given line using the current error context.
 
         Args:
@@ -178,6 +181,7 @@ def report(self, line: int, column: int, message: str, blocker: bool = False,
             severity: 'error', 'note' or 'warning'
             file: if non-None, override current file as context
             only_once: if True, only report this exact message once per build
+            origin_line: if non-None, override current context as origin
         """
         type = self.type_name[-1]
         if len(self.function_or_member) > 2:
@@ -186,15 +190,16 @@ def report(self, line: int, column: int, message: str, blocker: bool = False,
             file = self.file
         info = ErrorInfo(self.import_context(), file, type,
                          self.function_or_member[-1], line, column, severity, message,
-                         blocker, only_once)
+                         blocker, only_once,
+                         origin=(self.file, origin_line) if origin_line else None)
         self.add_error_info(info)
 
     def add_error_info(self, info: ErrorInfo) -> None:
-        if (info.file in self.ignored_lines and
-                info.line in self.ignored_lines[info.file] and
-                not info.blocker):
+        (file, line) = info.origin
+        if (file in self.ignored_lines and line in self.ignored_lines[file]
+                and not info.blocker):
             # Annotation requests us to ignore all errors on this line.
-            self.used_ignored_lines[info.file].add(info.line)
+            self.used_ignored_lines[file].add(line)
             return
         if info.only_once:
             if info.message in self.only_once_messages:
diff --git a/mypy/messages.py b/mypy/messages.py
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -128,20 +128,24 @@ def enable_errors(self) -> None:
     def is_errors(self) -> bool:
         return self.errors.is_errors()
 
-    def report(self, msg: str, context: Context, severity: str, file: str = None) -> None:
+    def report(self, msg: str, context: Context, severity: str,
+               file: str = None, origin: Context = 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.strip(), severity=severity, file=file)
+                               msg.strip(), severity=severity, file=file,
+                               origin_line=origin.get_line() if origin else None)
 
-    def fail(self, msg: str, context: Context, file: str = None) -> None:
+    def fail(self, msg: str, context: Context, file: str = None,
+             origin: Context = None) -> None:
         """Report an error message (unless disabled)."""
-        self.report(msg, context, 'error', file=file)
+        self.report(msg, context, 'error', file=file, origin=origin)
 
-    def note(self, msg: str, context: Context, file: str = None) -> None:
+    def note(self, msg: str, context: Context, file: str = None,
+             origin: Context = None) -> None:
         """Report an error message (unless disabled)."""
-        self.report(msg, context, 'note', file=file)
+        self.report(msg, context, 'note', file=file, origin=origin)
 
     def format(self, typ: Type, verbosity: int = 0) -> str:
         """Convert a type to a relatively short string that is suitable for error messages.
@@ -532,7 +536,8 @@ def unexpected_keyword_argument(self, callee: CallableType, name: str,
             if fullname is not None and '.' in fullname:
                 module_name = fullname.rsplit('.', 1)[0]
                 path = self.modules[module_name].path
-                self.note('{} defined here'.format(callee.name), callee.definition, file=path)
+                self.note('{} defined here'.format(callee.name), callee.definition,
+                          file=path, origin=context)
 
     def duplicate_argument_value(self, callee: CallableType, index: int,
                                  context: Context) -> None:


Test patch:
diff --git a/test-data/unit/check-ignore.test b/test-data/unit/check-ignore.test
--- a/test-data/unit/check-ignore.test
+++ b/test-data/unit/check-ignore.test
@@ -212,5 +212,12 @@ def f() -> None: pass
 main:3: error: Unexpected keyword argument "kw" for "f"
 tmp/m.py:2: note: "f" defined here
 
+[case testIgnoreUnexpectedKeywordArgument]
+import m
+m.f(kw=1)  # type: ignore
+[file m.py]
+def f() -> None: pass
+[out]
+
 [case testCannotIgnoreBlockingError]
 yield  # type: ignore  # E: 'yield' outside function