Skip to content

Commit 3158677

Browse files
authored
Fix more crashes in class scoped imports (#12199)
Fixes #12197 This essentially walks back changes in #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 5b4ffa9 commit 3158677

File tree

5 files changed

+129
-70
lines changed

5 files changed

+129
-70
lines changed

mypy/semanal.py

+43-27
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), f(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: Optional[Type] = 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

+78-37
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:
@@ -7163,42 +7159,64 @@ reveal_type(Foo.mod.foo) # N: Revealed type is "builtins.int"
71637159
[file mod.py]
71647160
foo: int
71657161

7166-
[case testClassScopeImportFunctionAlias]
7162+
[case testClassScopeImportAlias]
71677163
class Foo:
7168-
from mod import foo
7169-
bar = foo
7164+
from mod import function # E: Unsupported class scoped import
7165+
foo = function
71707166

7171-
from mod import const_foo
7172-
const_bar = const_foo
7167+
from mod import var1
7168+
bar = var1
7169+
7170+
from mod import var2
7171+
baz = var2
7172+
7173+
from mod import var3
7174+
qux = var3
7175+
7176+
reveal_type(Foo.foo) # N: Revealed type is "Any"
7177+
reveal_type(Foo.function) # N: Revealed type is "Any"
7178+
7179+
reveal_type(Foo.bar) # N: Revealed type is "builtins.int"
7180+
reveal_type(Foo.var1) # N: Revealed type is "builtins.int"
7181+
7182+
reveal_type(Foo.baz) # N: Revealed type is "mod.C"
7183+
reveal_type(Foo.var2) # N: Revealed type is "mod.C"
7184+
7185+
reveal_type(Foo.qux) # N: Revealed type is "builtins.int"
7186+
reveal_type(Foo.var3) # N: Revealed type is "builtins.int"
71737187

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"
7176-
reveal_type(Foo.const_foo) # N: Revealed type is "builtins.int"
7177-
reveal_type(Foo.const_bar) # N: Revealed type is "builtins.int"
71787188
[file mod.py]
7179-
def foo(x: int, y: int) -> int: ...
7180-
const_foo: int
7189+
def function(x: int, y: int) -> int: ...
7190+
var1: int
7191+
7192+
class C: ...
7193+
var2: C
7194+
7195+
A = int
7196+
var3: A
7197+
71817198

71827199
[case testClassScopeImportModuleStar]
71837200
class Foo:
7184-
from mod import *
7201+
from mod import * # E: Unsupported class scoped import
71857202

71867203
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"
7204+
reveal_type(Foo.bar) # N: Revealed type is "Any"
71887205
reveal_type(Foo.baz) # E: "Type[Foo]" has no attribute "baz" \
71897206
# N: Revealed type is "Any"
7207+
71907208
[file mod.py]
71917209
foo: int
71927210
def bar(x: int) -> int: ...
71937211

71947212
[case testClassScopeImportFunctionNested]
71957213
class Foo:
71967214
class Bar:
7197-
from mod import baz
7215+
from mod import baz # E: Unsupported class scoped import
7216+
7217+
reveal_type(Foo.Bar.baz) # N: Revealed type is "Any"
7218+
reveal_type(Foo.Bar().baz) # N: Revealed type is "Any"
71987219

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"
72027220
[file mod.py]
72037221
def baz(x: int) -> int: ...
72047222

@@ -7221,25 +7239,48 @@ def foo(x: int, y: int) -> int: ...
72217239

72227240
[case testClassScopeImportVarious]
72237241
class Foo:
7224-
from mod1 import foo
7225-
from mod2 import foo # E: Name "foo" already defined on line 2
7242+
from mod1 import foo # E: Unsupported class scoped import
7243+
from mod2 import foo
72267244

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

72307248
def meth2(self, a: str) -> str: ...
7231-
from mod1 import meth2 # E: Name "meth2" already defined on line 8
7249+
from mod1 import meth2 # E: Unsupported class scoped import \
7250+
# E: Name "meth2" already defined on line 8
72327251

72337252
class Bar:
7234-
from mod1 import foo
7253+
from mod1 import foo # E: Unsupported class scoped import
72357254

72367255
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"
7256+
reveal_type(Foo.foo) # N: Revealed type is "Any"
7257+
reveal_type(Bar.foo) # N: Revealed type is "Any"
72397258
reveal_type(mod1.foo) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> builtins.int"
7259+
72407260
[file mod1.py]
72417261
def foo(x: int, y: int) -> int: ...
72427262
def meth1(x: int) -> int: ...
72437263
def meth2(x: int) -> int: ...
72447264
[file mod2.py]
72457265
def foo(z: str) -> int: ...
7266+
7267+
7268+
[case testClassScopeImportWithError]
7269+
class Foo:
7270+
from mod import meth1 # E: Unsupported class scoped import
7271+
from mod import meth2 # E: Unsupported class scoped import
7272+
from mod import T
7273+
7274+
reveal_type(Foo.T) # E: Type variable "Foo.T" cannot be used as an expression \
7275+
# N: Revealed type is "Any"
7276+
7277+
[file mod.pyi]
7278+
from typing import Any, TypeVar, overload
7279+
7280+
@overload
7281+
def meth1(self: Any, y: int) -> int: ...
7282+
@overload
7283+
def meth1(self: Any, y: str) -> str: ...
7284+
7285+
T = TypeVar("T")
7286+
def meth2(self: Any, y: T) -> T: ...

test-data/unit/check-modules.test

+3-2
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

+2-2
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

test-data/unit/fine-grained-modules.test

+3-2
Original file line numberDiff line numberDiff line change
@@ -1509,11 +1509,12 @@ class C: pass
15091509
main:3: error: Name "f" is not defined
15101510
main:4: error: Name "C" is not defined
15111511
==
1512-
main:3: error: Missing positional argument "x" in call to "f"
1512+
main:2: error: Unsupported class scoped import
15131513
main:4: error: Name "C" is not defined
15141514
==
1515-
main:3: error: Missing positional argument "x" in call to "f"
1515+
main:2: error: Unsupported class scoped import
15161516
==
1517+
main:2: error: Unsupported class scoped import
15171518

15181519
[case testImportStarAddMissingDependencyInsidePackage1]
15191520
from p.b import f

0 commit comments

Comments
 (0)