Skip to content

Commit a267bb8

Browse files
committed
fix: longest prefix for nested modules
1 parent 8ea65c0 commit a267bb8

File tree

2 files changed

+46
-22
lines changed

2 files changed

+46
-22
lines changed

mypy/semanal.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6409,35 +6409,43 @@ def lookup_fully_qualified_or_none(self, fullname: str) -> SymbolTableNode | Non
64096409
# we might keep the module level only lookup for thing like 'builtins.int').
64106410
assert "." in fullname
64116411

6412-
module, name = fullname.rsplit(".", maxsplit=1)
6413-
6414-
# The reason for this is that we want to be able to handle cases such as importlib.machinery
6415-
if module not in self.modules:
6416-
# Check if there's nested module A.B.C
6417-
splitted = fullname.rsplit(".")
6418-
module, names = splitted[0], splitted[1:]
6419-
# If module still not in modules, return None
6420-
if module not in self.modules:
6421-
return None
6422-
filenode = self.modules[module]
6423-
result = filenode.names.get(names[0])
6412+
splitted_modules = fullname.rsplit(".")
6413+
names = []
64246414

6425-
if result is None and self.is_incomplete_namespace(module):
6426-
# TODO: More explicit handling of incomplete refs?
6427-
self.record_incomplete_ref()
6415+
while splitted_modules and ".".join(splitted_modules) not in self.modules:
6416+
'''
6417+
Try to find the module in the modules dictionary.
64286418
6429-
for part in names[1:]:
6430-
if result is not None and isinstance(result.node, TypeInfo):
6431-
result = result.node.names.get(part)
6432-
else:
6433-
return None
6434-
return result
6419+
If the module is not found, pop the last element of the splitted list and append it to the names list.
6420+
6421+
This is to find the longest prefix of the module name that is in the modules dictionary.
6422+
'''
6423+
names.append(splitted_modules.pop())
64356424

6425+
if not splitted_modules or not names:
6426+
'''
6427+
If no module or name is found, return None.
6428+
'''
6429+
return None
6430+
6431+
'''
6432+
Reverse the names list to get the correct order of names.
6433+
'''
6434+
names.reverse()
6435+
6436+
module = ".".join(splitted_modules)
64366437
filenode = self.modules[module]
6437-
result = filenode.names.get(name)
6438+
result = filenode.names.get(names[0])
6439+
64386440
if result is None and self.is_incomplete_namespace(module):
64396441
# TODO: More explicit handling of incomplete refs?
64406442
self.record_incomplete_ref()
6443+
6444+
for part in names[1:]:
6445+
if result is not None and isinstance(result.node, TypeInfo):
6446+
result = result.node.names.get(part)
6447+
else:
6448+
return None
64416449
return result
64426450

64436451
def object_type(self) -> Instance:

test-data/unit/check-python312.test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,3 +1933,19 @@ class Z:
19331933

19341934
a = Z.A()
19351935
v = a.f
1936+
1937+
[case testPEP695MultipleNestedGenericClass5]
1938+
# flags: --enable-incomplete-feature=NewGenericSyntax
1939+
from a.b.c import d
1940+
x: d.D.E.F.G[int]
1941+
x.g('a') # E: Argument 1 to "g" of "G" has incompatible type "str"; expected "int"
1942+
reveal_type(x) # N: Revealed type is "a.b.c.d.D.E.F.G[builtins.int]"
1943+
reveal_type(d.D.E.F.d) # N: Revealed type is "a.b.c.d.D.E.F.G[builtins.str]"
1944+
1945+
[file a/b/c/d.py]
1946+
class D:
1947+
class E:
1948+
class F:
1949+
class G[Q]:
1950+
def g(self, x: Q): ...
1951+
d: G[str]

0 commit comments

Comments
 (0)