Repository: python/mypy

Issue:
mypy should not follow imports from stubs with --follow-imports=skip
Currently, even with `--follow-imports=skip` imports from stub files are still followed. This means that if stub imports an unanalyzed module, the previously unanalyzed module is actually typechecked.

Consider the following example:
```py
# main.py
from stub import x
```

```py
# stub.pyi
from unanalyzed import x
```

```py
# unanalyzed.py
x: int = 42

x + ''  # error is reported here but it shouldn't be because this file is unanalyzed
```

Currently, if you run mypy the following happens:
```
$ mypy main.py --follow-imports=skip
unanalyzed.py:4: error: Unsupported operand types for + ("int" and "str")
```

I think we should treat imports from stub and non-stub files the same way. Following imports from stubs in silent mode was implemented #1372.

Discovered this bug when investigating an error in Dropbox internal codebases.


Discussion:
Hm, reading through the original discussion in #1364 and #1372 I agree that this is an iffy feature and I'm fine with rolling it back. I don't think this was ever documented -- I don't even recall being aware of this corner case when I refactored `--silent`/`-s` into `--follow-imports=<enum>` and `--ignore-missing-imports`.

Resolving patch:
diff --git a/mypy/build.py b/mypy/build.py
--- a/mypy/build.py
+++ b/mypy/build.py
@@ -1377,12 +1377,9 @@ 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
-                             caller_state.tree and
-                             caller_state.tree.is_stub)):
+                        and not root_source  # Honor top-level modules
+                        and path.endswith('.py')  # Stubs are always normal
+                        and id != 'builtins'):  # Builtins is always normal
                     if follow_imports == 'silent':
                         # Still import it, but silence non-blocker errors.
                         manager.log("Silencing %s (%s)" % (path, id))


Test patch:
diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test
--- a/test-data/unit/check-modules.test
+++ b/test-data/unit/check-modules.test
@@ -955,19 +955,24 @@ tmp/foo/bar.py: note: (Using --follow-imports=error, submodule passed on command
 # cmd: mypy -m main
 # flags: --follow-imports=skip
 [file main.py]
-from stub import x # Permitted
-from other import y # Disallowed
-x + '' # Error here
-y + '' # But not here
+from stub import x, z  # Followed
+from other import y  # Not followed
+x + ''  # No error here
+y + ''  # No error here
+z + ''  # Error here
 [file stub.pyi]
-from non_stub import x
+from non_stub import x  # this import is not followed
+
+z = 42
 [file non_stub.py]
 x = 42
+
+x + ''  # no error because file is not analyzed
 [file other.py]
 y = 42
 [builtins fixtures/module.pyi]
 [out]
-tmp/main.py:3: error: Unsupported left operand type for + ("int")
+tmp/main.py:5: error: Unsupported left operand type for + ("int")
 
 [case testSilentSubmoduleImport]
 # cmd: mypy -m foo