Skip to content

Commit 5cd075f

Browse files
Tinchetushar-deepsource
authored andcommitted
attrs namedtuple (python#11794)
1 parent 75eb7de commit 5cd075f

File tree

5 files changed

+86
-10
lines changed

5 files changed

+86
-10
lines changed

mypy/plugin.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ class C: pass
124124
from mypy_extensions import trait, mypyc_attr
125125

126126
from mypy.nodes import (
127-
Expression, Context, ClassDef, SymbolTableNode, MypyFile, CallExpr, ArgKind
127+
Expression, Context, ClassDef, SymbolTableNode, MypyFile, CallExpr, ArgKind, TypeInfo
128128
)
129129
from mypy.tvar_scope import TypeVarLikeScope
130130
from mypy.types import (
@@ -274,6 +274,10 @@ def named_type_or_none(self, fullname: str,
274274
"""
275275
raise NotImplementedError
276276

277+
@abstractmethod
278+
def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance, line: int) -> TypeInfo:
279+
raise NotImplementedError
280+
277281
@abstractmethod
278282
def parse_bool(self, expr: Expression) -> Optional[bool]:
279283
"""Parse True/False literals."""

mypy/plugins/attrs.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
)
2323
from mypy.types import (
2424
TupleType, Type, AnyType, TypeOfAny, CallableType, NoneType, TypeVarType,
25-
Overloaded, UnionType, FunctionLike, get_proper_type,
25+
Overloaded, UnionType, FunctionLike, Instance, get_proper_type,
2626
)
2727
from mypy.typeops import make_simplified_union, map_type_from_supertype
2828
from mypy.typevars import fill_typevars
@@ -50,6 +50,8 @@
5050
}
5151

5252
SELF_TVAR_NAME: Final = "_AT"
53+
MAGIC_ATTR_NAME: Final = "__attrs_attrs__"
54+
MAGIC_ATTR_CLS_NAME: Final = "_AttrsAttributes" # The namedtuple subclass name.
5355

5456

5557
class Converter:
@@ -302,7 +304,7 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext',
302304
ctx.api.defer()
303305
return
304306

305-
_add_attrs_magic_attribute(ctx, raw_attr_types=[info[attr.name].type for attr in attributes])
307+
_add_attrs_magic_attribute(ctx, [(attr.name, info[attr.name].type) for attr in attributes])
306308
if slots:
307309
_add_slots(ctx, attributes)
308310

@@ -710,23 +712,36 @@ def _add_init(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute],
710712

711713

712714
def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext',
713-
raw_attr_types: 'List[Optional[Type]]') -> None:
714-
attr_name = '__attrs_attrs__'
715+
attrs: 'List[Tuple[str, Optional[Type]]]') -> None:
715716
any_type = AnyType(TypeOfAny.explicit)
716717
attributes_types: 'List[Type]' = [
717718
ctx.api.named_type_or_none('attr.Attribute', [attr_type or any_type]) or any_type
718-
for attr_type in raw_attr_types
719+
for _, attr_type in attrs
719720
]
720721
fallback_type = ctx.api.named_type('builtins.tuple', [
721722
ctx.api.named_type_or_none('attr.Attribute', [any_type]) or any_type,
722723
])
723-
var = Var(name=attr_name, type=TupleType(attributes_types, fallback=fallback_type))
724+
725+
ti = ctx.api.basic_new_typeinfo(MAGIC_ATTR_CLS_NAME, fallback_type, 0)
726+
ti.is_named_tuple = True
727+
for (name, _), attr_type in zip(attrs, attributes_types):
728+
var = Var(name, attr_type)
729+
var.is_property = True
730+
proper_type = get_proper_type(attr_type)
731+
if isinstance(proper_type, Instance):
732+
var.info = proper_type.type
733+
ti.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True)
734+
attributes_type = Instance(ti, [])
735+
736+
var = Var(name=MAGIC_ATTR_NAME, type=TupleType(attributes_types, fallback=attributes_type))
724737
var.info = ctx.cls.info
725-
var._fullname = ctx.cls.info.fullname + '.' + attr_name
726-
ctx.cls.info.names[attr_name] = SymbolTableNode(
738+
var.is_classvar = True
739+
var._fullname = f"{ctx.cls.fullname}.{MAGIC_ATTR_CLS_NAME}"
740+
ctx.cls.info.names[MAGIC_ATTR_NAME] = SymbolTableNode(
727741
kind=MDEF,
728742
node=var,
729743
plugin_generated=True,
744+
no_serialize=True,
730745
)
731746

732747

mypy/test/testfinegrained.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class FineGrainedSuite(DataSuite):
4949
'fine-grained-modules.test',
5050
'fine-grained-follow-imports.test',
5151
'fine-grained-suggest.test',
52+
'fine-grained-attr.test',
5253
]
5354

5455
# Whether to use the fine-grained cache in the testing. This is overridden

test-data/unit/check-attr.test

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1399,7 +1399,40 @@ class A:
13991399
b: int = attr.ib()
14001400
c: str = attr.ib()
14011401

1402-
reveal_type(A.__attrs_attrs__) # N: Revealed type is "Tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str]]"
1402+
reveal_type(A.__attrs_attrs__) # N: Revealed type is "Tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A._AttrsAttributes]"
1403+
reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[builtins.int]"
1404+
reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[builtins.int]"
1405+
A.__attrs_attrs__.x # E: "_AttrsAttributes" has no attribute "x"
1406+
1407+
[builtins fixtures/attr.pyi]
1408+
1409+
[case testAttrsBareClassHasAttributeWithAttributes]
1410+
import attr
1411+
1412+
@attr.s
1413+
class A:
1414+
b = attr.ib()
1415+
c = attr.ib()
1416+
1417+
reveal_type(A.__attrs_attrs__) # N: Revealed type is "Tuple[attr.Attribute[Any], attr.Attribute[Any], fallback=__main__.A._AttrsAttributes]"
1418+
reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[Any]"
1419+
reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[Any]"
1420+
A.__attrs_attrs__.x # E: "_AttrsAttributes" has no attribute "x"
1421+
1422+
[builtins fixtures/attr.pyi]
1423+
1424+
[case testAttrsNGClassHasAttributeWithAttributes]
1425+
import attr
1426+
1427+
@attr.define
1428+
class A:
1429+
b: int
1430+
c: str
1431+
1432+
reveal_type(A.__attrs_attrs__) # N: Revealed type is "Tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A._AttrsAttributes]"
1433+
reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[builtins.int]"
1434+
reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[builtins.int]"
1435+
A.__attrs_attrs__.x # E: "_AttrsAttributes" has no attribute "x"
14031436

14041437
[builtins fixtures/attr.pyi]
14051438

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[case updateMagicField]
2+
from attr import Attribute
3+
import m
4+
5+
def g() -> Attribute[int]:
6+
return m.A.__attrs_attrs__[0]
7+
8+
[file m.py]
9+
from attr import define
10+
11+
@define
12+
class A:
13+
a: int
14+
[file m.py.2]
15+
from attr import define
16+
17+
@define
18+
class A:
19+
a: float
20+
[builtins fixtures/attr.pyi]
21+
[out]
22+
==
23+
main:5: error: Incompatible return value type (got "Attribute[float]", expected "Attribute[int]")

0 commit comments

Comments
 (0)