Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7576f65

Browse filesBrowse files
authored
Add __dataclass_fields__ and __attrs_attrs__ to dataclasses (#8578)
Fixes #6568.
1 parent fbedea5 commit 7576f65
Copy full SHA for 7576f65
Expand file treeCollapse file tree

15 files changed

+228
-80
lines changed

‎mypy/plugin.py

Copy file name to clipboardExpand all lines: mypy/plugin.py
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,18 @@ def named_type(self, qualified_name: str, args: Optional[List[Type]] = None) ->
254254
"""Construct an instance of a builtin type with given type arguments."""
255255
raise NotImplementedError
256256

257+
@abstractmethod
258+
def named_type_or_none(self,
259+
qualified_name: str,
260+
args: Optional[List[Type]] = None) -> Optional[Instance]:
261+
"""Construct an instance of a type with given type arguments.
262+
263+
Return None if a type could not be constructed for the qualified
264+
type name. This is possible when the qualified name includes a
265+
module name and the module has not been imported.
266+
"""
267+
raise NotImplementedError
268+
257269
@abstractmethod
258270
def parse_bool(self, expr: Expression) -> Optional[bool]:
259271
"""Parse True/False literals."""

‎mypy/plugins/attrs.py

Copy file name to clipboardExpand all lines: mypy/plugins/attrs.py
+25-2Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
deserialize_and_fixup_type
2222
)
2323
from mypy.types import (
24-
Type, AnyType, TypeOfAny, CallableType, NoneType, TypeVarType,
25-
Overloaded, UnionType, FunctionLike, get_proper_type
24+
TupleType, Type, AnyType, TypeOfAny, CallableType, NoneType, TypeVarType,
25+
Overloaded, UnionType, FunctionLike, get_proper_type,
2626
)
2727
from mypy.typeops import make_simplified_union, map_type_from_supertype
2828
from mypy.typevars import fill_typevars
@@ -300,6 +300,8 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext',
300300
ctx.api.defer()
301301
return
302302

303+
_add_attrs_magic_attribute(ctx, raw_attr_types=[info[attr.name].type for attr in attributes])
304+
303305
# Save the attributes so that subclasses can reuse them.
304306
ctx.cls.info.metadata['attrs'] = {
305307
'attributes': [attr.serialize() for attr in attributes],
@@ -703,6 +705,27 @@ def _add_init(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute],
703705
adder.add_method('__init__', args, NoneType())
704706

705707

708+
def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext',
709+
raw_attr_types: 'List[Optional[Type]]') -> None:
710+
attr_name = '__attrs_attrs__'
711+
any_type = AnyType(TypeOfAny.explicit)
712+
attributes_types: 'List[Type]' = [
713+
ctx.api.named_type_or_none('attr.Attribute', [attr_type or any_type]) or any_type
714+
for attr_type in raw_attr_types
715+
]
716+
fallback_type = ctx.api.named_type('__builtins__.tuple', [
717+
ctx.api.named_type_or_none('attr.Attribute', [any_type]) or any_type,
718+
])
719+
var = Var(name=attr_name, type=TupleType(attributes_types, fallback=fallback_type))
720+
var.info = ctx.cls.info
721+
var._fullname = ctx.cls.info.fullname + '.' + attr_name
722+
ctx.cls.info.names[attr_name] = SymbolTableNode(
723+
kind=MDEF,
724+
node=var,
725+
plugin_generated=True,
726+
)
727+
728+
706729
class MethodAdder:
707730
"""Helper to add methods to a TypeInfo.
708731

‎mypy/plugins/dataclasses.py

Copy file name to clipboardExpand all lines: mypy/plugins/dataclasses.py
+23-1Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
add_method, _get_decorator_bool_argument, deserialize_and_fixup_type,
1414
)
1515
from mypy.typeops import map_type_from_supertype
16-
from mypy.types import Type, Instance, NoneType, TypeVarType, CallableType, get_proper_type
16+
from mypy.types import (
17+
Type, Instance, NoneType, TypeVarType, CallableType, get_proper_type,
18+
AnyType, TypeOfAny,
19+
)
1720
from mypy.server.trigger import make_wildcard_trigger
1821

1922
# The set of decorators that generate dataclasses.
@@ -187,6 +190,8 @@ def transform(self) -> None:
187190

188191
self.reset_init_only_vars(info, attributes)
189192

193+
self._add_dataclass_fields_magic_attribute()
194+
190195
info.metadata['dataclass'] = {
191196
'attributes': [attr.serialize() for attr in attributes],
192197
'frozen': decorator_arguments['frozen'],
@@ -417,6 +422,23 @@ def _is_kw_only_type(self, node: Optional[Type]) -> bool:
417422
return False
418423
return node_type.type.fullname == 'dataclasses.KW_ONLY'
419424

425+
def _add_dataclass_fields_magic_attribute(self) -> None:
426+
attr_name = '__dataclass_fields__'
427+
any_type = AnyType(TypeOfAny.explicit)
428+
field_type = self._ctx.api.named_type_or_none('dataclasses.Field', [any_type]) or any_type
429+
attr_type = self._ctx.api.named_type('__builtins__.dict', [
430+
self._ctx.api.named_type('__builtins__.str'),
431+
field_type,
432+
])
433+
var = Var(name=attr_name, type=attr_type)
434+
var.info = self._ctx.cls.info
435+
var._fullname = self._ctx.cls.info.fullname + '.' + attr_name
436+
self._ctx.cls.info.names[attr_name] = SymbolTableNode(
437+
kind=MDEF,
438+
node=var,
439+
plugin_generated=True,
440+
)
441+
420442

421443
def dataclass_class_maker_callback(ctx: ClassDefContext) -> None:
422444
"""Hooks into the class typechecking process to add support for dataclasses.

‎test-data/unit/check-attr.test

Copy file name to clipboardExpand all lines: test-data/unit/check-attr.test
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,3 +1390,15 @@ class B(A):
13901390

13911391
reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> __main__.B"
13921392
[builtins fixtures/bool.pyi]
1393+
1394+
[case testAttrsClassHasAttributeWithAttributes]
1395+
import attr
1396+
1397+
@attr.s
1398+
class A:
1399+
b: int = attr.ib()
1400+
c: str = attr.ib()
1401+
1402+
reveal_type(A.__attrs_attrs__) # N: Revealed type is "Tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str]]"
1403+
1404+
[builtins fixtures/attr.pyi]

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.