Repository: python/mypy

Issue:
"object" not callable (another thorny `partial` issue)
I suspect that this will be closed in favor of #1484, but it seemed that creating a separate issue would be more convenient for tidiness.

I have some code which is yielding a mypy error:

```
error: "object" not callable
```

The code looks similar to this (with the line of the error denoted by `<---`):

```python
from functools import partial
from another_module import another_mutator

TAnyDict = Dict[str, Any]

def mutator1(bar: TAnyDict, baz: TAnyDict) -> TAnyDict:
    # ...
    return bar

def mutator2(bar: TAnyDict, baz: TAnyDict) -> TAnyDict:
    # ...
    return bar

# ... etc, more mutator functions

some_local_var = 'foo'
some_functions = [  # type: List[Callable[[TAnyDict, TAnyDict], TAnyDict]] 
    mutator1,
    mutator2,
    partial(another_mutator, some_local_var),
    mutator3,
    mutator4
]

for d in some_other_dicts:
    res = {}
    for func in some_functions:
        res = func(res, some_dict)  # <--- this is the line that causes the error
    do_something_with(res)
```

It was only after fully writing out an Issue for this that I realized that the core issue was related to lack of `partial` support, rather than an issue with my `Callable` declaration, or a different bug in `mypy`.

Obviously this will become a moot point when `partial` is properly supported, but in the meantime, I would have expected the `mypy` error to occur on the `partial(...)` line within the list, rather than within the `for` loop.


Discussion:
The latest mypy version complains about the example (the type annotation for `some_functions` is on the wrong line):

```
t.py:17: error: misplaced type annotation
```

If the annotation is left out, I can reproduce the issue -- the inferred type of `some_functions` will be `List[object]`. This is because the join of the result of `partial(...)` and a callable is `object`.

The recommended workaround is to use `lambda` or define a function using `def` instead of using `partial` when the latter doesn't work correctly. 

The fix to #797 may also fix this issue.
Maybe mypy should special-case functools.partial?


Resolving patch:
diff --git a/mypy/build.py b/mypy/build.py
--- a/mypy/build.py
+++ b/mypy/build.py
@@ -1114,6 +1114,7 @@ def __init__(self,
                  caller_state: 'State' = None,
                  caller_line: int = 0,
                  ancestor_for: 'State' = None,
+                 root_source: bool = False,
                  ) -> None:
         assert id or path or source is not None, "Neither id, path nor source given"
         self.manager = manager
@@ -1148,6 +1149,7 @@ def __init__(self,
                 # - skip -> don't analyze, make the type Any
                 follow_imports = self.options.follow_imports
                 if (follow_imports != 'normal'
+                    and not root_source  # Honor top-level modules
                     and path.endswith('.py')  # Stubs are always normal
                     and id != 'builtins'  # Builtins is always normal
                     and not (caller_state and
@@ -1519,6 +1521,9 @@ def write_cache(self) -> None:
 def dispatch(sources: List[BuildSource], manager: BuildManager) -> None:
     manager.log("Mypy version %s" % __version__)
     graph = load_graph(sources, manager)
+    if not graph:
+        print("Nothing to do?!")
+        return
     manager.log("Loaded graph with %d nodes" % len(graph))
     if manager.options.dump_graph:
         dump_graph(graph)
@@ -1596,7 +1601,8 @@ def load_graph(sources: List[BuildSource], manager: BuildManager) -> Graph:
     # Seed the graph with the initial root sources.
     for bs in sources:
         try:
-            st = State(id=bs.module, path=bs.path, source=bs.text, manager=manager)
+            st = State(id=bs.module, path=bs.path, source=bs.text, manager=manager,
+                       root_source=True)
         except ModuleNotFound:
             continue
         if st.id in graph:


Test patch: