Skip to content

Commit 1dbe724

Browse files
authored
Restore all_are_submodules import logic as workaround for #4498 (#5016)
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 on 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. Unfortunately the dependency on the module is actually required for correctness in some corner cases, so instead of eliding the import, we lower its priority. This causes the cycles in the regressions we are looking at to get processed in the order that works. This is obviously just a workaround.
1 parent 0533a07 commit 1dbe724

File tree

2 files changed

+49
-1
lines changed

2 files changed

+49
-1
lines changed

mypy/build.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,16 +713,22 @@ def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str:
713713
elif isinstance(imp, ImportFrom):
714714
cur_id = correct_rel_imp(imp)
715715
pos = len(res)
716+
all_are_submodules = True
716717
# Also add any imported names that are submodules.
717718
pri = import_priority(imp, PRI_MED)
718719
for name, __ in imp.names:
719720
sub_id = cur_id + '.' + name
720721
if self.is_module(sub_id):
721722
res.append((pri, sub_id, imp.line))
723+
else:
724+
all_are_submodules = False
722725
# Add cur_id as a dependency, even if all of the
723726
# imports are submodules. Processing import from will try
724727
# to look through cur_id, so we should depend on it.
725-
pri = import_priority(imp, PRI_HIGH)
728+
# As a workaround for for some bugs in cycle handling (#4498),
729+
# if all of the imports are submodules, do the import at a lower
730+
# priority.
731+
pri = import_priority(imp, PRI_HIGH if not all_are_submodules else PRI_LOW)
726732
res.insert(pos, ((pri, cur_id, imp.line)))
727733
elif isinstance(imp, ImportAll):
728734
pri = import_priority(imp, PRI_HIGH)

test-data/unit/check-modules.test

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

1004+
[case testImportReExportFromChildrenInCycle1]
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+
1026+
[case testImportReExportFromChildrenInCycle2]
1027+
# cmd: mypy -m project project.b project.ba project.c
1028+
# See comments in above test about this being a workaround.
1029+
[file foo.py]
1030+
def get_foo() -> int: return 12
1031+
1032+
[file project/ba.py]
1033+
from . import b
1034+
b.FOO
1035+
1036+
[file project/b.py]
1037+
import foo
1038+
from . import c
1039+
FOO = foo.get_foo()
1040+
1041+
[file project/c.py]
1042+
1043+
[file project/__init__.py]
1044+
from . import ba
1045+
10041046
[case testSuperclassInImportCycle]
10051047
import a
10061048
import d

0 commit comments

Comments
 (0)