Skip to content

attrs namedtuple #11794

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion mypy/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class C: pass
from mypy_extensions import trait, mypyc_attr

from mypy.nodes import (
Expression, Context, ClassDef, SymbolTableNode, MypyFile, CallExpr, ArgKind
Expression, Context, ClassDef, SymbolTableNode, MypyFile, CallExpr, ArgKind, TypeInfo
)
from mypy.tvar_scope import TypeVarLikeScope
from mypy.types import (
Expand Down Expand Up @@ -268,6 +268,10 @@ def named_type_or_none(self, fullname: str,
"""
raise NotImplementedError

@abstractmethod
def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance, line: int) -> TypeInfo:
raise NotImplementedError

@abstractmethod
def parse_bool(self, expr: Expression) -> Optional[bool]:
"""Parse True/False literals."""
Expand Down
31 changes: 23 additions & 8 deletions mypy/plugins/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
)
from mypy.types import (
TupleType, Type, AnyType, TypeOfAny, CallableType, NoneType, TypeVarType,
Overloaded, UnionType, FunctionLike, get_proper_type,
Overloaded, UnionType, FunctionLike, Instance, get_proper_type,
)
from mypy.typeops import make_simplified_union, map_type_from_supertype
from mypy.typevars import fill_typevars
Expand Down Expand Up @@ -50,6 +50,8 @@
}

SELF_TVAR_NAME: Final = "_AT"
MAGIC_ATTR_NAME: Final = "__attrs_attrs__"
MAGIC_ATTR_CLS_NAME: Final = "_AttrsAttributes" # The namedtuple subclass name.


class Converter:
Expand Down Expand Up @@ -302,7 +304,7 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext',
ctx.api.defer()
return

_add_attrs_magic_attribute(ctx, raw_attr_types=[info[attr.name].type for attr in attributes])
_add_attrs_magic_attribute(ctx, [(attr.name, info[attr.name].type) for attr in attributes])
if slots:
_add_slots(ctx, attributes)

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


def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext',
raw_attr_types: 'List[Optional[Type]]') -> None:
attr_name = '__attrs_attrs__'
attrs: 'List[Tuple[str, Optional[Type]]]') -> None:
any_type = AnyType(TypeOfAny.explicit)
attributes_types: 'List[Type]' = [
ctx.api.named_type_or_none('attr.Attribute', [attr_type or any_type]) or any_type
for attr_type in raw_attr_types
for _, attr_type in attrs
]
fallback_type = ctx.api.named_type('builtins.tuple', [
ctx.api.named_type_or_none('attr.Attribute', [any_type]) or any_type,
])
var = Var(name=attr_name, type=TupleType(attributes_types, fallback=fallback_type))

ti = ctx.api.basic_new_typeinfo(MAGIC_ATTR_CLS_NAME, fallback_type, 0)
ti.is_named_tuple = True
for (name, _), attr_type in zip(attrs, attributes_types):
var = Var(name, attr_type)
var.is_property = True
proper_type = get_proper_type(attr_type)
if isinstance(proper_type, Instance):
var.info = proper_type.type
ti.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True)
attributes_type = Instance(ti, [])

var = Var(name=MAGIC_ATTR_NAME, type=TupleType(attributes_types, fallback=attributes_type))
var.info = ctx.cls.info
var._fullname = ctx.cls.info.fullname + '.' + attr_name
ctx.cls.info.names[attr_name] = SymbolTableNode(
var.is_classvar = True
var._fullname = f"{ctx.cls.fullname}.{MAGIC_ATTR_CLS_NAME}"
ctx.cls.info.names[MAGIC_ATTR_NAME] = SymbolTableNode(
kind=MDEF,
node=var,
plugin_generated=True,
no_serialize=True,
)


Expand Down
1 change: 1 addition & 0 deletions mypy/test/testfinegrained.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class FineGrainedSuite(DataSuite):
'fine-grained-modules.test',
'fine-grained-follow-imports.test',
'fine-grained-suggest.test',
'fine-grained-attr.test',
]

# Whether to use the fine-grained cache in the testing. This is overridden
Expand Down
35 changes: 34 additions & 1 deletion test-data/unit/check-attr.test
Original file line number Diff line number Diff line change
Expand Up @@ -1399,7 +1399,40 @@ class A:
b: int = attr.ib()
c: str = attr.ib()

reveal_type(A.__attrs_attrs__) # N: Revealed type is "Tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str]]"
reveal_type(A.__attrs_attrs__) # N: Revealed type is "Tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A._AttrsAttributes]"
reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[builtins.int]"
reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[builtins.int]"
A.__attrs_attrs__.x # E: "_AttrsAttributes" has no attribute "x"

[builtins fixtures/attr.pyi]

[case testAttrsBareClassHasAttributeWithAttributes]
import attr

@attr.s
class A:
b = attr.ib()
c = attr.ib()

reveal_type(A.__attrs_attrs__) # N: Revealed type is "Tuple[attr.Attribute[Any], attr.Attribute[Any], fallback=__main__.A._AttrsAttributes]"
reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[Any]"
reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[Any]"
A.__attrs_attrs__.x # E: "_AttrsAttributes" has no attribute "x"

[builtins fixtures/attr.pyi]

[case testAttrsNGClassHasAttributeWithAttributes]
import attr

@attr.define
class A:
b: int
c: str

reveal_type(A.__attrs_attrs__) # N: Revealed type is "Tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A._AttrsAttributes]"
reveal_type(A.__attrs_attrs__[0]) # N: Revealed type is "attr.Attribute[builtins.int]"
reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[builtins.int]"
A.__attrs_attrs__.x # E: "_AttrsAttributes" has no attribute "x"

[builtins fixtures/attr.pyi]

Expand Down
23 changes: 23 additions & 0 deletions test-data/unit/fine-grained-attr.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[case updateMagicField]
from attr import Attribute
import m

def g() -> Attribute[int]:
return m.A.__attrs_attrs__[0]

[file m.py]
from attr import define

@define
class A:
a: int
[file m.py.2]
from attr import define

@define
class A:
a: float
[builtins fixtures/attr.pyi]
[out]
==
main:5: error: Incompatible return value type (got "Attribute[float]", expected "Attribute[int]")