Repository: python/mypy

Issue:
"INTERNAL ERROR" when an unexpected keyword argument is given to subprocess.Popen
The file `example.py` includes the following:

```
import subprocess
subprocess.Popen(example=True)
```

There is no `mypy` config file.

The following error is given:

```
(e2e) Adam@Adams-MacBook-Pro ~/Desktop> mypy example.py --show-traceback
example.py:2: error: Unexpected keyword argument "example" for "Popen"
example.py:2: error: INTERNAL ERROR -- please report a bug at https://github.com/python/mypy/issues version: 0.511
Traceback (most recent call last):
  File "/Users/Adam/.virtualenvs/e2e/bin/mypy", line 6, in <module>
    main(__file__)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/main.py", line 46, in main
    res = type_check_only(sources, bin_dir, options)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/main.py", line 93, in type_check_only
    options=options)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/build.py", line 188, in build
    graph = dispatch(sources, manager)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/build.py", line 1595, in dispatch
    process_graph(graph, manager)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/build.py", line 1838, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/build.py", line 1937, in process_stale_scc
    graph[id].type_check_first_pass()
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/build.py", line 1510, in type_check_first_pass
    self.type_checker.check_first_pass()
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/checker.py", line 177, in check_first_pass
    self.accept(d)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/checker.py", line 264, in accept
    stmt.accept(self)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/nodes.py", line 825, in accept
    return visitor.visit_expression_stmt(self)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/checker.py", line 1823, in visit_expression_stmt
    self.expr_checker.accept(s.expr, allow_none_return=True)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/checkexpr.py", line 2054, in accept
    typ = self.visit_call_expr(node, allow_none_return=True)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/checkexpr.py", line 201, in visit_call_expr
    ret_type = self.check_call_expr_with_callee_type(callee_type, e)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/checkexpr.py", line 341, in check_call_expr_with_callee_type
    e.arg_names, callable_node=e.callee)[0]
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/checkexpr.py", line 396, in check_call
    arg_names, formal_to_actual, context, self.msg)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/checkexpr.py", line 759, in check_argument_count
    callee, actual_names[i], context)
  File "/Users/Adam/.virtualenvs/e2e/lib/python3.6/site-packages/mypy/messages.py", line 619, in unexpected_keyword_argument
    path = self.modules[module_name].path
KeyError: 'subprocess.Popen'
example.py:2: note: use --pdb to drop into pdb
```

Further system info:

```
(e2e) Adam@Adams-MacBook-Pro ~/Desktop> mypy --version
mypy 0.511
(e2e) Adam@Adams-MacBook-Pro ~/Desktop> python
Python 3.6.0 (default, Mar  4 2017, 12:32:34)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
```


Discussion:
Confirmed. There seems to be some confusing in the code there about how to determine the module name from the full function name. This is probably best fixed by trying different prefixes until one is found.

Resolving patch:
diff --git a/mypy/messages.py b/mypy/messages.py
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -612,13 +612,10 @@ def unexpected_keyword_argument(self, callee: CallableType, name: str,
         if callee.name:
             msg += ' for {}'.format(callee.name)
         self.fail(msg, context)
-        if callee.definition:
-            fullname = callee.definition.fullname()
-            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, origin=context)
+        module = find_defining_module(self.modules, callee)
+        if module:
+            self.note('{} defined here'.format(callee.name), callee.definition,
+                      file=module.path, origin=context)
 
     def duplicate_argument_value(self, callee: CallableType, index: int,
                                  context: Context) -> None:
@@ -946,6 +943,21 @@ def callable_name(type: CallableType) -> str:
         return 'function'
 
 
+def find_defining_module(modules: Dict[str, MypyFile], typ: CallableType) -> MypyFile:
+    if not typ.definition:
+        return None
+    fullname = typ.definition.fullname()
+    if fullname is not None and '.' in fullname:
+        for i in range(fullname.count('.')):
+            module_name = fullname.rsplit('.', i + 1)[0]
+            try:
+                return modules[module_name]
+            except KeyError:
+                pass
+        assert False, "Couldn't determine module from CallableType"
+    return None
+
+
 def temp_message_builder() -> MessageBuilder:
     """Return a message builder usable for throwaway errors (which may not format properly)."""
     return MessageBuilder(Errors(), {})
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -328,6 +328,8 @@ def visit_func_def(self, defn: FuncDef) -> None:
             self.function_stack.append(defn)
             # First phase of analysis for function.
             self.errors.push_function(defn.name())
+            if not defn._fullname:
+                defn._fullname = self.qualified_name(defn.name())
             if defn.type:
                 assert isinstance(defn.type, CallableType)
                 self.update_function_type_variables(defn.type, defn)


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
@@ -1932,7 +1932,7 @@ main:3: note: "f" defined here
 
 [case testMagicMethodPositionalOnlyArg]
 class A(object):
-    def __eq__(self, other) -> bool: return True # We are all equal.
+    def __eq__(self, other) -> bool: return True # We are all equal.  # N: "__eq__" of "A" defined here
 
 a = A()
 a.__eq__(a)
@@ -1944,7 +1944,7 @@ a.__eq__(other=a) # E: Unexpected keyword argument "other" for "__eq__" of "A"
 
 
 class A(object):
-    def __eq__(self, other) -> bool: return True # We are all equal.
+    def __eq__(self, other) -> bool: return True # We are all equal.  # N: "__eq__" of "A" defined here
 
 a = A()
 a.__eq__(a)
diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test
--- a/test-data/unit/check-kwargs.test
+++ b/test-data/unit/check-kwargs.test
@@ -320,7 +320,7 @@ f(y=0) # E: Unexpected keyword argument "y" for "f"
 [case testKeywordArgumentAndCommentSignature2]
 import typing
 class A:
-    def f(self, x): # type: (int) -> str
+    def f(self, x): # type: (int) -> str  # N: "f" of "A" defined here
         pass
 A().f(x='') # E: Argument 1 to "f" of "A" has incompatible type "str"; expected "int"
 A().f(x=0)
@@ -359,3 +359,18 @@ f(**c) # E: Keywords must be strings
 def f(**k): pass
 f(*(1, 2))  # E: Too many arguments for "f"
 [builtins fixtures/dict.pyi]
+
+[case testUnexpectedMethodKwargInNestedClass]
+class A:
+    class B:
+        def __init__(self) -> None:  # N: "B" defined here
+            pass
+A.B(x=1)  # E: Unexpected keyword argument "x" for "B"
+
+[case testUnexpectedMethodKwargFromOtherModule]
+import m
+m.A(x=1)  # E: Unexpected keyword argument "x" for "A"
+[file m.py]
+class A:
+    def __init__(self) -> None:  # N: "A" defined here
+        pass
diff --git a/test-data/unit/semanal-classes.test b/test-data/unit/semanal-classes.test
--- a/test-data/unit/semanal-classes.test
+++ b/test-data/unit/semanal-classes.test
@@ -270,7 +270,7 @@ MypyFile:1(
         PassStmt:2()))
     AssignmentStmt:3(
       NameExpr(g* [m])
-      NameExpr(f [m]))))
+      NameExpr(f [__main__.A.f]))))
 
 [case testIfStatementInClassBody]
 class A: