Skip to content

Commit c0b3cf2

Browse files
committed
Restore all_are_submodules import logic as workaround for #4498
The logic in build to determine what imported modules are depended on used to elide dependencies to m in `from m import a, b, c` if all of a, b, c were submodules. This was removed in #4910 because it seemed like it ought not be necessary (and that semantically there *was* a dependency), and early versions of #4910 depended removing it. The addition of this dependency, though, can cause cycles that wouldn't be there otherwise, which can cause #4498 (invalid type when using aliases in import cycles) to trip when it otherwise wouldn't. We've seen this once in a bug report and once internally, so restore the `all_are_submodules` logic in avoid triggering #4498 in these cases. Fixes #5015
1 parent bb13ecc commit c0b3cf2

File tree

2 files changed

+34
-6
lines changed

2 files changed

+34
-6
lines changed

mypy/build.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,6 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str:
696696
blocker=True)
697697

698698
return new_id
699-
700699
res = [] # type: List[Tuple[int, str, int]]
701700
for imp in file.imports:
702701
if not imp.is_unreachable:
@@ -713,17 +712,24 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str:
713712
elif isinstance(imp, ImportFrom):
714713
cur_id = correct_rel_imp(imp)
715714
pos = len(res)
715+
all_are_submodules = True
716716
# Also add any imported names that are submodules.
717717
pri = import_priority(imp, PRI_MED)
718718
for name, __ in imp.names:
719719
sub_id = cur_id + '.' + name
720720
if self.is_module(sub_id):
721721
res.append((pri, sub_id, imp.line))
722-
# Add cur_id as a dependency, even if all of the
723-
# imports are submodules. Processing import from will try
724-
# to look through cur_id, so we should depend on it.
725-
pri = import_priority(imp, PRI_HIGH)
726-
res.insert(pos, ((pri, cur_id, imp.line)))
722+
else:
723+
all_are_submodules = False
724+
# If all imported names are submodules, don't add
725+
# cur_id as a dependency. This is basically a workaround
726+
# of bugs in cycle handling (#4498).
727+
# Otherwise (i.e., if at least one imported name
728+
# isn't a submodule) cur_id is also a dependency,
729+
# and we should insert it *before* any submodules.
730+
if not all_are_submodules:
731+
pri = import_priority(imp, PRI_HIGH)
732+
res.insert(pos, ((pri, cur_id, imp.line)))
727733
elif isinstance(imp, ImportAll):
728734
pri = import_priority(imp, PRI_HIGH)
729735
res.append((pri, correct_rel_imp(imp), imp.line))

test-data/unit/check-modules.test

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,28 @@ from foo import bar
10011001
[file foo/bar.py]
10021002
pass
10031003

1004+
[case testImportReExportFromChildrentInCycle]
1005+
# cmd: mypy -m project.root project.study.a project.neighbor
1006+
[file project/__init__.py]
1007+
from project.study import CustomType
1008+
x = 10
1009+
[file project/root.py]
1010+
[file project/study/__init__.py]
1011+
from project.study.a import CustomType
1012+
[file project/study/a.py]
1013+
from project import root
1014+
# TODO (#4498): This test is basically testing the `all_are_submodules` logic
1015+
# in build, which skips generating a dependenecy to a module if
1016+
# everything in it is a submodule. But that is still all just a
1017+
# workaround for bugs in cycle handling. If we uncomment the next
1018+
# line, we'll still break:
1019+
# from project import x
1020+
CustomType = str
1021+
[file project/neighbor/__init__.py]
1022+
from project.study import CustomType
1023+
def m(arg: CustomType) -> str:
1024+
return 'test'
1025+
10041026
[case testSuperclassInImportCycle]
10051027
import a
10061028
import d

0 commit comments

Comments
 (0)