8000 Fix crash on Any metaclass in incremental mode (#14495) · python/mypy@e8c844b · GitHub
[go: up one dir, main page]

Skip to content

Commit e8c844b

Browse files
authored
Fix crash on Any metaclass in incremental mode (#14495)
Fixes #14254 This essentially re-implements #13605 in a simpler way that also works in incremental mode. Also I decided to set `meta_fallback_to_any` in case of errors, to match how we do this for base classes.
1 parent cc1bcc9 commit e8c844b

File tree

8 files changed

+52
-23
lines changed

8 files changed

+52
-23
lines changed

mypy/checkmember.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,7 @@ def analyze_class_attribute_access(
901901
# For modules use direct symbol table lookup.
902902
if not itype.extra_attrs.mod_name:
903903
return itype.extra_attrs.attrs[name]
904-
if info.fallback_to_any:
904+
if info.fallback_to_any or info.meta_fallback_to_any:
905905
return apply_class_attr_hook(mx, hook, AnyType(TypeOfAny.special_form))
906906
return None
907907

mypy/nodes.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2800,6 +2800,7 @@ class is generic then it will be a type constructor of higher kind.
28002800
"inferring",
28012801
"is_enum",
28022802
"fallback_to_any",
2803+
"meta_fallback_to_any",
28032804
"type_vars",
28042805
"has_param_spec_type",
28052806
"bases",
@@ -2894,6 +2895,10 @@ class is generic then it will be a type constructor of higher kind.
28942895
# (and __setattr__), but without the __getattr__ method.
28952896
fallback_to_any: bool
28962897

2898+
# Same as above but for cases where metaclass has type Any. This will suppress
2899+
# all attribute errors only for *class object* access.
2900+
meta_fallback_to_any: bool
2901+
28972902
# Information related to type annotations.
28982903

28992904
# Generic type variable names (full names)
@@ -2963,6 +2968,7 @@ class is generic then it will be a type constructor of higher kind.
29632968
"is_abstract",
29642969
"is_enum",
29652970
"fallback_to_any",
2971+
"meta_fallback_to_any",
29662972
"is_named_tuple",
29672973
"is_newtype",
29682974
"is_protocol",
@@ -3002,6 +3008,7 @@ def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None
30023008
self.is_final = False
30033009
self.is_enum = False
30043010
self.fallback_to_any = False
3011+
self.meta_fallback_to_any = False
30053012
self._promote = []
30063013
self.alt_promote = None
30073014
self.tuple_type = None

mypy/semanal.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,7 +1579,9 @@ def analyze_class(self, defn: ClassDef) -> None:
15791579
self.mark_incomplete(defn.name, defn)
15801580
return
15811581

1582-
declared_metaclass, should_defer = self.get_declared_metaclass(defn.name, defn.metaclass)
1582+
declared_metaclass, should_defer, any_meta = self.get_declared_metaclass(
1583+
defn.name, defn.metaclass
1584+
)
15831585
if should_defer or self.found_incomplete_ref(tag):
15841586
# Metaclass was not ready. Defer current target.
15851587
self.mark_incomplete(defn.name, defn)
@@ -1599,6 +1601,8 @@ def analyze_class(self, defn: ClassDef) -> None:
15991601
self.setup_type_vars(defn, tvar_defs)
16001602
if base_error:
16011603
defn.info.fallback_to_any = True
1604+
if any_meta:
1605+
defn.info.meta_fallback_to_any = True
16021606

16031607
with self.scope.class_scope(defn.info):
16041608
self.configure_base_classes(defn, base_types)
@@ -2247,8 +2251,17 @@ def is_base_class(self, t: TypeInfo, s: TypeInfo) -> bool:
22472251

22482252
def get_declared_metaclass(
22492253
self, name: str, metaclass_expr: Expression | None
2250-
) -> tuple[Instance | None, bool]:
2251-
"""Returns either metaclass instance or boolean whether we should defer."""
2254+
) -> tuple[Instance | None, bool, bool]:
2255+
"""Get declared metaclass from metaclass expression.
2256+
2257+
Returns a tuple of three values:
2258+
* A metaclass instance or None
2259+
* A boolean indicating whether we should defer
2260+
* A boolean indicating whether we should set metaclass Any fallback
2261+
(either for Any metaclass or invalid/dynamic metaclass).
2262+
2263+
The two boolean flags can only be True if instance is None.
2264+
"""
22522265
declared_metaclass = None
22532266
if metaclass_expr:
22542267
metaclass_name = None
@@ -2258,25 +2271,20 @@ def get_declared_metaclass(
22582271
metaclass_name = get_member_expr_fullname(metaclass_expr)
22592272
if metaclass_name is None:
22602273
self.fail(f'Dynamic metaclass not supported for "{name}"', metaclass_expr)
2261-
return None, False
2274+
return None, False, True
22622275
sym = self.lookup_qualified(metaclass_name, metaclass_expr)
22632276
if sym is None:
22642277
# Probably a name error - it is already handled elsewhere
2265-
return None, False
2278+
return None, False, True
22662279
if isinstance(sym.node, Var) and isinstance(get_proper_type(sym.node.type), AnyType):
2267-
# Create a fake TypeInfo that fallbacks to `Any`, basically allowing
2268-
# all the attributes. Same thing as we do for `Any` base class.
2269-
any_info = self.make_empty_type_info(ClassDef(sym.node.name, Block([])))
2270-
any_info.fallback_to_any = True
2271-
any_info._fullname = sym.node.fullname
22722280
if self.options.disallow_subclassing_any:
22732281
self.fail(
2274-
f'Class cannot use "{any_info.fullname}" as a metaclass (has type "Any")',
2282+
f'Class cannot use "{sym.node.name}" as a metaclass (has type "Any")',
22752283
metaclass_expr,
22762284
)
2277-
return Instance(any_info, []), False
2285+
return None, False, True
22782286
if isinstance(sym.node, PlaceholderNode):
2279-
return None, True # defer later in the caller
2287+
return None, True, False # defer later in the caller
22802288

22812289
# Support type aliases, like `_Meta: TypeAlias = type`
22822290
if (
@@ -2291,16 +2299,16 @@ def get_declared_metaclass(
22912299

22922300
if not isinstance(metaclass_info, TypeInfo) or metaclass_info.tuple_type is not None:
22932301
self.fail(f'Invalid metaclass "{metaclass_name}"', metaclass_expr)
2294-
return None, False
2302+
return None, False, False
22952303
if not metaclass_info.is_metaclass():
22962304
self.fail(
22972305
'Metaclasses not inheriting from "type" are not supported', metaclass_expr
22982306
)
2299-
return None, False
2307+
return None, False, False
23002308
inst = fill_typevars(metaclass_info)
23012309
assert isinstance(inst, Instance)
23022310
declared_metaclass = inst
2303-
return declared_metaclass, False
2311+
return declared_metaclass, False, False
23042312

23052313
def recalculate_metaclass(self, defn: ClassDef, declared_metaclass: Instance | None) -> None:
23062314
defn.info.declared_metaclass = declared_metaclass

mypy/server/astdiff.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ def snapshot_definition(node: SymbolNode | None, common: tuple[object, ...]) ->
255255
node.is_enum,
256256
node.is_protocol,
257257
node.fallback_to_any,
258+
node.meta_fallback_to_any,
258259
node.is_named_tuple,
259260
node.is_newtype,
260261
# We need this to e.g. trigger metaclass calculation in subclasses.

mypy/subtypes.py

Lines changed: 1 addition & 1 deletion
< 10000 div data-testid="deletion diffstat" class="DiffSquares-module__diffSquare--h5kjy DiffSquares-module__deletion--hKV3q">
Original file line numberDiff line numberDiff line change
@@ -1167,7 +1167,7 @@ def find_member(
11671167
if isinstance(getattr_type, CallableType):
11681168
return getattr_type.ret_type
11691169
return getattr_type
1170-
if itype.type.fallback_to_any:
1170+
if itype.type.fallback_to_any or class_obj and itype.type.meta_fallback_to_any:
11711171
return AnyType(TypeOfAny.special_form)
11721172
if isinstance(v, TypeInfo):
11731173
# PEP 544 doesn't specify anything about such use cases. So we just try

test-data/unit/check-classes.test

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4439,7 +4439,7 @@ def f(TB: Type[B]):
44394439
reveal_type(TB.x) # N: Revealed type is "builtins.int"
44404440

44414441
[case testMetaclassAsAny]
4442-
from typing import Any, ClassVar
4442+
from typing import Any, ClassVar, Type
44434443

44444444
MyAny: Any
44454445
class WithMeta(metaclass=MyAny):
@@ -4451,13 +4451,15 @@ reveal_type(WithMeta.x) # N: Revealed type is "builtins.int"
44514451
reveal_type(WithMeta().x) # N: Revealed type is "builtins.int"
44524452
WithMeta().m # E: "WithMeta" has no attribute "m"
44534453
WithMeta().a # E: "WithMeta" has no attribute "a"
4454+
t: Type[WithMeta]
4455+
t.unknown # OK
44544456

44554457
[case testMetaclassAsAnyWithAFlag]
44564458
# flags: --disallow-subclassing-any
4457-
from typing import Any, ClassVar
4459+
from typing import Any, ClassVar, Type
44584460

44594461
MyAny: Any
4460-
class WithMeta(metaclass=MyAny): # E: Class cannot use "__main__.MyAny" as a metaclass (has type "Any")
4462+
class WithMeta(metaclass=MyAny): # E: Class cannot use "MyAny" as a metaclass (has type "Any")
44614463
x: ClassVar[int]
44624464

44634465
reveal_type(WithMeta.a) # N: Revealed type is "Any"
@@ -4466,6 +4468,8 @@ reveal_type(WithMeta.x) # N: Revealed type is "builtins.int"
44664468
reveal_type(WithMeta().x) # N: Revealed type is "builtins.int"
44674469
WithMeta().m # E: "WithMeta" has no attribute "m"
44684470
WithMeta().a # E: "WithMeta" has no attribute "a"
4471+
t: Type[WithMeta]
4472+
t.unknown # OK
44694473

44704474
[case testMetaclassIterable]
44714475
from typing import Iterable, Iterator

test-data/unit/check-incremental.test

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6348,3 +6348,14 @@ class C(B):
63486348
self.x = self.foo()
63496349
[out]
63506350
[out2]
6351+
6352+
[case testNoCrashIncrementalMetaAny]
6353+
import a
6354+
[file a.py]
6355+
from m import Foo
6356+
[file a.py.2]
6357+
from m import Foo
6358+
# touch
6359+
[file m.py]
6360+
from missing_module import Meta # type: ignore[import]
6361+
class Foo(metaclass=Meta): ...

test-data/unit/fine-grained.test

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3124,7 +3124,6 @@ whatever: int
31243124
[out]
31253125
==
31263126
b.py:2: error: Name "c.M" is not defined
3127-
a.py:3: error: "Type[B]" has no attribute "x"
31283127

31293128
[case testFixMissingMetaclass]
31303129
import a
@@ -3143,7 +3142,6 @@ class M(type):
31433142
x: int
31443143
[out]
31453144
b.py:2: error: Name "c.M" is not defined
3146-
a.py:3: error: "Type[B]" has no attribute "x"
31473145
==
31483146

31493147
[case testGoodMetaclassSpoiled]

0 commit comments

Comments
 (0)
0