Skip to content

Commit b1f5936

Browse files
author
hauntsaninja
committed
Fix more crashes in class scoped imports
More context in python#12197 This essentially walks back changes in python#12023 that apply to function-like things that are imported in class-scope. We just issue a (non-blocking) error and mark the type as Any. Inference for variables still works fine; I'm yet to think of any problems that could cause. A full fix seems quite difficult, but this is better than both a crash and a blocking error.
1 parent 1de5e55 commit b1f5936

File tree

4 files changed

+94
-60
lines changed

4 files changed

+94
-60
lines changed

mypy/semanal.py

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
reduce memory use).
4949
"""
5050

51-
import copy
5251
from contextlib import contextmanager
5352

5453
from typing import (
@@ -4791,6 +4790,48 @@ def add_module_symbol(self,
47914790
module_hidden=module_hidden
47924791
)
47934792

4793+
def _get_node_for_class_scoped_import(
4794+
self, name: str, symbol_node: Optional[SymbolNode], context: Context
4795+
) -> Optional[SymbolNode]:
4796+
if symbol_node is None:
4797+
return None
4798+
# I promise this type checks; I'm just making mypyc issues go away.
4799+
# mypyc is absolutely convinced that `symbol_node` narrows to a Var in the following,
4800+
# when it can also be a FuncBase. Once fixed, `f` in the following can be removed.
4801+
# See also https://github.com/mypyc/mypyc/issues/892
4802+
f = cast(Any, lambda x: x)
4803+
if isinstance(f(symbol_node), (FuncBase, Var)):
4804+
# For imports in class scope, we construct a new node to represent the symbol and
4805+
# set its `info` attribute to `self.type`.
4806+
existing = self.current_symbol_table().get(name)
4807+
if (
4808+
# The redefinition checks in `add_symbol_table_node` don't work for our
4809+
# constructed Var / FuncBase, so check for possible redefinitions here.
4810+
existing is not None
4811+
and isinstance(f(existing.node), (FuncBase, Var))
4812+
and (
4813+
isinstance(f(existing.type), AnyType)
4814+
or f(existing.type) == f(symbol_node).type
4815+
)
4816+
):
4817+
return existing.node
4818+
4819+
# Construct the new node
4820+
if isinstance(f(symbol_node), FuncBase):
4821+
# In theory we could construct a new node here as well, but in practice
4822+
# it doesn't work well, see #12197
4823+
typ = AnyType(TypeOfAny.from_error)
4824+
self.fail('Unsupported class scoped import', context)
4825+
else:
4826+
typ = f(symbol_node).type
4827+
symbol_node = Var(name, typ)
4828+
symbol_node._fullname = self.qualified_name(name)
4829+
assert self.type is not None # guaranteed by is_class_scope
4830+
symbol_node.info = self.type
4831+
symbol_node.line = context.line
4832+
symbol_node.column = context.column
4833+
return symbol_node
4834+
47944835
def add_imported_symbol(self,
47954836
name: str,
47964837
node: SymbolTableNode,
@@ -4803,32 +4844,7 @@ def add_imported_symbol(self,
48034844
symbol_node: Optional[SymbolNode] = node.node
48044845

48054846
if self.is_class_scope():
4806-
# I promise this type checks; I'm just making mypyc issues go away.
4807-
# mypyc is absolutely convinced that `symbol_node` narrows to a Var in the following,
4808-
# when it can also be a FuncBase. Once fixed, `f` in the following can be removed.
4809-
# See also https://github.com/mypyc/mypyc/issues/892
4810-
f = cast(Any, lambda x: x)
4811-
if isinstance(f(symbol_node), (FuncBase, Var)):
4812-
# For imports in class scope, we construct a new node to represent the symbol and
4813-
# set its `info` attribute to `self.type`.
4814-
existing = self.current_symbol_table().get(name)
4815-
if (
4816-
# The redefinition checks in `add_symbol_table_node` don't work for our
4817-
# constructed Var / FuncBase, so check for possible redefinitions here.
4818-
existing is not None
4819-
and isinstance(f(existing.node), (FuncBase, Var))
4820-
and f(existing.type) == f(symbol_node).type
4821-
):
4822-
symbol_node = existing.node
4823-
else:
4824-
# Construct the new node
4825-
constructed_node = copy.copy(f(symbol_node))
4826-
assert self.type is not None # guaranteed by is_class_scope
4827-
constructed_node.line = context.line
4828-
constructed_node.column = context.column
4829-
constructed_node.info = self.type
4830-
constructed_node._fullname = self.qualified_name(name)
4831-
symbol_node = constructed_node
4847+
symbol_node = self._get_node_for_class_scoped_import(name, symbol_node, context)
48324848

48334849
symbol = SymbolTableNode(node.kind, symbol_node,
48344850
module_public=module_public,

test-data/unit/check-classes.test

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7135,24 +7135,20 @@ class B(A): # E: Final class __main__.B has abstract attributes "foo"
71357135
class C:
71367136
class C1(XX): pass # E: Name "XX" is not defined
71377137

7138-
[case testClassScopeImportFunction]
7138+
[case testClassScopeImports]
71397139
class Foo:
7140-
from mod import foo
7140+
from mod import plain_function # E: Unsupported class scoped import
7141+
from mod import plain_var
71417142

7142-
reveal_type(Foo.foo) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int"
7143-
reveal_type(Foo().foo) # E: Invalid self argument "Foo" to attribute function "foo" with type "Callable[[int, int], int]" \
7144-
# N: Revealed type is "def (y: builtins.int) -> builtins.int"
7145-
[file mod.py]
7146-
def foo(x: int, y: int) -> int: ...
7143+
reveal_type(Foo.plain_function) # N: Revealed type is "Any"
7144+
reveal_type(Foo().plain_function) # N: Revealed type is "Any"
71477145

7148-
[case testClassScopeImportVariable]
7149-
class Foo:
7150-
from mod import foo
7146+
reveal_type(Foo.plain_var) # N: Revealed type is "builtins.int"
7147+
reveal_type(Foo().plain_var) # N: Revealed type is "builtins.int"
71517148

7152-
reveal_type(Foo.foo) # N: Revealed type is "builtins.int"
7153-
reveal_type(Foo().foo) # N: Revealed type is "builtins.int"
71547149
[file mod.py]
7155-
foo: int
7150+
def plain_function(x: int, y: int) -> int: ...
7151+
plain_var: int
71567152

71577153
[case testClassScopeImportModule]
71587154
class Foo:
@@ -7165,40 +7161,42 @@ foo: int
71657161

71667162
[case testClassScopeImportFunctionAlias]
71677163
class Foo:
7168-
from mod import foo
7164+
from mod import foo # E: Unsupported class scoped import
71697165
bar = foo
71707166

71717167
from mod import const_foo
71727168
const_bar = const_foo
71737169

7174-
reveal_type(Foo.foo) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int"
7175-
reveal_type(Foo.bar) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int"
7170+
reveal_type(Foo.foo) # N: Revealed type is "Any"
7171+
reveal_type(Foo.bar) # N: Revealed type is "Any"
71767172
reveal_type(Foo.const_foo) # N: Revealed type is "builtins.int"
71777173
reveal_type(Foo.const_bar) # N: Revealed type is "builtins.int"
7174+
71787175
[file mod.py]
71797176
def foo(x: int, y: int) -> int: ...
71807177
const_foo: int
71817178

71827179
[case testClassScopeImportModuleStar]
71837180
class Foo:
7184-
from mod import *
7181+
from mod import * # E: Unsupported class scoped import
71857182

71867183
reveal_type(Foo.foo) # N: Revealed type is "builtins.int"
7187-
reveal_type(Foo.bar) # N: Revealed type is "def (x: builtins.int) -> builtins.int"
7184+
reveal_type(Foo.bar) # N: Revealed type is "Any"
71887185
reveal_type(Foo.baz) # E: "Type[Foo]" has no attribute "baz" \
71897186
# N: Revealed type is "Any"
7187+
71907188
[file mod.py]
71917189
foo: int
71927190
def bar(x: int) -> int: ...
71937191

71947192
[case testClassScopeImportFunctionNested]
71957193
class Foo:
71967194
class Bar:
7197-
from mod import baz
7195+
from mod import baz # E: Unsupported class scoped import
7196+
7197+
reveal_type(Foo.Bar.baz) # N: Revealed type is "Any"
7198+
reveal_type(Foo.Bar().baz) # N: Revealed type is "Any"
71987199

7199-
reveal_type(Foo.Bar.baz) # N: Revealed type is "def (x: builtins.int) -> builtins.int"
7200-
reveal_type(Foo.Bar().baz) # E: Invalid self argument "Bar" to attribute function "baz" with type "Callable[[int], int]" \
7201-
# N: Revealed type is "def () -> builtins.int"
72027200
[file mod.py]
72037201
def baz(x: int) -> int: ...
72047202

@@ -7221,25 +7219,44 @@ def foo(x: int, y: int) -> int: ...
72217219

72227220
[case testClassScopeImportVarious]
72237221
class Foo:
7224-
from mod1 import foo
7225-
from mod2 import foo # E: Name "foo" already defined on line 2
7222+
from mod1 import foo # E: Unsupported class scoped import
7223+
from mod2 import foo
72267224

7227-
from mod1 import meth1
7225+
from mod1 import meth1 # E: Unsupported class scoped import
72287226
def meth1(self, a: str) -> str: ... # E: Name "meth1" already defined on line 5
72297227

72307228
def meth2(self, a: str) -> str: ...
7231-
from mod1 import meth2 # E: Name "meth2" already defined on line 8
7229+
from mod1 import meth2 # E: Unsupported class scoped import \
7230+
# E: Name "meth2" already defined on line 8
72327231

72337232
class Bar:
7234-
from mod1 import foo
7233+
from mod1 import foo # E: Unsupported class scoped import
72357234

72367235
import mod1
7237-
reveal_type(Foo.foo) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int"
7238-
reveal_type(Bar.foo) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int"
7236+
reveal_type(Foo.foo) # N: Revealed type is "Any"
7237+
reveal_type(Bar.foo) # N: Revealed type is "Any"
72397238
reveal_type(mod1.foo) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int"
7239+
72407240
[file mod1.py]
72417241
def foo(x: int, y: int) -> int: ...
72427242
def meth1(x: int) -> int: ...
72437243
def meth2(x: int) -> int: ...
72447244
[file mod2.py]
72457245
def foo(z: str) -> int: ...
7246+
7247+
7248+
[case testClassScopeImportWithError]
7249+
class Foo:
7250+
from mod import meth1 # E: Unsupported class scoped import
7251+
from mod import meth2 # E: Unsupported class scoped import
7252+
7253+
[file mod.pyi]
7254+
from typing import Any, TypeVar, overload
7255+
7256+
@overload
7257+
def meth1(self: Any, y: int) -> int: ...
7258+
@overload
7259+
def meth1(self: Any, y: str) -> str: ...
7260+
7261+
T = TypeVar("T")
7262+
def meth2(self: Any, y: T) -> T: ...

test-data/unit/check-modules.test

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,10 @@ def f() -> None: pass
131131
[case testImportWithinClassBody2]
132132
import typing
133133
class C:
134-
from m import f # E: Method must have at least one argument
134+
from m import f # E: Unsupported class scoped import
135135
f()
136-
f(C) # E: Too many arguments for "f" of "C"
136+
# ideally, the following should error:
137+
f(C)
137138
[file m.py]
138139
def f() -> None: pass
139140
[out]

test-data/unit/check-newsemanal.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2722,7 +2722,7 @@ import m
27222722

27232723
[file m.py]
27242724
class C:
2725-
from mm import f # E: Method must have at least one argument
2725+
from mm import f # E: Unsupported class scoped import
27262726
@dec(f)
27272727
def m(self): pass
27282728

@@ -2742,7 +2742,7 @@ import m
27422742

27432743
[file m/__init__.py]
27442744
class C:
2745-
from m.m import f # E: Method must have at least one argument
2745+
from m.m import f # E: Unsupported class scoped import
27462746
@dec(f)
27472747
def m(self): pass
27482748

0 commit comments

Comments
 (0)