From ad646b9266ecd8b58ec7d4b7d05c386a9f6c2d3e Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 24 May 2017 00:25:40 -0700 Subject: [PATCH 01/21] Detect and support module aliasing via assignment. In semantic analysis, if we find a simple assignment statement where the rvalue is a module, make the lvalue a direct alias to that same module. Fixes #1778. --- mypy/semanal.py | 19 +++++++++++++++++++ test-data/unit/check-modules.test | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index b14e3dd9076e..ec8fabe4428a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1515,6 +1515,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: self.process_namedtuple_definition(s) self.process_typeddict_definition(s) self.process_enum_call(s) + self.process_module_assignment(s) if (len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr) and s.lvalues[0].name == '__all__' and s.lvalues[0].kind == GDEF and @@ -2355,6 +2356,24 @@ def is_classvar(self, typ: Type) -> bool: def fail_invalid_classvar(self, context: Context) -> None: self.fail('ClassVar can only be used for assignments in class body', context) + def process_module_assignment(self, s: AssignmentStmt) -> None: + """Check if s assigns a module an alias name; if yes, update symbol table.""" + # TODO support more complex forms of module alias assignment + # (e.g. `x, y = (mod1, mod2)`) and aliases not in global scope + if ( + len(s.lvalues) != 1 + or not isinstance(s.lvalues[0], NameExpr) + or not isinstance(s.rvalue, NameExpr) + or not self.is_module_scope() + ): + return + rnode = self.lookup(s.rvalue.name, s) + if rnode and rnode.kind == MODULE_REF: + lnode = self.lookup(s.lvalues[0].name, s) + if lnode: + lnode.kind = MODULE_REF + lnode.node = rnode.node + def process_enum_call(self, s: AssignmentStmt) -> None: """Check if s defines an Enum; if yes, store the definition in symbol table.""" if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr): diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 66050601d26f..f2e3410c91e8 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1416,3 +1416,13 @@ reveal_type(f()) # E: Revealed type is 'types.ModuleType' reveal_type(types) # E: Revealed type is 'types.ModuleType' [builtins fixtures/module.pyi] + +[case testModuleAssignment] +import m +m2 = m +reveal_type(m2.a) # E: Revealed type is 'builtins.str' + +[file m.py] +a = 'foo' + +[builtins fixtures/module.pyi] From c498e2a8108aa9f6ab11e1860aa53f74419c2f65 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 24 May 2017 10:52:58 -0700 Subject: [PATCH 02/21] Handle chained assignment and iterable unpacking assignment. --- mypy/semanal.py | 47 +++++++++++++++++++++---------- test-data/unit/check-modules.test | 34 ++++++++++++++++++++++ 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index ec8fabe4428a..55dc3dcd86af 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2357,22 +2357,39 @@ def fail_invalid_classvar(self, context: Context) -> None: self.fail('ClassVar can only be used for assignments in class body', context) def process_module_assignment(self, s: AssignmentStmt) -> None: - """Check if s assigns a module an alias name; if yes, update symbol table.""" - # TODO support more complex forms of module alias assignment - # (e.g. `x, y = (mod1, mod2)`) and aliases not in global scope - if ( - len(s.lvalues) != 1 - or not isinstance(s.lvalues[0], NameExpr) - or not isinstance(s.rvalue, NameExpr) - or not self.is_module_scope() - ): + """Check if s assigns a module an alias name; if so, update symbol table.""" + # TODO support module alias assignment not in global scope + if not self.is_module_scope(): return - rnode = self.lookup(s.rvalue.name, s) - if rnode and rnode.kind == MODULE_REF: - lnode = self.lookup(s.lvalues[0].name, s) - if lnode: - lnode.kind = MODULE_REF - lnode.node = rnode.node + self._process_module_assignment(s.lvalues, s.rvalue, s) + + def _process_module_assignment( + self, + lvals: List[Expression], + rval: Expression, + ctx: AssignmentStmt, + ) -> None: + """Propagate module references across assignments. + + Recursively handles the simple form of iterable unpacking; doesn't + handle advanced unpacking with *rest, dictionary unpacking, etc. + + """ + if all(isinstance(v, (TupleExpr, ListExpr)) for v in lvals + [rval]): + litemlists = [v.items for v in cast(List[Union[TupleExpr, ListExpr]], lvals)] + ritems = cast(Union[TupleExpr, ListExpr], rval).items + for rv, *lvs in zip(ritems, *litemlists): + self._process_module_assignment(lvs, rv, ctx) + elif isinstance(rval, NameExpr): + rnode = self.lookup(rval.name, ctx) + if rnode and rnode.kind == MODULE_REF: + for lval in lvals: + if not isinstance(lval, NameExpr): + continue + lnode = self.lookup(lval.name, ctx) + if lnode: + lnode.kind = MODULE_REF + lnode.node = rnode.node def process_enum_call(self, s: AssignmentStmt) -> None: """Check if s defines an Enum; if yes, store the definition in symbol table.""" diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index f2e3410c91e8..b028c0d2f132 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1426,3 +1426,37 @@ reveal_type(m2.a) # E: Revealed type is 'builtins.str' a = 'foo' [builtins fixtures/module.pyi] + +[case testChainedModuleAssignment] +import m +m3 = m2 = m +reveal_type(m2.a) # E: Revealed type is 'builtins.str' +reveal_type(m3.a) # E: Revealed type is 'builtins.str' + +[file m.py] +a = 'foo' + +[builtins fixtures/module.pyi] + +[case testMultiModuleAssignment] +import m, n +m2, n2, (m3, n3) = m, n, [m, n] +reveal_type(m2.a) # E: Revealed type is 'builtins.str' +reveal_type(n2.b) # E: Revealed type is 'builtins.str' +reveal_type(m3.a) # E: Revealed type is 'builtins.str' +reveal_type(n3.b) # E: Revealed type is 'builtins.str' + +[file m.py] +a = 'foo' + +[file n.py] +b = 'bar' + +[builtins fixtures/module.pyi] + +[case testMultiAssignment] +m2, n2 = 'foo', 'bar' +reveal_type(m2) # E: Revealed type is 'builtins.str' +reveal_type(n2) # E: Revealed type is 'builtins.str' + +[builtins fixtures/module.pyi] From fdb3af1906d3fe7433cb9dd819f363700c0eb96b Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 24 May 2017 21:02:45 -0700 Subject: [PATCH 03/21] Remove test case that was only for exploration, not intended for inclusion. --- test-data/unit/check-modules.test | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index b028c0d2f132..f4b754a1695b 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1453,10 +1453,3 @@ a = 'foo' b = 'bar' [builtins fixtures/module.pyi] - -[case testMultiAssignment] -m2, n2 = 'foo', 'bar' -reveal_type(m2) # E: Revealed type is 'builtins.str' -reveal_type(n2) # E: Revealed type is 'builtins.str' - -[builtins fixtures/module.pyi] From ee8bd80d4c717ef407935f9ee12cfea96a80cce9 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 30 May 2017 14:33:58 -0700 Subject: [PATCH 04/21] Add some more tests for module assignment. --- test-data/unit/check-modules.test | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index f4b754a1695b..5ebd10e9e7b3 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1430,8 +1430,12 @@ a = 'foo' [case testChainedModuleAssignment] import m m3 = m2 = m +m4 = m3 +m5 = m4 reveal_type(m2.a) # E: Revealed type is 'builtins.str' reveal_type(m3.a) # E: Revealed type is 'builtins.str' +reveal_type(m4.a) # E: Revealed type is 'builtins.str' +reveal_type(m5.a) # E: Revealed type is 'builtins.str' [file m.py] a = 'foo' @@ -1446,6 +1450,8 @@ reveal_type(n2.b) # E: Revealed type is 'builtins.str' reveal_type(m3.a) # E: Revealed type is 'builtins.str' reveal_type(n3.b) # E: Revealed type is 'builtins.str' +x, y = m # E: 'types.ModuleType' object is not iterable + [file m.py] a = 'foo' From 7efbdb798616e031607543b7a0b394d286dfcffa Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 30 May 2017 14:38:45 -0700 Subject: [PATCH 05/21] Also add tests for access/assignment of nonexistent module attribute. --- test-data/unit/check-modules.test | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 5ebd10e9e7b3..a7efcba6395c 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1421,6 +1421,8 @@ reveal_type(types) # E: Revealed type is 'types.ModuleType' import m m2 = m reveal_type(m2.a) # E: Revealed type is 'builtins.str' +m2.b # E: Module has no attribute "b" +m2.c = 'bar' # E: Module has no attribute "c" [file m.py] a = 'foo' From fd3eb848346e97ece5b02dbda72fd1676cb7bc45 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 30 May 2017 16:27:53 -0700 Subject: [PATCH 06/21] Break down code and add comments for clarity; add test for mismatch lengths. --- mypy/semanal.py | 32 ++++++++++++++++++++++++++++--- test-data/unit/check-modules.test | 2 ++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 84f4b6b0b6eb..fd4496499c7f 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2375,11 +2375,37 @@ def _process_module_assignment( Recursively handles the simple form of iterable unpacking; doesn't handle advanced unpacking with *rest, dictionary unpacking, etc. + In an expression like x = y = z, z is the rval and lvals will be [x, + y]. + """ if all(isinstance(v, (TupleExpr, ListExpr)) for v in lvals + [rval]): - litemlists = [v.items for v in cast(List[Union[TupleExpr, ListExpr]], lvals)] - ritems = cast(Union[TupleExpr, ListExpr], rval).items - for rv, *lvs in zip(ritems, *litemlists): + # rval and all lvals are either list or tuple, so we are dealing + # with unpacking assignment like `x, y = a, b`. Mypy didn't + # understand our all(isinstance(...)), so cast them as + # Union[TupleExpr, ListExpr] so mypy knows it is safe to access + # their .items attribute. + cast_lvals = cast(List[Union[TupleExpr, ListExpr]], lvals) + cast_rval = cast(Union[TupleExpr, ListExpr], rval) + # given an assignment like: + # x, y = m, n = a, b + # we now have: + # rval = (a, b) + # lvals = [(x, y), (m, n)] + # We now zip this into: + # matched_sets = [(a, x, m), (b, y, n)] + # where each "matched set" consists of one element of rval and the + # corresponding element of each lval. Effectively, we transform + # x, y = m, n = a, b + # into separate assignments + # x = m = a + # y = n = b + # and then we recursively call this method for each of those assignments. + # If the rval and all lvals are not all of the same length, zip will just ignore + # extra elements, so no error will be raised here; mypy will later complain + # about the length mismatch in type-checking. + matched_sets = zip(cast_rval.items, *[v.items for v in cast_lvals]) + for rv, *lvs in matched_sets: self._process_module_assignment(lvs, rv, ctx) elif isinstance(rval, NameExpr): rnode = self.lookup(rval.name, ctx) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index a7efcba6395c..ce3445efa577 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1454,6 +1454,8 @@ reveal_type(n3.b) # E: Revealed type is 'builtins.str' x, y = m # E: 'types.ModuleType' object is not iterable +x, y, z = m, n # E: Need more than 2 values to unpack (3 expected) + [file m.py] a = 'foo' From 4006d0a959e1831ff0cd5dd8a97e81c4d1d9ddf3 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 30 May 2017 16:46:10 -0700 Subject: [PATCH 07/21] Naming improvements. --- mypy/semanal.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index fd4496499c7f..1c6bcd5d03e7 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2385,27 +2385,27 @@ def _process_module_assignment( # understand our all(isinstance(...)), so cast them as # Union[TupleExpr, ListExpr] so mypy knows it is safe to access # their .items attribute. - cast_lvals = cast(List[Union[TupleExpr, ListExpr]], lvals) - cast_rval = cast(Union[TupleExpr, ListExpr], rval) + seq_lvals = cast(List[Union[TupleExpr, ListExpr]], lvals) + seq_rval = cast(Union[TupleExpr, ListExpr], rval) # given an assignment like: - # x, y = m, n = a, b + # (x, y) = (m, n) = (a, b) # we now have: - # rval = (a, b) - # lvals = [(x, y), (m, n)] + # seq_rval = (a, b) + # seq_lvals = [(x, y), (m, n)] # We now zip this into: - # matched_sets = [(a, x, m), (b, y, n)] - # where each "matched set" consists of one element of rval and the - # corresponding element of each lval. Effectively, we transform - # x, y = m, n = a, b - # into separate assignments + # elementwise_assignments = [(a, x, m), (b, y, n)] + # where each elementwise assignment consists of one element of rval and the + # corresponding element of each lval. We unpack + # (x, y) = (m, n) = (a, b) + # into elementwise assignments # x = m = a # y = n = b # and then we recursively call this method for each of those assignments. # If the rval and all lvals are not all of the same length, zip will just ignore # extra elements, so no error will be raised here; mypy will later complain # about the length mismatch in type-checking. - matched_sets = zip(cast_rval.items, *[v.items for v in cast_lvals]) - for rv, *lvs in matched_sets: + elementwise_assignments = zip(seq_rval.items, *[v.items for v in seq_lvals]) + for rv, *lvs in elementwise_assignments: self._process_module_assignment(lvs, rv, ctx) elif isinstance(rval, NameExpr): rnode = self.lookup(rval.name, ctx) From 6dc0757dc79867ca4c6eedb7476c1658135fd7cb Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 31 May 2017 12:44:22 -0700 Subject: [PATCH 08/21] Support tracking module assignment in non-global scope. --- mypy/semanal.py | 3 --- test-data/stdlib-samples/3.2/test/test_genericpath.py | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 206aa2db84f4..188ea49ca267 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2359,9 +2359,6 @@ def fail_invalid_classvar(self, context: Context) -> None: def process_module_assignment(self, s: AssignmentStmt) -> None: """Check if s assigns a module an alias name; if so, update symbol table.""" - # TODO support module alias assignment not in global scope - if not self.is_module_scope(): - return self._process_module_assignment(s.lvalues, s.rvalue, s) def _process_module_assignment( diff --git a/test-data/stdlib-samples/3.2/test/test_genericpath.py b/test-data/stdlib-samples/3.2/test/test_genericpath.py index 43b78e77db61..7dc3fa73e577 100644 --- a/test-data/stdlib-samples/3.2/test/test_genericpath.py +++ b/test-data/stdlib-samples/3.2/test/test_genericpath.py @@ -23,7 +23,7 @@ def safe_rmdir(dirname: str) -> None: class GenericTest(unittest.TestCase): # The path module to be tested - pathmodule = genericpath # type: Any + pathmodule = genericpath common_attributes = ['commonprefix', 'getsize', 'getatime', 'getctime', 'getmtime', 'exists', 'isdir', 'isfile'] attributes = [] # type: List[str] @@ -143,7 +143,7 @@ def test_exists(self) -> None: f.close() self.assertIs(self.pathmodule.exists(support.TESTFN), True) if not self.pathmodule == genericpath: - self.assertIs(self.pathmodule.lexists(support.TESTFN), + self.assertIs(self.pathmodule.lexists(support.TESTFN), # type: ignore True) finally: if not f.closed: From 9aa8169c7d8e59791cb8bad34ba78786140f3569 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 31 May 2017 13:00:43 -0700 Subject: [PATCH 09/21] Add more tests for unpacking mismatch cases. --- test-data/unit/check-modules.test | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index a8d36f7861bb..3a5d6714a3ea 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1476,8 +1476,10 @@ reveal_type(m3.a) # E: Revealed type is 'builtins.str' reveal_type(n3.b) # E: Revealed type is 'builtins.str' x, y = m # E: 'types.ModuleType' object is not iterable - x, y, z = m, n # E: Need more than 2 values to unpack (3 expected) +x, y = m, m, m # E: Too many values to unpack (2 expected, 3 provided) +x, (y, z) = m, n # E: 'types.ModuleType' object is not iterable +x, (y, z) = m, (n, n, n) # E: Too many values to unpack (2 expected, 3 provided) [file m.py] a = 'foo' From 6e7cd24f243d44ea1e8b23df91c2fb16622fe606 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 31 May 2017 13:06:15 -0700 Subject: [PATCH 10/21] Keep rvals always on the right. --- mypy/semanal.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 188ea49ca267..8dda3e69e6c3 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2387,12 +2387,12 @@ def _process_module_assignment( # given an assignment like: # (x, y) = (m, n) = (a, b) # we now have: - # seq_rval = (a, b) # seq_lvals = [(x, y), (m, n)] + # seq_rval = (a, b) # We now zip this into: - # elementwise_assignments = [(a, x, m), (b, y, n)] - # where each elementwise assignment consists of one element of rval and the - # corresponding element of each lval. We unpack + # elementwise_assignments = [(x, m, a), (y, n, b)] + # where each elementwise assignment includes one element of rval and the + # corresponding element of each lval. Basically we unpack # (x, y) = (m, n) = (a, b) # into elementwise assignments # x = m = a @@ -2401,9 +2401,9 @@ def _process_module_assignment( # If the rval and all lvals are not all of the same length, zip will just ignore # extra elements, so no error will be raised here; mypy will later complain # about the length mismatch in type-checking. - elementwise_assignments = zip(seq_rval.items, *[v.items for v in seq_lvals]) - for rv, *lvs in elementwise_assignments: - self._process_module_assignment(lvs, rv, ctx) + elementwise_assignments = zip(*[v.items for v in seq_lvals], seq_rval.items) + for *lvs, rv in elementwise_assignments: + self._process_module_assignment(list(lvs), rv, ctx) elif isinstance(rval, NameExpr): rnode = self.lookup(rval.name, ctx) if rnode and rnode.kind == MODULE_REF: From d3d8f9fdb6a8eec6bb95541036b2e4a274df8e04 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 31 May 2017 13:47:29 -0700 Subject: [PATCH 11/21] Don't use a form of unpacking that is Py35+ only. --- mypy/semanal.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 8dda3e69e6c3..43fae1686df6 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2402,8 +2402,11 @@ def _process_module_assignment( # extra elements, so no error will be raised here; mypy will later complain # about the length mismatch in type-checking. elementwise_assignments = zip(*[v.items for v in seq_lvals], seq_rval.items) - for *lvs, rv in elementwise_assignments: - self._process_module_assignment(list(lvs), rv, ctx) + for vals in elementwise_assignments: + # once Py35 is minimum supported, could use `*lvs, rv =` unpacking above + rv = vals[-1] + lvs = list(vals[:-1]) + self._process_module_assignment(lvs, rv, ctx) elif isinstance(rval, NameExpr): rnode = self.lookup(rval.name, ctx) if rnode and rnode.kind == MODULE_REF: From 1cbe94cfdaa6eb0c040be06484180025d7fd295e Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 31 May 2017 13:50:49 -0700 Subject: [PATCH 12/21] It's the zip that is problematic, not just the unpacking. --- mypy/semanal.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 43fae1686df6..dfb861e24bbb 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2390,7 +2390,7 @@ def _process_module_assignment( # seq_lvals = [(x, y), (m, n)] # seq_rval = (a, b) # We now zip this into: - # elementwise_assignments = [(x, m, a), (y, n, b)] + # elementwise_assignments = [(a, x, m), (b, y, n)] # where each elementwise assignment includes one element of rval and the # corresponding element of each lval. Basically we unpack # (x, y) = (m, n) = (a, b) @@ -2401,11 +2401,8 @@ def _process_module_assignment( # If the rval and all lvals are not all of the same length, zip will just ignore # extra elements, so no error will be raised here; mypy will later complain # about the length mismatch in type-checking. - elementwise_assignments = zip(*[v.items for v in seq_lvals], seq_rval.items) - for vals in elementwise_assignments: - # once Py35 is minimum supported, could use `*lvs, rv =` unpacking above - rv = vals[-1] - lvs = list(vals[:-1]) + elementwise_assignments = zip(seq_rval.items, *[v.items for v in seq_lvals]) + for rv, *lvs in elementwise_assignments: self._process_module_assignment(lvs, rv, ctx) elif isinstance(rval, NameExpr): rnode = self.lookup(rval.name, ctx) From 325ae9420ea7c0001b89b42c3e13a7bf849a0e60 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 31 May 2017 14:06:58 -0700 Subject: [PATCH 13/21] Add tests for module assignment in class and local scopes. --- test-data/unit/check-modules.test | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 3a5d6714a3ea..fa18e4213a4e 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1452,6 +1452,36 @@ a = 'foo' [builtins fixtures/module.pyi] +[case testClassModuleAssignment] +import m + +class C: + x = m + def foo(self) -> None: + reveal_type(self.x.a) # E: Revealed type is 'builtins.str' + +[file m.py] +a = 'foo' + +[builtins fixtures/module.pyi] + +[case testLocalModuleAssignment] +import m + +def foo() -> None: + x = m + reveal_type(x.a) # E: Revealed type is 'builtins.str' + +class C: + def foo(self) -> None: + x = m + reveal_type(x.a) # E: Revealed type is 'builtins.str' + +[file m.py] +a = 'foo' + +[builtins fixtures/module.pyi] + [case testChainedModuleAssignment] import m m3 = m2 = m From 9592ce947378eb0a9180626bac4d96501dcba16e Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Fri, 2 Jun 2017 17:12:01 -0700 Subject: [PATCH 14/21] Simplify to single method. --- mypy/semanal.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index d5c03712d46f..58e7a644bb4f 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1546,7 +1546,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: self.process_namedtuple_definition(s) self.process_typeddict_definition(s) self.process_enum_call(s) - self.process_module_assignment(s) + self.process_module_assignment(s.lvalues, s.rvalue, s) if (len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr) and s.lvalues[0].name == '__all__' and s.lvalues[0].kind == GDEF and @@ -2383,11 +2383,7 @@ def is_classvar(self, typ: Type) -> bool: def fail_invalid_classvar(self, context: Context) -> None: self.fail('ClassVar can only be used for assignments in class body', context) - def process_module_assignment(self, s: AssignmentStmt) -> None: - """Check if s assigns a module an alias name; if so, update symbol table.""" - self._process_module_assignment(s.lvalues, s.rvalue, s) - - def _process_module_assignment( + def process_module_assignment( self, lvals: List[Expression], rval: Expression, @@ -2429,7 +2425,7 @@ def _process_module_assignment( # about the length mismatch in type-checking. elementwise_assignments = zip(seq_rval.items, *[v.items for v in seq_lvals]) for rv, *lvs in elementwise_assignments: - self._process_module_assignment(lvs, rv, ctx) + self.process_module_assignment(lvs, rv, ctx) elif isinstance(rval, NameExpr): rnode = self.lookup(rval.name, ctx) if rnode and rnode.kind == MODULE_REF: From f438f9c3d0c581b9ebc5803d91e2e21dd72f7ec6 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Fri, 2 Jun 2017 18:12:53 -0700 Subject: [PATCH 15/21] Go back to annotating genericpath as Any in stdlib-sample. --- test-data/stdlib-samples/3.2/test/test_genericpath.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/stdlib-samples/3.2/test/test_genericpath.py b/test-data/stdlib-samples/3.2/test/test_genericpath.py index 7dc3fa73e577..df0e10701d39 100644 --- a/test-data/stdlib-samples/3.2/test/test_genericpath.py +++ b/test-data/stdlib-samples/3.2/test/test_genericpath.py @@ -23,7 +23,7 @@ def safe_rmdir(dirname: str) -> None: class GenericTest(unittest.TestCase): # The path module to be tested - pathmodule = genericpath + pathmodule = genericpath # type: Any common_attributes = ['commonprefix', 'getsize', 'getatime', 'getctime', 'getmtime', 'exists', 'isdir', 'isfile'] attributes = [] # type: List[str] @@ -143,7 +143,7 @@ def test_exists(self) -> None: f.close() self.assertIs(self.pathmodule.exists(support.TESTFN), True) if not self.pathmodule == genericpath: - self.assertIs(self.pathmodule.lexists(support.TESTFN), # type: ignore + self.assertIs(self.pathmodule.lexists(support.TESTFN), True) finally: if not f.closed: From e1ce5b8a80f42522de0ffa7b4da88cb0885c031e Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Fri, 2 Jun 2017 18:13:11 -0700 Subject: [PATCH 16/21] Respect explicit type annotation and don't propagate module reference. --- mypy/semanal.py | 6 ++++++ test-data/unit/check-modules.test | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index 58e7a644bb4f..304b54dd984a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2432,6 +2432,12 @@ def process_module_assignment( for lval in lvals: if not isinstance(lval, NameExpr): continue + # respect explicitly annotated type + if ( + isinstance(lval.node, Var) and + lval.node.type is not None + ): + continue lnode = self.lookup(lval.name, ctx) if lnode: lnode.kind = MODULE_REF diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index fa18e4213a4e..100562778376 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1518,3 +1518,20 @@ a = 'foo' b = 'bar' [builtins fixtures/module.pyi] + +[case testModuleAssignmentWithExplicitAnnotation] +from typing import Any +import types +import m +mod_mod: types.ModuleType = m +mod_any: Any = m +mod_int: int = m # E: Incompatible types in assignment (expression has type Module, variable has type "int") + +reveal_type(mod_mod) # E: Revealed type is 'types.ModuleType' +mod_mod.a # E: Module has no attribute "a" +reveal_type(mod_any) # E: Revealed type is 'Any' + +[file m.py] +a = 'foo' + +[builtins fixtures/module.pyi] From 4d402073663acd638e015e574b85a56620b72b56 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sat, 3 Jun 2017 06:22:48 -0700 Subject: [PATCH 17/21] Backtrack to simple ModuleType var in case of incompatible module aliasing. --- mypy/semanal.py | 30 +++++++---- test-data/unit/check-modules.test | 83 ++++++++++++++++++++++++++++--- test-data/unit/lib-stub/types.pyi | 3 +- 3 files changed, 100 insertions(+), 16 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 01c68fa51340..b0b7d7e364c5 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -255,6 +255,12 @@ def __init__(self, self.postpone_nested_functions_stack = [FUNCTION_BOTH_PHASES] self.postponed_functions_stack = [] self.all_exports = set() # type: Set[str] + # If we consider a var a module alias, then later hit an assignment to a + # different module, we need to backtrack to considering it a normal var + # of type ModuleType. Here we store the original (kind, symbolnode) for + # each SymbolTableNode that we convert to module alias, in case we need + # to backtrack that conversion. + self.module_alias_backtrack = {} # type: Dict[SymbolTableNode, Tuple[int, SymbolNode]] def visit_file(self, file_node: MypyFile, fnam: str, options: Options) -> None: self.options = options @@ -1547,7 +1553,8 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: self.process_namedtuple_definition(s) self.process_typeddict_definition(s) self.process_enum_call(s) - self.process_module_assignment(s.lvalues, s.rvalue, s) + if not s.type: + self.process_module_assignment(s.lvalues, s.rvalue, s) if (len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr) and s.lvalues[0].name == '__all__' and s.lvalues[0].kind == GDEF and @@ -2384,12 +2391,8 @@ def is_classvar(self, typ: Type) -> bool: def fail_invalid_classvar(self, context: Context) -> None: self.fail('ClassVar can only be used for assignments in class body', context) - def process_module_assignment( - self, - lvals: List[Expression], - rval: Expression, - ctx: AssignmentStmt, - ) -> None: + def process_module_assignment(self, lvals: List[Expression], rval: Expression, + ctx: AssignmentStmt) -> None: """Propagate module references across assignments. Recursively handles the simple form of iterable unpacking; doesn't @@ -2428,6 +2431,7 @@ def process_module_assignment( for rv, *lvs in elementwise_assignments: self.process_module_assignment(lvs, rv, ctx) elif isinstance(rval, NameExpr): + # import pdb; pdb.set_trace() rnode = self.lookup(rval.name, ctx) if rnode and rnode.kind == MODULE_REF: for lval in lvals: @@ -2441,8 +2445,16 @@ def process_module_assignment( continue lnode = self.lookup(lval.name, ctx) if lnode: - lnode.kind = MODULE_REF - lnode.node = rnode.node + # if the same variable is aliased to multiple different + # modules, revert to inferring plain ModuleType var + if lnode.kind == MODULE_REF and lnode.node is not rnode.node: + lnode.kind, lnode.node = self.module_alias_backtrack[lnode] + continue + # we may have already back-tracked once, don't re-create module alias + elif lnode not in self.module_alias_backtrack: + self.module_alias_backtrack[lnode] = (lnode.kind, lnode.node) + lnode.kind = MODULE_REF + lnode.node = rnode.node def process_enum_call(self, s: AssignmentStmt) -> None: """Check if s defines an Enum; if yes, store the definition in symbol table.""" diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 100562778376..0b55c93f282a 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1440,7 +1440,7 @@ a = 'foo' [builtins fixtures/module.pyi] -[case testModuleAssignment] +[case testModuleAlias] import m m2 = m reveal_type(m2.a) # E: Revealed type is 'builtins.str' @@ -1452,7 +1452,7 @@ a = 'foo' [builtins fixtures/module.pyi] -[case testClassModuleAssignment] +[case testClassModuleAlias] import m class C: @@ -1465,7 +1465,7 @@ a = 'foo' [builtins fixtures/module.pyi] -[case testLocalModuleAssignment] +[case testLocalModuleAlias] import m def foo() -> None: @@ -1482,7 +1482,7 @@ a = 'foo' [builtins fixtures/module.pyi] -[case testChainedModuleAssignment] +[case testChainedModuleAlias] import m m3 = m2 = m m4 = m3 @@ -1497,7 +1497,7 @@ a = 'foo' [builtins fixtures/module.pyi] -[case testMultiModuleAssignment] +[case testMultiModuleAlias] import m, n m2, n2, (m3, n3) = m, n, [m, n] reveal_type(m2.a) # E: Revealed type is 'builtins.str' @@ -1519,19 +1519,90 @@ b = 'bar' [builtins fixtures/module.pyi] -[case testModuleAssignmentWithExplicitAnnotation] +[case testModuleAliasWithExplicitAnnotation] from typing import Any import types import m mod_mod: types.ModuleType = m +mod_mod2: types.ModuleType +mod_mod2 = m +mod_mod3 = m # type: types.ModuleType mod_any: Any = m mod_int: int = m # E: Incompatible types in assignment (expression has type Module, variable has type "int") reveal_type(mod_mod) # E: Revealed type is 'types.ModuleType' mod_mod.a # E: Module has no attribute "a" +reveal_type(mod_mod2) # E: Revealed type is 'types.ModuleType' +mod_mod2.a # E: Module has no attribute "a" +reveal_type(mod_mod3) # E: Revealed type is 'types.ModuleType' +mod_mod3.a # E: Module has no attribute "a" reveal_type(mod_any) # E: Revealed type is 'Any' [file m.py] a = 'foo' [builtins fixtures/module.pyi] + +[case testModuleAliasPassedToFunction] +import types +import m + +def takes_module(x: types.ModuleType): + reveal_type(x.__file__) # E: Revealed type is 'builtins.str' + +n = m +takes_module(m) +takes_module(n) + +[file m.py] +a = 'foo' + +[builtins fixtures/module.pyi] + +[case testModuleAliasRepeated] +import types +import m, n, o + +if bool(): + x = m +else: + x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type Module) + +if bool(): + y = 3 +else: + y = m # E: Incompatible types in assignment (expression has type Module, variable has type "int") + +if bool(): + xx = m +else: + xx = n +xx.a # E: Module has no attribute "a" + +yy = m +yy = n +yy = o + +yy.a # E: Module has no attribute "a" +reveal_type(yy.__file__) # E: Revealed type is 'builtins.str' + +zz = o +zz, aa = m, n + +zz.a # E: Module has no attribute "a" +reveal_type(aa.a) # E: Revealed type is 'builtins.int' + +xxx = m +xxx = m +reveal_type(xxx.a) # E: Revealed type is 'builtins.str' + +[file m.py] +a = 'foo' + +[file n.py] +a = 3 + +[file o.py] +a = 'bar' + +[builtins fixtures/module.pyi] diff --git a/test-data/unit/lib-stub/types.pyi b/test-data/unit/lib-stub/types.pyi index 12a942493402..02113aea3834 100644 --- a/test-data/unit/lib-stub/types.pyi +++ b/test-data/unit/lib-stub/types.pyi @@ -6,4 +6,5 @@ def coroutine(func: _T) -> _T: pass class bool: ... -class ModuleType: ... +class ModuleType: + __file__ = ... # type: str From a8a4f4473af66cdc77d5c7be9af1bc6000d8ffab Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sat, 3 Jun 2017 06:32:20 -0700 Subject: [PATCH 18/21] Remove stray pdb comment. --- mypy/semanal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index b0b7d7e364c5..d6f19df60454 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2431,7 +2431,6 @@ def process_module_assignment(self, lvals: List[Expression], rval: Expression, for rv, *lvs in elementwise_assignments: self.process_module_assignment(lvs, rv, ctx) elif isinstance(rval, NameExpr): - # import pdb; pdb.set_trace() rnode = self.lookup(rval.name, ctx) if rnode and rnode.kind == MODULE_REF: for lval in lvals: From 6584b0b6ece4baf9f41efd503a5c2bc626aa5c5d Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sat, 3 Jun 2017 10:37:18 -0700 Subject: [PATCH 19/21] Also handle reassignment of an original (non-alias) module reference. --- mypy/semanal.py | 18 ++++++++++++++--- test-data/unit/check-modules.test | 32 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index d6f19df60454..a179c540cf51 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2447,10 +2447,22 @@ def process_module_assignment(self, lvals: List[Expression], rval: Expression, # if the same variable is aliased to multiple different # modules, revert to inferring plain ModuleType var if lnode.kind == MODULE_REF and lnode.node is not rnode.node: - lnode.kind, lnode.node = self.module_alias_backtrack[lnode] + if lnode in self.module_alias_backtrack: + # lnode is a module reference created by previous assignment + lnode.kind, lnode.node = self.module_alias_backtrack[lnode] + else: + # lnode is an imported module, convert to variable + if self.is_func_scope(): + lnode.kind = LDEF + elif self.type: + lnode.kind = MDEF + else: + lnode.kind = GDEF + lnode.node = Var(lval.name) + lnode.node.set_line(lval) continue - # we may have already back-tracked once, don't re-create module alias - elif lnode not in self.module_alias_backtrack: + # never create module alias except on initial var definition + elif lval.is_def: self.module_alias_backtrack[lnode] = (lnode.kind, lnode.node) lnode.kind = MODULE_REF lnode.node = rnode.node diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 0b55c93f282a..270f4ac2b4fa 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1606,3 +1606,35 @@ a = 3 a = 'bar' [builtins fixtures/module.pyi] + +[case testModuleAliasToOtherModule] +import m, n +m = n + +m.a # E: Module has no attribute "a" + +[file m.py] +a = 'foo' + +[file n.py] +a = 3 + +[builtins fixtures/module.pyi] + +[case testModuleAliasToOtherModuleInFunction] +import n + +def foo() -> None: + import m + m = n + m.a # E: Module has no attribute "a" + +m # E: Name 'm' is not defined + +[file m.py] +a = 'foo' + +[file n.py] +a = 3 + +[builtins fixtures/module.pyi] From b292673cadbdc971966adbc67debdb20b0405e5c Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sat, 3 Jun 2017 11:20:19 -0700 Subject: [PATCH 20/21] Simplify: fail on assignment of different modules to same variable without explicit annotation. --- mypy/semanal.py | 27 ++---------- test-data/unit/check-modules.test | 72 ++++++++++++++++--------------- 2 files changed, 41 insertions(+), 58 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index a179c540cf51..665eb3dbfab9 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -255,12 +255,6 @@ def __init__(self, self.postpone_nested_functions_stack = [FUNCTION_BOTH_PHASES] self.postponed_functions_stack = [] self.all_exports = set() # type: Set[str] - # If we consider a var a module alias, then later hit an assignment to a - # different module, we need to backtrack to considering it a normal var - # of type ModuleType. Here we store the original (kind, symbolnode) for - # each SymbolTableNode that we convert to module alias, in case we need - # to backtrack that conversion. - self.module_alias_backtrack = {} # type: Dict[SymbolTableNode, Tuple[int, SymbolNode]] def visit_file(self, file_node: MypyFile, fnam: str, options: Options) -> None: self.options = options @@ -2444,26 +2438,13 @@ def process_module_assignment(self, lvals: List[Expression], rval: Expression, continue lnode = self.lookup(lval.name, ctx) if lnode: - # if the same variable is aliased to multiple different - # modules, revert to inferring plain ModuleType var if lnode.kind == MODULE_REF and lnode.node is not rnode.node: - if lnode in self.module_alias_backtrack: - # lnode is a module reference created by previous assignment - lnode.kind, lnode.node = self.module_alias_backtrack[lnode] - else: - # lnode is an imported module, convert to variable - if self.is_func_scope(): - lnode.kind = LDEF - elif self.type: - lnode.kind = MDEF - else: - lnode.kind = GDEF - lnode.node = Var(lval.name) - lnode.node.set_line(lval) - continue + self.fail( + "Cannot assign multiple modules to name '{}' " + "without explicit annotation to ModuleType".format(lval.name), + ctx) # never create module alias except on initial var definition elif lval.is_def: - self.module_alias_backtrack[lnode] = (lnode.kind, lnode.node) lnode.kind = MODULE_REF lnode.node = rnode.node diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 270f4ac2b4fa..725a58b74cbe 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1560,8 +1560,7 @@ a = 'foo' [builtins fixtures/module.pyi] [case testModuleAliasRepeated] -import types -import m, n, o +import m, n if bool(): x = m @@ -1574,27 +1573,9 @@ else: y = m # E: Incompatible types in assignment (expression has type Module, variable has type "int") if bool(): - xx = m + z = m else: - xx = n -xx.a # E: Module has no attribute "a" - -yy = m -yy = n -yy = o - -yy.a # E: Module has no attribute "a" -reveal_type(yy.__file__) # E: Revealed type is 'builtins.str' - -zz = o -zz, aa = m, n - -zz.a # E: Module has no attribute "a" -reveal_type(aa.a) # E: Revealed type is 'builtins.int' - -xxx = m -xxx = m -reveal_type(xxx.a) # E: Revealed type is 'builtins.str' + z = n # E: Cannot assign multiple modules to name 'z' without explicit annotation to ModuleType [file m.py] a = 'foo' @@ -1602,16 +1583,20 @@ a = 'foo' [file n.py] a = 3 -[file o.py] -a = 'bar' - [builtins fixtures/module.pyi] -[case testModuleAliasToOtherModule] +[case testModuleAliasRepeatedWithAnnotation] +import types import m, n -m = n -m.a # E: Module has no attribute "a" +x: types.ModuleType +if bool(): + x = m +else: + x = n + +x.a # E: Module has no attribute "a" +reveal_type(x.__file__) # E: Revealed type is 'builtins.str' [file m.py] a = 'foo' @@ -1621,15 +1606,19 @@ a = 3 [builtins fixtures/module.pyi] -[case testModuleAliasToOtherModuleInFunction] -import n +[case testModuleAliasRepeatedComplex] +import m, n, o + +x = m +x = n # E: Cannot assign multiple modules to name 'x' without explicit annotation to ModuleType +x = o # E: Cannot assign multiple modules to name 'x' without explicit annotation to ModuleType -def foo() -> None: - import m - m = n - m.a # E: Module has no attribute "a" +y = o +y, z = m, n # E: Cannot assign multiple modules to name 'y' without explicit annotation to ModuleType -m # E: Name 'm' is not defined +xx = m +xx = m +reveal_type(xx.a) # E: Revealed type is 'builtins.str' [file m.py] a = 'foo' @@ -1637,4 +1626,17 @@ a = 'foo' [file n.py] a = 3 +[file o.py] +a = 'bar' + +[builtins fixtures/module.pyi] + +[case testModuleAliasToOtherModule] +import m, n +m = n # E: Cannot assign multiple modules to name 'm' without explicit annotation to ModuleType + +[file m.py] + +[file n.py] + [builtins fixtures/module.pyi] From f62d58efda070187e437637c64713e1e04e39ccc Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sat, 3 Jun 2017 16:24:52 -0700 Subject: [PATCH 21/21] Style and working tweaks. --- mypy/semanal.py | 7 ++----- test-data/unit/check-modules.test | 10 +++++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 665eb3dbfab9..b4adfb85f3cb 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2431,17 +2431,14 @@ def process_module_assignment(self, lvals: List[Expression], rval: Expression, if not isinstance(lval, NameExpr): continue # respect explicitly annotated type - if ( - isinstance(lval.node, Var) and - lval.node.type is not None - ): + if (isinstance(lval.node, Var) and lval.node.type is not None): continue lnode = self.lookup(lval.name, ctx) if lnode: if lnode.kind == MODULE_REF and lnode.node is not rnode.node: self.fail( "Cannot assign multiple modules to name '{}' " - "without explicit annotation to ModuleType".format(lval.name), + "without explicit 'types.ModuleType' annotation".format(lval.name), ctx) # never create module alias except on initial var definition elif lval.is_def: diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 725a58b74cbe..b1b6857e5518 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1575,7 +1575,7 @@ else: if bool(): z = m else: - z = n # E: Cannot assign multiple modules to name 'z' without explicit annotation to ModuleType + z = n # E: Cannot assign multiple modules to name 'z' without explicit 'types.ModuleType' annotation [file m.py] a = 'foo' @@ -1610,11 +1610,11 @@ a = 3 import m, n, o x = m -x = n # E: Cannot assign multiple modules to name 'x' without explicit annotation to ModuleType -x = o # E: Cannot assign multiple modules to name 'x' without explicit annotation to ModuleType +x = n # E: Cannot assign multiple modules to name 'x' without explicit 'types.ModuleType' annotation +x = o # E: Cannot assign multiple modules to name 'x' without explicit 'types.ModuleType' annotation y = o -y, z = m, n # E: Cannot assign multiple modules to name 'y' without explicit annotation to ModuleType +y, z = m, n # E: Cannot assign multiple modules to name 'y' without explicit 'types.ModuleType' annotation xx = m xx = m @@ -1633,7 +1633,7 @@ a = 'bar' [case testModuleAliasToOtherModule] import m, n -m = n # E: Cannot assign multiple modules to name 'm' without explicit annotation to ModuleType +m = n # E: Cannot assign multiple modules to name 'm' without explicit 'types.ModuleType' annotation [file m.py]