Skip to content

Commit

Permalink
Fix crashes in class scoped imports (#12023)
Browse files Browse the repository at this point in the history
Fixes #11045, fixes huggingface/transformers#13390
Fixes #10488
Fixes #7045
Fixes #7806
Fixes #11641
Fixes #11351
Fixes #10488

Co-authored-by: @A5rocks
  • Loading branch information
hauntsaninja authored Feb 17, 2022
1 parent 84aaef5 commit 1de5e55
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 8 deletions.
38 changes: 35 additions & 3 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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,
Expand Down Expand Up @@ -4798,7 +4799,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)
Expand Down
109 changes: 109 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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: ...
4 changes: 2 additions & 2 deletions test-data/unit/check-modules.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-newsemanal.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/semanal-modules.test
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 1de5e55

Please sign in to comment.