Repository: python/mypy

Issue:
Attributes are not reprocessed in fine grained mode with `# type: ignore`
This test case currently fails:
```
[case testIgnoredAttrReprocessedModule]
import a
[file a.py]
import b
x = b.x  # type: ignore
y: int = x
[file b.py]
import c
[file b.py.2]
import c
x = c.x
[file c.py]
x: str
[out]
==
a.py:3: error: Incompatible types in assignment (expression has type "str", variable has type "int")
```
The problem is that since there are no errors in `a.py` on the first run it is not reprocessed, and since `b.x` is invalid at the first run no deps of the form `<c.x> -> <a>` or `<b.x> -> <a>` are generated. Therefore `a.py` is never reprocessed, and the error is not caught, here is the debug output:
```
<a.x>
    a
<a.y>
    a
<a>
    __main__
<b>
    a
<c.x>
    c
<c>
    b

==== update 'b' ====
--- update single 'b' ---
triggered: ['<b.x>', '<b[wildcard]>']
triggered: []
update once: b in 0.001s - 0 left
Expected:
  ==
  a.py:3: error: Incompatible types in assignment (expression has type "str", variable has type "int") (diff)
Actual:
  ==
```


Discussion:
Looks like there should a dependency `<b.x> -> a` that apparently is missing?
Another thing to pay attention here is redefinitions of names by imports.

Resolving patch:
diff --git a/mypy/server/deps.py b/mypy/server/deps.py
--- a/mypy/server/deps.py
+++ b/mypy/server/deps.py
@@ -599,11 +599,15 @@ def visit_member_expr(self, e: MemberExpr) -> None:
             # Reference to a module attribute
             self.process_global_ref_expr(e)
         else:
-            # Reference to a non-module attribute
+            # Reference to a non-module (or missing) attribute
             if e.expr not in self.type_map:
                 # No type available -- this happens for unreachable code. Since it's unreachable,
                 # it wasn't type checked and we don't need to generate dependencies.
                 return
+            if isinstance(e.expr, RefExpr) and isinstance(e.expr.node, MypyFile):
+                # Special case: reference to a missing module attribute.
+                self.add_dependency(make_trigger(e.expr.node.fullname() + '.' + e.name))
+                return
             typ = self.type_map[e.expr]
             self.add_attribute_dependency(typ, e.name)
             if self.use_logical_deps() and isinstance(typ, AnyType):


Test patch:
diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test
--- a/test-data/unit/deps.test
+++ b/test-data/unit/deps.test
@@ -827,6 +827,47 @@ def f(x: A) -> None:
 <m.A> -> <m.f>, m.f
 <p.b.A> -> m
 
+[case testIgnoredMissingModuleAttribute]
+import a
+a.x  # type: ignore
+
+import b.c
+b.c.x  # type: ignore
+
+from b import c
+c.y  # type: ignore
+[file a.py]
+[file b/__init__.py]
+[file b/c.py]
+[builtins fixtures/module.pyi]
+[out]
+<a.x> -> m
+<a> -> m
+<b.c.x> -> m
+<b.c.y> -> m
+<b.c> -> m
+<b> -> m
+
+[case testIgnoredMissingInstanceAttribute]
+from a import C
+C().x  # type: ignore
+[file a.py]
+class C: pass
+[out]
+<a.C.__init__> -> m
+<a.C.__new__> -> m
+<a.C.x> -> m
+<a.C> -> m
+
+[case testIgnoredMissingClassAttribute]
+from a import C
+C.x  # type: ignore
+[file a.py]
+class C: pass
+[out]
+<a.C.x> -> m
+<a.C> -> m
+
 [case testDepsFromOverloadMod]
 # __dump_all__
 import mod
diff --git a/test-data/unit/fine-grained-modules.test b/test-data/unit/fine-grained-modules.test
--- a/test-data/unit/fine-grained-modules.test
+++ b/test-data/unit/fine-grained-modules.test
@@ -2125,3 +2125,20 @@ def f(x: str) -> None: pass
 [out]
 ==
 main.py:2: error: Argument 1 to "f" has incompatible type "int"; expected "str"
+
+[case testIgnoredAttrReprocessedModule]
+import a
+[file a.py]
+import b
+x = b.x  # type: ignore
+y: int = x
+[file b.py]
+import c
+[file b.py.2]
+import c
+x = c.x
+[file c.py]
+x: str
+[out]
+==
+a.py:3: error: Incompatible types in assignment (expression has type "str", variable has type "int")