diff --git a/mypy/semanal.py b/mypy/semanal.py index 0f9f3996892a..c83bc01908ad 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -48,7 +48,6 @@ reduce memory use). """ -import copy from contextlib import contextmanager from typing import ( @@ -4791,6 +4790,48 @@ def add_module_symbol(self, module_hidden=module_hidden ) + def _get_node_for_class_scoped_import( + self, name: str, symbol_node: Optional[SymbolNode], context: Context + ) -> Optional[SymbolNode]: + if symbol_node is None: + return None + # I promise this type checks; I'm just making mypyc issues go away. + # mypyc is absolutely convinced that `symbol_node` narrows to a Var in the following, + # when it can also be a FuncBase. Once fixed, `f` in the following can be removed. + # See also https://github.com/mypyc/mypyc/issues/892 + f = cast(Any, lambda x: x) + if isinstance(f(symbol_node), (FuncBase, Var)): + # For imports in class scope, we construct a new node to represent the symbol and + # set its `info` attribute to `self.type`. + existing = self.current_symbol_table().get(name) + if ( + # The redefinition checks in `add_symbol_table_node` don't work for our + # constructed Var / FuncBase, so check for possible redefinitions here. + existing is not None + and isinstance(f(existing.node), (FuncBase, Var)) + and ( + isinstance(f(existing.type), f(AnyType)) + or f(existing.type) == f(symbol_node).type + ) + ): + return existing.node + + # Construct the new node + if isinstance(f(symbol_node), FuncBase): + # In theory we could construct a new node here as well, but in practice + # it doesn't work well, see #12197 + typ: Optional[Type] = AnyType(TypeOfAny.from_error) + self.fail('Unsupported class scoped import', context) + else: + typ = f(symbol_node).type + symbol_node = Var(name, typ) + symbol_node._fullname = self.qualified_name(name) + assert self.type is not None # guaranteed by is_class_scope + symbol_node.info = self.type + symbol_node.line = context.line + symbol_node.column = context.column + return symbol_node + def add_imported_symbol(self, name: str, node: SymbolTableNode, @@ -4803,32 +4844,7 @@ def add_imported_symbol(self, symbol_node: Optional[SymbolNode] = node.node if self.is_class_scope(): - # I promise this type checks; I'm just making mypyc issues go away. - # mypyc is absolutely convinced that `symbol_node` narrows to a Var in the following, - # when it can also be a FuncBase. Once fixed, `f` in the following can be removed. - # See also https://github.com/mypyc/mypyc/issues/892 - f = cast(Any, lambda x: x) - if isinstance(f(symbol_node), (FuncBase, Var)): - # For imports in class scope, we construct a new node to represent the symbol and - # set its `info` attribute to `self.type`. - existing = self.current_symbol_table().get(name) - if ( - # The redefinition checks in `add_symbol_table_node` don't work for our - # constructed Var / FuncBase, so check for possible redefinitions here. - existing is not None - and isinstance(f(existing.node), (FuncBase, Var)) - and f(existing.type) == f(symbol_node).type - ): - symbol_node = existing.node - else: - # Construct the new node - constructed_node = copy.copy(f(symbol_node)) - assert self.type is not None # guaranteed by is_class_scope - constructed_node.line = context.line - constructed_node.column = context.column - constructed_node.info = self.type - constructed_node._fullname = self.qualified_name(name) - symbol_node = constructed_node + symbol_node = self._get_node_for_class_scoped_import(name, symbol_node, context) symbol = SymbolTableNode(node.kind, symbol_node, module_public=module_public, diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 8f3a2c308786..65b0e8d69cb5 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7135,24 +7135,20 @@ class B(A): # E: Final class __main__.B has abstract attributes "foo" class C: class C1(XX): pass # E: Name "XX" is not defined -[case testClassScopeImportFunction] +[case testClassScopeImports] class Foo: - from mod import foo + from mod import plain_function # E: Unsupported class scoped import + from mod import plain_var -reveal_type(Foo.foo) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int" -reveal_type(Foo().foo) # E: Invalid self argument "Foo" to attribute function "foo" with type "Callable[[int, int], int]" \ - # N: Revealed type is "def (y: builtins.int) -> builtins.int" -[file mod.py] -def foo(x: int, y: int) -> int: ... +reveal_type(Foo.plain_function) # N: Revealed type is "Any" +reveal_type(Foo().plain_function) # N: Revealed type is "Any" -[case testClassScopeImportVariable] -class Foo: - from mod import foo +reveal_type(Foo.plain_var) # N: Revealed type is "builtins.int" +reveal_type(Foo().plain_var) # N: Revealed type is "builtins.int" -reveal_type(Foo.foo) # N: Revealed type is "builtins.int" -reveal_type(Foo().foo) # N: Revealed type is "builtins.int" [file mod.py] -foo: int +def plain_function(x: int, y: int) -> int: ... +plain_var: int [case testClassScopeImportModule] class Foo: @@ -7163,30 +7159,52 @@ reveal_type(Foo.mod.foo) # N: Revealed type is "builtins.int" [file mod.py] foo: int -[case testClassScopeImportFunctionAlias] +[case testClassScopeImportAlias] class Foo: - from mod import foo - bar = foo + from mod import function # E: Unsupported class scoped import + foo = function - from mod import const_foo - const_bar = const_foo + from mod import var1 + bar = var1 + + from mod import var2 + baz = var2 + + from mod import var3 + qux = var3 + +reveal_type(Foo.foo) # N: Revealed type is "Any" +reveal_type(Foo.function) # N: Revealed type is "Any" + +reveal_type(Foo.bar) # N: Revealed type is "builtins.int" +reveal_type(Foo.var1) # N: Revealed type is "builtins.int" + +reveal_type(Foo.baz) # N: Revealed type is "mod.C" +reveal_type(Foo.var2) # N: Revealed type is "mod.C" + +reveal_type(Foo.qux) # N: Revealed type is "builtins.int" +reveal_type(Foo.var3) # N: Revealed type is "builtins.int" -reveal_type(Foo.foo) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int" -reveal_type(Foo.bar) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int" -reveal_type(Foo.const_foo) # N: Revealed type is "builtins.int" -reveal_type(Foo.const_bar) # N: Revealed type is "builtins.int" [file mod.py] -def foo(x: int, y: int) -> int: ... -const_foo: int +def function(x: int, y: int) -> int: ... +var1: int + +class C: ... +var2: C + +A = int +var3: A + [case testClassScopeImportModuleStar] class Foo: - from mod import * + from mod import * # E: Unsupported class scoped import reveal_type(Foo.foo) # N: Revealed type is "builtins.int" -reveal_type(Foo.bar) # N: Revealed type is "def (x: builtins.int) -> builtins.int" +reveal_type(Foo.bar) # N: Revealed type is "Any" reveal_type(Foo.baz) # E: "Type[Foo]" has no attribute "baz" \ # N: Revealed type is "Any" + [file mod.py] foo: int def bar(x: int) -> int: ... @@ -7194,11 +7212,11 @@ def bar(x: int) -> int: ... [case testClassScopeImportFunctionNested] class Foo: class Bar: - from mod import baz + from mod import baz # E: Unsupported class scoped import + +reveal_type(Foo.Bar.baz) # N: Revealed type is "Any" +reveal_type(Foo.Bar().baz) # N: Revealed type is "Any" -reveal_type(Foo.Bar.baz) # N: Revealed type is "def (x: builtins.int) -> builtins.int" -reveal_type(Foo.Bar().baz) # E: Invalid self argument "Bar" to attribute function "baz" with type "Callable[[int], int]" \ - # N: Revealed type is "def () -> builtins.int" [file mod.py] def baz(x: int) -> int: ... @@ -7221,25 +7239,48 @@ def foo(x: int, y: int) -> int: ... [case testClassScopeImportVarious] class Foo: - from mod1 import foo - from mod2 import foo # E: Name "foo" already defined on line 2 + from mod1 import foo # E: Unsupported class scoped import + from mod2 import foo - from mod1 import meth1 + from mod1 import meth1 # E: Unsupported class scoped import def meth1(self, a: str) -> str: ... # E: Name "meth1" already defined on line 5 def meth2(self, a: str) -> str: ... - from mod1 import meth2 # E: Name "meth2" already defined on line 8 + from mod1 import meth2 # E: Unsupported class scoped import \ + # E: Name "meth2" already defined on line 8 class Bar: - from mod1 import foo + from mod1 import foo # E: Unsupported class scoped import import mod1 -reveal_type(Foo.foo) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int" -reveal_type(Bar.foo) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int" +reveal_type(Foo.foo) # N: Revealed type is "Any" +reveal_type(Bar.foo) # N: Revealed type is "Any" reveal_type(mod1.foo) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int" + [file mod1.py] def foo(x: int, y: int) -> int: ... def meth1(x: int) -> int: ... def meth2(x: int) -> int: ... [file mod2.py] def foo(z: str) -> int: ... + + +[case testClassScopeImportWithError] +class Foo: + from mod import meth1 # E: Unsupported class scoped import + from mod import meth2 # E: Unsupported class scoped import + from mod import T + +reveal_type(Foo.T) # E: Type variable "Foo.T" cannot be used as an expression \ + # N: Revealed type is "Any" + +[file mod.pyi] +from typing import Any, TypeVar, overload + +@overload +def meth1(self: Any, y: int) -> int: ... +@overload +def meth1(self: Any, y: str) -> str: ... + +T = TypeVar("T") +def meth2(self: Any, y: T) -> T: ... diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index e708ee520497..6c32c088255d 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -131,9 +131,10 @@ def f() -> None: pass [case testImportWithinClassBody2] import typing class C: - from m import f # E: Method must have at least one argument + from m import f # E: Unsupported class scoped import f() - f(C) # E: Too many arguments for "f" of "C" + # ideally, the following should error: + f(C) [file m.py] def f() -> None: pass [out] diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 276f634df9e4..3b4b0303691a 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -2722,7 +2722,7 @@ import m [file m.py] class C: - from mm import f # E: Method must have at least one argument + from mm import f # E: Unsupported class scoped import @dec(f) def m(self): pass @@ -2742,7 +2742,7 @@ import m [file m/__init__.py] class C: - from m.m import f # E: Method must have at least one argument + from m.m import f # E: Unsupported class scoped import @dec(f) def m(self): pass diff --git a/test-data/unit/fine-grained-modules.test b/test-data/unit/fine-grained-modules.test index 856eaaad083c..80a2883ee756 100644 --- a/test-data/unit/fine-grained-modules.test +++ b/test-data/unit/fine-grained-modules.test @@ -1509,11 +1509,12 @@ class C: pass main:3: error: Name "f" is not defined main:4: error: Name "C" is not defined == -main:3: error: Missing positional argument "x" in call to "f" +main:2: error: Unsupported class scoped import main:4: error: Name "C" is not defined == -main:3: error: Missing positional argument "x" in call to "f" +main:2: error: Unsupported class scoped import == +main:2: error: Unsupported class scoped import [case testImportStarAddMissingDependencyInsidePackage1] from p.b import f