diff --git a/mypy/semanal.py b/mypy/semanal.py index 2d7c4c7e3d4c..8a25b70447ec 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -48,10 +48,11 @@ reduce memory use). """ +import copy from contextlib import contextmanager from typing import ( - List, Dict, Set, Tuple, cast, TypeVar, Union, Optional, Callable, Iterator, Iterable + Any, List, Dict, Set, Tuple, cast, TypeVar, Union, Optional, Callable, Iterator, Iterable ) from typing_extensions import Final, TypeAlias as _TypeAlias @@ -78,7 +79,7 @@ typing_extensions_aliases, EnumCallExpr, RUNTIME_PROTOCOL_DECOS, FakeExpression, Statement, AssignmentExpr, ParamSpecExpr, EllipsisExpr, TypeVarLikeExpr, implicit_module_attrs, - MatchStmt, + MatchStmt, FuncBase ) from mypy.patterns import ( AsPattern, OrPattern, ValuePattern, SequencePattern, @@ -4802,7 +4803,38 @@ def add_imported_symbol(self, module_hidden: bool) -> None: """Add an alias to an existing symbol through import.""" assert not module_hidden or not module_public - symbol = SymbolTableNode(node.kind, node.node, + + 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 = SymbolTableNode(node.kind, symbol_node, module_public=module_public, module_hidden=module_hidden) self.add_symbol_table_node(name, symbol, context) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index a2bcdade3287..8f3a2c308786 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7134,3 +7134,112 @@ class B(A): # E: Final class __main__.B has abstract attributes "foo" [case testUndefinedBaseclassInNestedClass] class C: class C1(XX): pass # E: Name "XX" is not defined + +[case testClassScopeImportFunction] +class Foo: + from mod import foo + +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: ... + +[case testClassScopeImportVariable] +class Foo: + from mod import foo + +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 + +[case testClassScopeImportModule] +class Foo: + import mod + +reveal_type(Foo.mod) # N: Revealed type is "builtins.object" +reveal_type(Foo.mod.foo) # N: Revealed type is "builtins.int" +[file mod.py] +foo: int + +[case testClassScopeImportFunctionAlias] +class Foo: + from mod import foo + bar = foo + + from mod import const_foo + const_bar = const_foo + +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 + +[case testClassScopeImportModuleStar] +class Foo: + from mod 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.baz) # E: "Type[Foo]" has no attribute "baz" \ + # N: Revealed type is "Any" +[file mod.py] +foo: int +def bar(x: int) -> int: ... + +[case testClassScopeImportFunctionNested] +class Foo: + class Bar: + from mod import baz + +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: ... + +[case testClassScopeImportUndefined] +class Foo: + from unknown import foo # E: Cannot find implementation or library stub for module named "unknown" \ + # N: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports + +reveal_type(Foo.foo) # N: Revealed type is "Any" +reveal_type(Foo().foo) # N: Revealed type is "Any" + +[case testClassScopeImportWithFollowImports] +# flags: --follow-imports=skip +class Foo: + from mod import foo + +reveal_type(Foo().foo) # N: Revealed type is "Any" +[file mod.py] +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 meth1 + 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 + +class Bar: + from mod1 import foo + +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(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: ... diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 4f0aa83912ea..e708ee520497 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -131,9 +131,9 @@ def f() -> None: pass [case testImportWithinClassBody2] import typing class C: - from m import f + from m import f # E: Method must have at least one argument f() - f(C) # E: Too many arguments for "f" + f(C) # E: Too many arguments for "f" of "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 8a039ad278f3..276f634df9e4 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 + from mm import f # E: Method must have at least one argument @dec(f) def m(self): pass @@ -2742,7 +2742,7 @@ import m [file m/__init__.py] class C: - from m.m import f + from m.m import f # E: Method must have at least one argument @dec(f) def m(self): pass diff --git a/test-data/unit/semanal-modules.test b/test-data/unit/semanal-modules.test index 86bb82d7bd69..16b9a9b18250 100644 --- a/test-data/unit/semanal-modules.test +++ b/test-data/unit/semanal-modules.test @@ -568,7 +568,7 @@ MypyFile:1( ImportFrom:2(_x, [y]) AssignmentStmt:3( NameExpr(z* [m]) - NameExpr(y [_x.y])))) + NameExpr(y [__main__.A.y])))) [case testImportInClassBody2] class A: