From 42ff70b379a74cbe1e0fe33e55d9256fc1498b74 Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Sun, 19 Dec 2021 02:37:43 +0100 Subject: [PATCH 1/4] Specialize __attrs_attrs__ --- mypy/plugin.py | 6 +++++- mypy/plugins/attrs.py | 29 +++++++++++++++++++++++------ test-data/unit/check-attr.test | 32 +++++++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/mypy/plugin.py b/mypy/plugin.py index 3772d7039b05..b3c6f645ff70 100644 --- a/mypy/plugin.py +++ b/mypy/plugin.py @@ -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 ( @@ -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.""" diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 051dae18b96c..865f9623e1a5 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -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 @@ -302,7 +302,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) @@ -710,23 +710,40 @@ 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: + attrs: 'List[Tuple[str, Optional[Type]]]') -> None: attr_name = '__attrs_attrs__' 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)) + + namedtuple_cls_name = "_AttrsAttributes" + ti = ctx.api.basic_new_typeinfo(namedtuple_cls_name, fallback_type, 0) + ti.is_named_tuple = True + ti.type_vars = [repr(a) for a in attributes_types] + ti.tuple_type = TupleType(attributes_types, fallback=fallback_type) + 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, attributes_types) + + var = Var(name=attr_name, type=attributes_type) var.info = ctx.cls.info - var._fullname = ctx.cls.info.fullname + '.' + attr_name + var.is_classvar = True + var._fullname = f"{ctx.cls.fullname}.{namedtuple_cls_name}" ctx.cls.info.names[attr_name] = SymbolTableNode( kind=MDEF, node=var, plugin_generated=True, + no_serialize=True, ) diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index e7cea6c27b99..552d4fa17331 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-attr.test @@ -1399,7 +1399,37 @@ 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 "__main__.A._AttrsAttributes[attr.Attribute[builtins.int], attr.Attribute[builtins.str]]" +reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[builtins.int]" +A.__attrs_attrs__.x # E: "_AttrsAttributes[Attribute[int], Attribute[str]]" 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 "__main__.A._AttrsAttributes[attr.Attribute[Any], attr.Attribute[Any]]" +reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[Any]" +A.__attrs_attrs__.x # E: "_AttrsAttributes[Attribute[Any], Attribute[Any]]" 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 "__main__.A._AttrsAttributes[attr.Attribute[builtins.int], attr.Attribute[builtins.str]]" +reveal_type(A.__attrs_attrs__.b) # N: Revealed type is "attr.Attribute[builtins.int]" +A.__attrs_attrs__.x # E: "_AttrsAttributes[Attribute[int], Attribute[str]]" has no attribute "x" [builtins fixtures/attr.pyi] From aa3fede2f9580c609a2087492073cbc09bda3ee6 Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Sun, 19 Dec 2021 14:22:34 +0100 Subject: [PATCH 2/4] Tweak some more --- mypy/plugins/attrs.py | 6 ++---- test-data/unit/check-attr.test | 15 +++++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 865f9623e1a5..06e5058268f3 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -724,8 +724,6 @@ def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext', namedtuple_cls_name = "_AttrsAttributes" ti = ctx.api.basic_new_typeinfo(namedtuple_cls_name, fallback_type, 0) ti.is_named_tuple = True - ti.type_vars = [repr(a) for a in attributes_types] - ti.tuple_type = TupleType(attributes_types, fallback=fallback_type) for (name, _), attr_type in zip(attrs, attributes_types): var = Var(name, attr_type) var.is_property = True @@ -733,9 +731,9 @@ def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext', if isinstance(proper_type, Instance): var.info = proper_type.type ti.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True) - attributes_type = Instance(ti, attributes_types) + attributes_type = Instance(ti, []) - var = Var(name=attr_name, type=attributes_type) + var = Var(name=attr_name, type=TupleType(attributes_types, fallback=attributes_type)) var.info = ctx.cls.info var.is_classvar = True var._fullname = f"{ctx.cls.fullname}.{namedtuple_cls_name}" diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index 552d4fa17331..083feb0152e5 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-attr.test @@ -1399,9 +1399,10 @@ class A: b: int = attr.ib() c: str = attr.ib() -reveal_type(A.__attrs_attrs__) # N: Revealed type is "__main__.A._AttrsAttributes[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[Attribute[int], Attribute[str]]" has no attribute "x" +A.__attrs_attrs__.x # E: "_AttrsAttributes" has no attribute "x" [builtins fixtures/attr.pyi] @@ -1413,9 +1414,10 @@ class A: b = attr.ib() c = attr.ib() -reveal_type(A.__attrs_attrs__) # N: Revealed type is "__main__.A._AttrsAttributes[attr.Attribute[Any], attr.Attribute[Any]]" +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[Attribute[Any], Attribute[Any]]" has no attribute "x" +A.__attrs_attrs__.x # E: "_AttrsAttributes" has no attribute "x" [builtins fixtures/attr.pyi] @@ -1427,9 +1429,10 @@ class A: b: int c: str -reveal_type(A.__attrs_attrs__) # N: Revealed type is "__main__.A._AttrsAttributes[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[Attribute[int], Attribute[str]]" has no attribute "x" +A.__attrs_attrs__.x # E: "_AttrsAttributes" has no attribute "x" [builtins fixtures/attr.pyi] From 8be7eab35735c809bd1926c84a88045f0cad8cb4 Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Mon, 20 Dec 2021 01:20:11 +0100 Subject: [PATCH 3/4] Refactor constants --- mypy/plugins/attrs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 06e5058268f3..b777bf482e8f 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -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: @@ -711,7 +713,6 @@ def _add_init(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute], def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext', attrs: 'List[Tuple[str, Optional[Type]]]') -> None: - attr_name = '__attrs_attrs__' 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 @@ -721,8 +722,7 @@ def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext', ctx.api.named_type_or_none('attr.Attribute', [any_type]) or any_type, ]) - namedtuple_cls_name = "_AttrsAttributes" - ti = ctx.api.basic_new_typeinfo(namedtuple_cls_name, fallback_type, 0) + 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) @@ -733,11 +733,11 @@ def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext', ti.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True) attributes_type = Instance(ti, []) - var = Var(name=attr_name, type=TupleType(attributes_types, fallback=attributes_type)) + var = Var(name=MAGIC_ATTR_NAME, type=TupleType(attributes_types, fallback=attributes_type)) var.info = ctx.cls.info var.is_classvar = True - var._fullname = f"{ctx.cls.fullname}.{namedtuple_cls_name}" - ctx.cls.info.names[attr_name] = SymbolTableNode( + 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, From 9a08d19acd2fb0fc2aecaba735c3591968b7d472 Mon Sep 17 00:00:00 2001 From: Tin Tvrtkovic Date: Fri, 7 Jan 2022 03:19:03 +0100 Subject: [PATCH 4/4] Add fine-grained test --- mypy/test/testfinegrained.py | 1 + test-data/unit/fine-grained-attr.test | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 test-data/unit/fine-grained-attr.test diff --git a/mypy/test/testfinegrained.py b/mypy/test/testfinegrained.py index ed99b4aeae13..79507948af14 100644 --- a/mypy/test/testfinegrained.py +++ b/mypy/test/testfinegrained.py @@ -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 diff --git a/test-data/unit/fine-grained-attr.test b/test-data/unit/fine-grained-attr.test new file mode 100644 index 000000000000..0a54f9a6ea59 --- /dev/null +++ b/test-data/unit/fine-grained-attr.test @@ -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]")