Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand imports in classes #11344

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1869,6 +1869,15 @@ def visit_import_from(self, imp: ImportFrom) -> None:
continue

if node and not node.module_hidden:
# expand into importing class, too... unless there's something already there.
# (deciding what expands is based on line order which cannot be checked here.)
if self.is_class_scope():
assert self.type is not None
defn = node.node
if imported_id not in self.type.names.keys() and isinstance(defn, FuncDef):
if not defn.is_decorated and not defn.is_overload:
defn.info = self.type
self.add_symbol(defn.name, defn, imp)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this doesn't seem right/enough. We modify the function as defined in the original module, but it should only be treated as a method in this class, not elsewhere. I wonder what would happen if import the same function into multiple classes. For completeness, we should probably do this for Decorator and OverloadedFuncDef as well.

Implementing this in a full and correct way seems somewhat complicated. We could perhaps take a copy of the original FuncDef (or other node) and only modify the copy.

Alternatively, you could detect this and generate a blocking error, and not add anything to the symbol table. This would be the easiest fix. The error message can contain a note that suggests doing something like name = mod.func instead.

self.process_imported_symbol(
node, module_id, id, imported_id, fullname, module_public, context=imp
)
Expand Down
65 changes: 65 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -6968,3 +6968,68 @@ 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 testExpandImportedMethodInClass]
# https://github.com/python/mypy/issues/7045
class Foo:
from a import meth
A5rocks marked this conversation as resolved.
Show resolved Hide resolved

reveal_type(Foo().meth) # N: Revealed type is "def (x: builtins.int) -> builtins.int"
[file a.py]
def meth(self, x: int) -> int:
...

[case testExpandImportedFunctionInClass]
class Foo:
from a import func

reveal_type(Foo.func) # N: Revealed type is "def () -> builtins.str"
[file a.py]
def func() -> str:
...

[case testExpandImportedMethodInNestedClass]
class Foo:
class Bar:
from a import meth

reveal_type(Foo.Bar().meth) # N: Revealed type is "def (x: builtins.int) -> builtins.int"
[file a.py]
def meth(self, x: int) -> int:
...

[case testExpandUndefinedImports]
class Foo:
from not_a_module import func

reveal_type(Foo.func)
reveal_type(Foo().func)
[out]
main:2: error: Cannot find implementation or library stub for module named "not_a_module"
main:2: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
main:4: note: Revealed type is "Any"
main:5: note: Revealed type is "Any"

[case testExpandImportedMethodWithFollowImports]
# flags: --follow-imports=skip
class Foo:
from a import meth

reveal_type(Foo().meth) # N: Revealed type is "Any"
[file a.py]
def meth(self, x: int) -> int:
...

[case testExpandWithNonSelfFirstArgument]
class Foo:
from a import func

reveal_type(Foo().func)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test what happens if we import two functions with the same name (from different modules)?

Also test what happens if we import a function over an existing name.

Test multiple classes that import the same function.

Test calling the original function via module and instance.

reveal_type(Foo.func)
[file a.py]
def func(self: str, x: int) -> int:
...
[out]
main:4: error: Invalid self argument "Foo" to attribute function "func" with type "Callable[[str, int], int]"
main:4: note: Revealed type is "def (x: builtins.int) -> builtins.int"
main:5: note: Revealed type is "def (self: builtins.str, x: builtins.int) -> builtins.int"