Skip to content

Commit c13cf6d

Browse files
committed
Fix crashed related to forward references in attrs classes
This is similar to #12762, but for attrs classes.
1 parent 8faf44a commit c13cf6d

File tree

3 files changed

+40
-27
lines changed

3 files changed

+40
-27
lines changed

mypy/plugins/attrs.py

+10-11
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ def _get_decorator_optional_bool_argument(
260260

261261
def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext',
262262
auto_attribs_default: Optional[bool] = False,
263-
frozen_default: bool = False) -> None:
263+
frozen_default: bool = False) -> bool:
264264
"""Add necessary dunder methods to classes decorated with attr.s.
265265
266266
attrs is a package that lets you define classes without writing dull boilerplate code.
@@ -286,27 +286,24 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext',
286286
if ctx.api.options.python_version[0] < 3:
287287
if auto_attribs:
288288
ctx.api.fail("auto_attribs is not supported in Python 2", ctx.reason)
289-
return
289+
return True
290290
if not info.defn.base_type_exprs:
291291
# Note: This will not catch subclassing old-style classes.
292292
ctx.api.fail("attrs only works with new-style classes", info.defn)
293-
return
293+
return True
294294
if kw_only:
295295
ctx.api.fail(KW_ONLY_PYTHON_2_UNSUPPORTED, ctx.reason)
296-
return
296+
return True
297297

298298
attributes = _analyze_class(ctx, auto_attribs, kw_only)
299299

300300
# Check if attribute types are ready.
301301
for attr in attributes:
302302
node = info.get(attr.name)
303-
if node is None:
304-
# This name is likely blocked by a star import. We don't need to defer because
305-
# defer() is already called by mark_incomplete().
306-
return
307-
if node.type is None and not ctx.api.final_iteration:
308-
ctx.api.defer()
309-
return
303+
if node is None or node.type is None:
304+
# This name is likely blocked by some semantic analysis error that
305+
# should have been reported already.
306+
return True
310307

311308
_add_attrs_magic_attribute(ctx, [(attr.name, info[attr.name].type) for attr in attributes])
312309
if slots:
@@ -330,6 +327,8 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext',
330327
if frozen:
331328
_make_frozen(ctx, attributes)
332329

330+
return True
331+
333332

334333
def _get_frozen(ctx: 'mypy.plugin.ClassDefContext', frozen_default: bool) -> bool:
335334
"""Return whether this class is frozen."""

mypy/plugins/default.py

+16-16
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,24 @@ def get_attribute_hook(self, fullname: str
9494

9595
def get_class_decorator_hook(self, fullname: str
9696
) -> Optional[Callable[[ClassDefContext], None]]:
97-
from mypy.plugins import attrs
9897
from mypy.plugins import dataclasses
9998

100-
if fullname in attrs.attr_class_makers:
99+
if fullname in dataclasses.dataclass_makers:
100+
return dataclasses.dataclass_tag_callback
101+
102+
return None
103+
104+
def get_class_decorator_hook_2(self, fullname: str
105+
) -> Optional[Callable[[ClassDefContext], bool]]:
106+
from mypy.plugins import dataclasses
107+
from mypy.plugins import functools
108+
from mypy.plugins import attrs
109+
110+
if fullname in dataclasses.dataclass_makers:
111+
return dataclasses.dataclass_class_maker_callback
112+
elif fullname in functools.functools_total_ordering_makers:
113+
return functools.functools_total_ordering_maker_callback
114+
elif fullname in attrs.attr_class_makers:
101115
return attrs.attr_class_maker_callback
102116
elif fullname in attrs.attr_dataclass_makers:
103117
return partial(
@@ -115,20 +129,6 @@ def get_class_decorator_hook(self, fullname: str
115129
attrs.attr_class_maker_callback,
116130
auto_attribs_default=None,
117131
)
118-
elif fullname in dataclasses.dataclass_makers:
119-
return dataclasses.dataclass_tag_callback
120-
121-
return None
122-
123-
def get_class_decorator_hook_2(self, fullname: str
124-
) -> Optional[Callable[[ClassDefContext], bool]]:
125-
from mypy.plugins import dataclasses
126-
from mypy.plugins import functools
127-
128-
if fullname in dataclasses.dataclass_makers:
129-
return dataclasses.dataclass_class_maker_callback
130-
elif fullname in functools.functools_total_ordering_makers:
131-
return functools.functools_total_ordering_maker_callback
132132

133133
return None
134134

test-data/unit/check-attr.test

+14
Original file line numberDiff line numberDiff line change
@@ -1591,3 +1591,17 @@ class B:
15911591
class AB(A, B):
15921592
pass
15931593
[builtins fixtures/attr.pyi]
1594+
1595+
[case testAttrsForwardReferenceInTypeVarBound]
1596+
from typing import TypeVar, Generic
1597+
import attr
1598+
1599+
T = TypeVar("T", bound="C")
1600+
1601+
@attr.define
1602+
class D(Generic[T]):
1603+
x: int
1604+
1605+
class C:
1606+
pass
1607+
[builtins fixtures/attr.pyi]

0 commit comments

Comments
 (0)