From 07f8894d0ebd8d4f3e05936bec11a651cd02f095 Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Mon, 23 Mar 2020 17:23:54 +0300 Subject: [PATCH 01/15] Add __attrs_attrs__ attribute to attr.s decorated classes --- mypy/plugins/attrs.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 540905839992f..78b8e5a90ae9e 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -278,6 +278,8 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext', ctx.api.defer() return + _add_attrs_magic_attribute(ctx) + # Save the attributes so that subclasses can reuse them. ctx.cls.info.metadata['attrs'] = { 'attributes': [attr.serialize() for attr in attributes], @@ -656,6 +658,20 @@ def _add_init(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute], adder.add_method('__init__', args, NoneType()) +def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext') -> None: + attr_name = '__attrs_attrs__' + attr_type = ctx.api.named_type('__builtins__.tuple', [ + ctx.api.named_type('attr.Attribute'), + ]) + var = Var(name=attr_name, type=attr_type) + var.info = ctx.cls.info + ctx.cls.info.names[attr_name] = SymbolTableNode( + kind=MDEF, + node=var, + plugin_generated=True, + ) + + class MethodAdder: """Helper to add methods to a TypeInfo. From abfe9516ab85a71c784fe8c044fe92ed30f7118b Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Mon, 23 Mar 2020 17:25:09 +0300 Subject: [PATCH 02/15] Add __dataclass_fields__ attribute to dataclasses --- mypy/plugins/dataclasses.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 81f50d1a4c4d8..5ec5e238a7cdc 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -183,6 +183,8 @@ def transform(self) -> None: self.reset_init_only_vars(info, attributes) + self._add_dataclass_fields_magic_attribute() + info.metadata['dataclass'] = { 'attributes': [attr.serialize() for attr in attributes], 'frozen': decorator_arguments['frozen'], @@ -361,6 +363,21 @@ def _freeze(self, attributes: List[DataclassAttribute]) -> None: var._fullname = info.fullname + '.' + var.name info.names[var.name] = SymbolTableNode(MDEF, var) + def _add_dataclass_fields_magic_attribute(self) -> None: + attr_name = '__dataclass_fields__' + dataclass_field_type = self._ctx.api.named_type('dataclasses.Field') + attr_type = self._ctx.api.named_type('__builtins__.dict', [ + self._ctx.api.named_type('__builtins__.str'), + dataclass_field_type, + ]) + var = Var(name=attr_name, type=attr_type) + var.info = self._ctx.cls.info + self._ctx.cls.info.names[attr_name] = SymbolTableNode( + kind=MDEF, + node=var, + plugin_generated=True, + ) + def dataclass_class_maker_callback(ctx: ClassDefContext) -> None: """Hooks into the class typechecking process to add support for dataclasses. From 4a8688e23e05e671ec356c2b758c4003b1365c15 Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Fri, 27 Mar 2020 08:27:42 +0300 Subject: [PATCH 03/15] Add explicit Any as type argument to dataclasses.Field and attr.Attribute --- mypy/plugins/attrs.py | 2 +- mypy/plugins/dataclasses.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 78b8e5a90ae9e..3db941dea8f31 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -661,7 +661,7 @@ def _add_init(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute], def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext') -> None: attr_name = '__attrs_attrs__' attr_type = ctx.api.named_type('__builtins__.tuple', [ - ctx.api.named_type('attr.Attribute'), + ctx.api.named_type('attr.Attribute', [AnyType(TypeOfAny.explicit)]), ]) var = Var(name=attr_name, type=attr_type) var.info = ctx.cls.info diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 5ec5e238a7cdc..0f4eaac91d0e7 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -12,7 +12,10 @@ from mypy.plugins.common import ( add_method, _get_decorator_bool_argument, deserialize_and_fixup_type, ) -from mypy.types import Type, Instance, NoneType, TypeVarDef, TypeVarType, get_proper_type +from mypy.types import ( + Type, Instance, NoneType, TypeVarDef, TypeVarType, get_proper_type, + AnyType, TypeOfAny, +) from mypy.server.trigger import make_wildcard_trigger # The set of decorators that generate dataclasses. @@ -365,7 +368,7 @@ def _freeze(self, attributes: List[DataclassAttribute]) -> None: def _add_dataclass_fields_magic_attribute(self) -> None: attr_name = '__dataclass_fields__' - dataclass_field_type = self._ctx.api.named_type('dataclasses.Field') + dataclass_field_type = self._ctx.api.named_type('dataclasses.Field', [AnyType(TypeOfAny.explicit)]) attr_type = self._ctx.api.named_type('__builtins__.dict', [ self._ctx.api.named_type('__builtins__.str'), dataclass_field_type, From 17a94031fb0fea0e34d728e09acc9c649041e3a2 Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Fri, 27 Mar 2020 08:28:37 +0300 Subject: [PATCH 04/15] Fix some tests --- mypy/plugins/attrs.py | 1 + mypy/plugins/dataclasses.py | 1 + test-data/unit/fixtures/attr.pyi | 1 + test-data/unit/lib-stub/attr.pyi | 4 +++- test-data/unit/lib-stub/dataclasses.pyi | 3 +++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 3db941dea8f31..dcc4c376710f9 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -665,6 +665,7 @@ def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext') -> None: ]) var = Var(name=attr_name, type=attr_type) var.info = ctx.cls.info + var._fullname = ctx.cls.info.fullname + '.' + attr_name ctx.cls.info.names[attr_name] = SymbolTableNode( kind=MDEF, node=var, diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 0f4eaac91d0e7..653597d2e155b 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -375,6 +375,7 @@ def _add_dataclass_fields_magic_attribute(self) -> None: ]) var = Var(name=attr_name, type=attr_type) var.info = self._ctx.cls.info + var._fullname = self._ctx.cls.info.fullname + '.' + attr_name self._ctx.cls.info.names[attr_name] = SymbolTableNode( kind=MDEF, node=var, diff --git a/test-data/unit/fixtures/attr.pyi b/test-data/unit/fixtures/attr.pyi index deb1906d931e7..c209abfef0d97 100644 --- a/test-data/unit/fixtures/attr.pyi +++ b/test-data/unit/fixtures/attr.pyi @@ -25,3 +25,4 @@ class complex: class str: pass class unicode: pass class ellipsis: pass +class tuple: pass diff --git a/test-data/unit/lib-stub/attr.pyi b/test-data/unit/lib-stub/attr.pyi index 7399eb4425945..495e54da350b8 100644 --- a/test-data/unit/lib-stub/attr.pyi +++ b/test-data/unit/lib-stub/attr.pyi @@ -1,4 +1,4 @@ -from typing import TypeVar, overload, Callable, Any, Type, Optional, Union, Sequence, Mapping +from typing import TypeVar, overload, Callable, Any, Type, Optional, Union, Sequence, Mapping, Generic _T = TypeVar('_T') _C = TypeVar('_C', bound=type) @@ -115,6 +115,8 @@ def attrs(maybe_cls: None = ..., ) -> Callable[[_C], _C]: ... +class Attribute(Generic[_T]): pass + # aliases s = attributes = attrs ib = attr = attrib diff --git a/test-data/unit/lib-stub/dataclasses.pyi b/test-data/unit/lib-stub/dataclasses.pyi index 160cfcd066ba9..c92775e9ee740 100644 --- a/test-data/unit/lib-stub/dataclasses.pyi +++ b/test-data/unit/lib-stub/dataclasses.pyi @@ -28,3 +28,6 @@ def field(*, default_factory: Callable[[], _T], def field(*, init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ...) -> Any: ... + + +class Field(Generic[_T]): pass From 229c5680a3b9e31f6016d71668bb05128e2d2f40 Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Sun, 29 Mar 2020 18:00:51 +0300 Subject: [PATCH 05/15] Fix almost all tests --- mypy/plugins/attrs.py | 10 +- mypy/plugins/dataclasses.py | 6 +- test-data/unit/check-attr.test | 11 ++ test-data/unit/check-dataclasses.test | 128 ++++++++++++++---------- test-data/unit/check-flags.test | 6 ++ test-data/unit/check-incremental.test | 16 +-- test-data/unit/deps.test | 6 +- test-data/unit/fine-grained.test | 16 +-- test-data/unit/fixtures/dataclasses.pyi | 25 +++++ 9 files changed, 146 insertions(+), 78 deletions(-) create mode 100644 test-data/unit/fixtures/dataclasses.pyi diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index dcc4c376710f9..a91ab8e4cc085 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -660,10 +660,12 @@ def _add_init(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute], def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext') -> None: attr_name = '__attrs_attrs__' - attr_type = ctx.api.named_type('__builtins__.tuple', [ - ctx.api.named_type('attr.Attribute', [AnyType(TypeOfAny.explicit)]), - ]) - var = Var(name=attr_name, type=attr_type) + any_type = AnyType(TypeOfAny.explicit) + attribute_type = ctx.api.named_type_or_none('attr.Attribute', [any_type]) + attribute_type = attribute_type or any_type + var = Var(name=attr_name, type=ctx.api.named_type('__builtins__.tuple', [ + attribute_type, + ])) var.info = ctx.cls.info var._fullname = ctx.cls.info.fullname + '.' + attr_name ctx.cls.info.names[attr_name] = SymbolTableNode( diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 653597d2e155b..ecbe8c4236f17 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -368,10 +368,12 @@ def _freeze(self, attributes: List[DataclassAttribute]) -> None: def _add_dataclass_fields_magic_attribute(self) -> None: attr_name = '__dataclass_fields__' - dataclass_field_type = self._ctx.api.named_type('dataclasses.Field', [AnyType(TypeOfAny.explicit)]) + any_type = AnyType(TypeOfAny.explicit) + field_type = self._ctx.api.named_type_or_none('dataclasses.Field', [any_type]) + field_type = field_type or any_type attr_type = self._ctx.api.named_type('__builtins__.dict', [ self._ctx.api.named_type('__builtins__.str'), - dataclass_field_type, + field_type, ]) var = Var(name=attr_name, type=attr_type) var.info = self._ctx.cls.info diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index fd6fc2b3755ef..a1591c45928cc 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-attr.test @@ -1259,3 +1259,14 @@ class B(A): reveal_type(B) # N: Revealed type is 'def (foo: builtins.int) -> __main__.B' [builtins fixtures/bool.pyi] + +[case testAttrsClassHasAttributeWithAttributes] +import attr + +@attr.s +class A: + pass + +reveal_type(A.__attrs_attrs__) # N: Revealed type is 'builtins.tuple[attr.Attribute[Any]]' + +[builtins fixtures/attr.pyi] diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 97bb9954ec293..ce38becbfd263 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -14,7 +14,7 @@ reveal_type(Person) # N: Revealed type is 'def (name: builtins.str, age: builti Person('John', 32) Person('Jonh', 21, None) # E: Too many arguments for "Person" -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [typing fixtures/typing-medium.pyi] [case testDataclassesCustomInit] @@ -30,7 +30,7 @@ class A: A('1') -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesBasicInheritance] # flags: --python-version 3.6 @@ -52,7 +52,7 @@ Mammal(10) Person(32, 'John') Person(21, 'Jonh', None) # E: Too many arguments for "Person" -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [typing fixtures/typing-medium.pyi] [case testDataclassesDeepInheritance] @@ -80,7 +80,7 @@ reveal_type(B) # N: Revealed type is 'def (a: builtins.int, b: builtins.int) -> reveal_type(C) # N: Revealed type is 'def (a: builtins.int, b: builtins.int, c: builtins.int) -> __main__.C' reveal_type(D) # N: Revealed type is 'def (a: builtins.int, b: builtins.int, c: builtins.int, d: builtins.int) -> __main__.D' -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesMultipleInheritance] from dataclasses import dataclass, field, InitVar @@ -102,7 +102,7 @@ class C(A, B): reveal_type(C) # N: Revealed type is 'def (b: builtins.bool, a: builtins.bool) -> __main__.C' -[builtins fixtures/bool.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesDeepInitVarInheritance] from dataclasses import dataclass, field, InitVar @@ -130,7 +130,7 @@ class D(C): reveal_type(C) # N: Revealed type is 'def () -> __main__.C' reveal_type(D) # N: Revealed type is 'def (b: builtins.bool) -> __main__.D' -[builtins fixtures/bool.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesOverriding] # flags: --python-version 3.6 @@ -163,7 +163,7 @@ Person(21, 'John', None) # E: Too many arguments for "Person" SpecialPerson(21, 'John', 0.5) ExtraSpecialPerson(21, 'John', 0.5) -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesOverridingWithDefaults] # Issue #5681 https://github.com/python/mypy/issues/5681 @@ -183,7 +183,7 @@ class C(Base): reveal_type(C) # N: Revealed type is 'def (some_int: builtins.int, some_str: builtins.str =) -> __main__.C' -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesFreezing] # flags: --python-version 3.6 @@ -196,7 +196,7 @@ class Person: john = Person('John') john.name = 'Ben' # E: Property "name" defined in "Person" is read-only -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesFields] # flags: --python-version 3.6 @@ -212,7 +212,7 @@ john = Person('John') john.age = 'invalid' # E: Incompatible types in assignment (expression has type "str", variable has type "int") john.age = 24 -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesBadInit] # flags: --python-version 3.6 @@ -226,7 +226,7 @@ class Person: # N: def field(*, init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ...) -> Any \ # N: <2 more non-matching overloads not shown> -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesMultiInit] # flags: --python-version 3.6 @@ -242,7 +242,7 @@ class Person: reveal_type(Person) # N: Revealed type is 'def (name: builtins.str, friend_names: builtins.list[builtins.str], enemy_names: builtins.list[builtins.str]) -> __main__.Person' -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesMultiInitDefaults] # flags: --python-version 3.6 @@ -259,7 +259,7 @@ class Person: reveal_type(Person) # N: Revealed type is 'def (name: builtins.str, friend_names: builtins.list[builtins.str], enemy_names: builtins.list[builtins.str], nickname: Union[builtins.str, None] =) -> __main__.Person' -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesDefaults] # flags: --python-version 3.6 @@ -273,7 +273,7 @@ class Application: reveal_type(Application) # N: Revealed type is 'def (name: builtins.str =, rating: builtins.int =) -> __main__.Application' app = Application() -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesDefaultFactories] # flags: --python-version 3.6 @@ -285,7 +285,7 @@ class Application: rating: int = field(default_factory=int) rating_count: int = field() # E: Attributes without a default cannot follow attributes with one -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesDefaultFactoryTypeChecking] # flags: --python-version 3.6 @@ -296,7 +296,7 @@ class Application: name: str = 'Unnamed' rating: int = field(default_factory=str) # E: Incompatible types in assignment (expression has type "str", variable has type "int") -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesDefaultOrdering] # flags: --python-version 3.6 @@ -307,7 +307,7 @@ class Application: name: str = 'Unnamed' rating: int # E: Attributes without a default cannot follow attributes with one -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesClassmethods] # flags: --python-version 3.6 @@ -323,8 +323,7 @@ class Application: app = Application.parse('') -[builtins fixtures/list.pyi] -[builtins fixtures/classmethod.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesOverloadsAndClassmethods] # flags: --python-version 3.6 @@ -357,7 +356,7 @@ class A: reveal_type(A.foo(3)) # N: Revealed type is 'builtins.int' reveal_type(A.foo("foo")) # N: Revealed type is 'builtins.str' -[builtins fixtures/classmethod.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesClassVars] # flags: --python-version 3.6 @@ -375,7 +374,7 @@ application = Application("example") application.COUNTER = 1 # E: Cannot assign to class variable "COUNTER" via instance Application.COUNTER = 1 -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassEquality] # flags: --python-version 3.6 @@ -390,9 +389,10 @@ app1 = Application("example-1", 5) app2 = Application("example-2", 5) app1 == app2 app1 != app2 -app1 == None # E: Unsupported operand types for == ("Application" and "None") +app1 == None +reveal_type(app1.__eq__) # N: Revealed type is 'def (builtins.object) -> builtins.bool' -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassCustomEquality] # flags: --python-version 3.6 @@ -409,15 +409,24 @@ class Application: app1 = Application("example-1", 5) app2 = Application("example-2", 5) app1 == app2 -app1 != app2 # E: Unsupported left operand type for != ("Application") -app1 == None # E: Unsupported operand types for == ("Application" and "None") +app1 != app2 +app1 == None class SpecializedApplication(Application): ... app1 == SpecializedApplication("example-3", 5) -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] + +[out1] +main:9: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object" +main:9: note: It is recommended for "__eq__" to work with arbitrary objects, for example: +main:9: note: def __eq__(self, other: object) -> bool: +main:9: note: if not isinstance(other, Application): +main:9: note: return NotImplemented +main:9: note: return +main:16: error: Unsupported operand types for == ("Application" and "None") [case testDataclassOrdering] # flags: --python-version 3.6 @@ -448,7 +457,7 @@ app1 > app3 app1 <= app3 app1 >= app3 -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassOrderingWithoutEquality] # flags: --python-version 3.6 @@ -458,7 +467,7 @@ from dataclasses import dataclass class Application: # E: eq must be True if order is True ... -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassOrderingWithCustomMethods] # flags: --python-version 3.6 @@ -469,7 +478,7 @@ class Application: def __lt__(self, other: 'Application') -> bool: # E: You may not have a custom __lt__ method when order=True ... -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassDefaultsInheritance] # flags: --python-version 3.6 @@ -487,7 +496,7 @@ class SpecializedApplication(Application): reveal_type(SpecializedApplication) # N: Revealed type is 'def (id: Union[builtins.int, None], name: builtins.str, rating: builtins.int =) -> __main__.SpecializedApplication' -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassGenerics] # flags: --python-version 3.6 @@ -520,7 +529,7 @@ reveal_type(a.y) # N: Revealed type is 'builtins.int*' reveal_type(a.z) # N: Revealed type is 'builtins.list[builtins.int*]' s: str = a.bar() # E: Incompatible types in assignment (expression has type "int", variable has type "str") -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassGenericsClassmethod] # flags: --python-version 3.6 @@ -542,7 +551,7 @@ class A(Generic[T]): def other(cls, x: T) -> A[T]: ... reveal_type(A(0).other) # N: Revealed type is 'def (x: builtins.int*) -> __main__.A[builtins.int*]' -[builtins fixtures/classmethod.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesForwardRefs] from dataclasses import dataclass @@ -559,7 +568,7 @@ reveal_type(A) # N: Revealed type is 'def (b: __main__.B) -> __main__.A' A(b=B(42)) A(b=42) # E: Argument "b" to "A" has incompatible type "int"; expected "B" -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesInitVars] @@ -588,7 +597,7 @@ app.name app.rating app.database_name # E: "SpecializedApplication" has no attribute "database_name" -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesInitVarsAndDefer] @@ -608,7 +617,7 @@ app.name app.database_name # E: "Application" has no attribute "database_name" class Yes: ... -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesNoInitInitVarInheritance] from dataclasses import dataclass, field, InitVar @@ -624,7 +633,7 @@ class Sub(Super): sub = Sub(5) sub.foo # E: "Sub" has no attribute "foo" sub.bar -[builtins fixtures/bool.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassFactory] from typing import Type, TypeVar @@ -639,7 +648,7 @@ class A: reveal_type(cls) # N: Revealed type is 'Type[T`-1]' reveal_type(cls()) # N: Revealed type is 'T`-1' return cls() -[builtins fixtures/classmethod.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesInitVarOverride] import dataclasses @@ -661,7 +670,7 @@ class B(A): super().__init__(b+1) self._b = b -[builtins fixtures/bool.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesInitVarNoOverride] import dataclasses @@ -686,7 +695,7 @@ class B(A): B(1, 2) B(1, 'a') # E: Argument 2 to "B" has incompatible type "str"; expected "int" -[builtins fixtures/bool.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesInitVarPostInitOverride] import dataclasses @@ -777,7 +786,7 @@ from typing import Optional @dataclass class Foo: bar: Optional[int] = field(default=None) -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out] [case testNoComplainFieldNoneStrict] @@ -789,7 +798,7 @@ from typing import Optional @dataclass class Foo: bar: Optional[int] = field(default=None) -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out] [case testDisallowUntypedWorksForward] @@ -805,7 +814,7 @@ class C(List[C]): pass reveal_type(B) # N: Revealed type is 'def (x: __main__.C) -> __main__.B' -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDisallowUntypedWorksForwardBad] # flags: --disallow-untyped-defs @@ -817,7 +826,7 @@ class B: y = undefined() # E: Name 'undefined' is not defined reveal_type(B) # N: Revealed type is 'def (x: Any) -> __main__.B' -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testMemberExprWorksAsField] import dataclasses @@ -854,7 +863,7 @@ b = Application('', 0) a < b class Yes: ... -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassFieldDeferred] from dataclasses import field, dataclass @@ -865,7 +874,7 @@ class C: def func() -> int: ... C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "int" -[builtins fixtures/bool.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassFieldDeferredFrozen] from dataclasses import field, dataclass @@ -877,7 +886,7 @@ class C: def func() -> int: ... c: C c.x = 1 # E: Property "x" defined in "C" is read-only -[builtins fixtures/bool.pyi] +[builtins fixtures/dataclasses.pyi] [case testTypeInDataclassDeferredStar] import lib @@ -895,7 +904,7 @@ C() # E: Too few arguments for "C" C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "int" [file other.py] import lib -[builtins fixtures/bool.pyi] +[builtins fixtures/dataclasses.pyi] [case testDeferredDataclassInitSignature] from dataclasses import dataclass @@ -911,7 +920,7 @@ class C: return cls(x=None, y=None) class Deferred: pass -[builtins fixtures/classmethod.pyi] +[builtins fixtures/dataclasses.pyi] [case testDeferredDataclassInitSignatureSubclass] # flags: --strict-optional @@ -927,7 +936,7 @@ class C(B): y: str a = C(None, 'abc') -[builtins fixtures/bool.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesDefaultsIncremental] # flags: --python-version 3.6 @@ -959,7 +968,7 @@ class Person: b: int a: str = 'test' -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesDefaultsMroOtherFile] # flags: --python-version 3.6 @@ -988,7 +997,7 @@ class A1: class A2: b: str = 'test' -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesInheritingDuplicateField] # see mypy issue #7792 @@ -1003,6 +1012,8 @@ class A: # E: Name 'x' already defined (possibly by an import) class B(A): pass +[builtins fixtures/dataclasses.pyi] + [case testDataclassInheritanceNoAnnotation] from dataclasses import dataclass @@ -1017,6 +1028,8 @@ class B(A): reveal_type(B) # N: Revealed type is 'def (foo: builtins.int) -> __main__.B' +[builtins fixtures/dataclasses.pyi] + [case testDataclassInheritanceNoAnnotation2] from dataclasses import dataclass @@ -1031,4 +1044,15 @@ class B(A): reveal_type(B) # N: Revealed type is 'def (foo: builtins.int) -> __main__.B' -[builtins fixtures/property.pyi] +[builtins fixtures/dataclasses.pyi] + +[case testDataclassHasAttributeWithFields] +from dataclasses import dataclass + +@dataclass +class A: + pass + +reveal_type(A.__dataclass_fields__) # N: Revealed type is 'builtins.dict[builtins.str, dataclasses.Field[Any]]' + +[builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index a2c36c0ca0cb1..08a55a81d6035 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -1038,6 +1038,8 @@ import attr class Unannotated: foo = attr.ib() +[builtins fixtures/attr.pyi] + [case testDisallowIncompleteDefsAttrsWithAnnotations] # flags: --disallow-incomplete-defs import attr @@ -1046,6 +1048,8 @@ import attr class Annotated: bar: int = attr.ib() +[builtins fixtures/attr.pyi] + [case testDisallowIncompleteDefsAttrsPartialAnnotations] # flags: --disallow-incomplete-defs import attr @@ -1055,6 +1059,8 @@ class PartiallyAnnotated: # E: Function is missing a type annotation for one or bar: int = attr.ib() baz = attr.ib() +[builtins fixtures/attr.pyi] + [case testAlwaysTrueAlwaysFalseFlags] # flags: --always-true=YOLO --always-true=YOLO1 --always-false=BLAH1 --always-false BLAH --ignore-missing-imports from somewhere import YOLO, BLAH diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 8a887685da05b..2400b58c9fcf3 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -3781,7 +3781,7 @@ class A: E = 7 F: ClassVar[int] = 22 -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out1] [out2] @@ -3813,7 +3813,7 @@ from dataclasses import dataclass class A: x: int -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out1] [out2] tmp/b.py:8: note: Revealed type is 'def (x: builtins.int) -> b.B' @@ -3857,7 +3857,7 @@ class NoInit: class NoCmp: x: int -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out1] [out2] tmp/b.py:4: error: Property "x" defined in "Frozen" is read-only @@ -3911,7 +3911,7 @@ from dataclasses import dataclass class A: a: int -[builtins fixtures/attr.pyi] +[builtins fixtures/dataclasses.pyi] [out1] [out2] tmp/b.py:3: note: Revealed type is 'def (a: builtins.int) -> a.A' @@ -3957,7 +3957,7 @@ from dataclasses import dataclass class B(A): y: int -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out1] [out2] main:2: error: Argument 2 to "B" has incompatible type "str"; expected "int" @@ -3990,7 +3990,7 @@ from dataclasses import dataclass class B(A): y: str -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out1] main:2: error: Argument 2 to "B" has incompatible type "str"; expected "int" @@ -4032,7 +4032,7 @@ from dataclasses import dataclass class C(A, B): c: bool -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out1] [out2] tmp/c.py:7: error: Incompatible types in assignment (expression has type "bool", base class "B" defined the type as "str") @@ -4063,7 +4063,7 @@ from dataclasses import dataclass class A: a: int = 6 -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out1] [out2] main:2: error: Argument 1 to "A" has incompatible type "int"; expected "str" diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test index 62ddeac07bc7b..d833a05e201a0 100644 --- a/test-data/unit/deps.test +++ b/test-data/unit/deps.test @@ -1430,14 +1430,12 @@ class A: @dataclass class B(A): y: int -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out] -> , m - -> - -> + -> -> , m.B.__init__ - -> -> -> -> diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 3ea6b31f379aa..2a08f5724e6cf 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -688,7 +688,7 @@ class A: == b.py:8: error: Argument 1 to "B" has incompatible type "int"; expected "str" == -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassUpdate2] # flags: --python-version 3.7 @@ -719,7 +719,7 @@ B(1, 2) [out] == b.py:8: error: Argument 1 to "B" has incompatible type "int"; expected "str" -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassUpdate3] # flags: --python-version 3.7 @@ -743,7 +743,7 @@ from dataclasses import dataclass class A: a: int other: int -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out] == main:3: error: Too few arguments for "B" @@ -770,7 +770,7 @@ from dataclasses import dataclass class A: a: int other: int -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out] == main:3: error: Too few arguments for "B" @@ -804,7 +804,7 @@ from dataclasses import dataclass class A: a: int -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out] == main:3: error: Too few arguments for "B" @@ -831,7 +831,7 @@ from dataclasses import dataclass @dataclass class A: a: int -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out] == main:3: error: Unsupported left operand type for < ("B") @@ -864,7 +864,7 @@ from dataclasses import dataclass class A: a: int other: int -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out] == main:3: error: Too few arguments for "C" @@ -904,7 +904,7 @@ from dataclasses import dataclass class A: a: int -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [out] == main:3: error: Too few arguments for "C" diff --git a/test-data/unit/fixtures/dataclasses.pyi b/test-data/unit/fixtures/dataclasses.pyi new file mode 100644 index 0000000000000..b5128f5d8ec14 --- /dev/null +++ b/test-data/unit/fixtures/dataclasses.pyi @@ -0,0 +1,25 @@ +from typing import Generic, Sequence, TypeVar + + +_T = TypeVar('_T') +_U = TypeVar('_U') + +class object: + def __init__(self) -> None: pass + def __eq__(self, o: object) -> bool: pass + def __ne__(self, o: object) -> bool: pass + + +class type: pass +class ellipsis: pass +class tuple(Generic[_T]): pass +# class function: pass +class int: pass +class float: pass +class str: pass +class bool(int): pass +class dict(Generic[_T, _U]): pass +class list(Generic[_T], Sequence[_T]): pass +class function: pass +class classmethod: pass +property = object() From 426d644873ce1d8b98a938a1fa4a7abc6a643212 Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Sun, 29 Mar 2020 18:07:35 +0300 Subject: [PATCH 06/15] Remove commented code from stub --- test-data/unit/fixtures/dataclasses.pyi | 3 --- 1 file changed, 3 deletions(-) diff --git a/test-data/unit/fixtures/dataclasses.pyi b/test-data/unit/fixtures/dataclasses.pyi index b5128f5d8ec14..fb0053c80b259 100644 --- a/test-data/unit/fixtures/dataclasses.pyi +++ b/test-data/unit/fixtures/dataclasses.pyi @@ -1,6 +1,5 @@ from typing import Generic, Sequence, TypeVar - _T = TypeVar('_T') _U = TypeVar('_U') @@ -9,11 +8,9 @@ class object: def __eq__(self, o: object) -> bool: pass def __ne__(self, o: object) -> bool: pass - class type: pass class ellipsis: pass class tuple(Generic[_T]): pass -# class function: pass class int: pass class float: pass class str: pass From 8dadb8ab11c8f5ba7549dbb1924d4b04c31b658a Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Sun, 11 Oct 2020 14:34:55 +0300 Subject: [PATCH 07/15] Fix narrowing tests --- mypy/plugin.py | 5 +++++ test-data/unit/check-narrowing.test | 4 ++-- test-data/unit/fixtures/narrowing.pyi | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 test-data/unit/fixtures/narrowing.pyi diff --git a/mypy/plugin.py b/mypy/plugin.py index eb31878b62a74..4fa89c9133225 100644 --- a/mypy/plugin.py +++ b/mypy/plugin.py @@ -252,6 +252,11 @@ def named_type(self, qualified_name: str, args: Optional[List[Type]] = None) -> """Construct an instance of a builtin type with given type arguments.""" raise NotImplementedError + @abstractmethod + def named_type_or_none(self, qualified_name: str, args: Optional[List[Type]] = None) -> Optional[Instance]: + """Construct an instance of a builtin type with given type arguments.""" + raise NotImplementedError + @abstractmethod def parse_bool(self, expr: Expression) -> Optional[bool]: """Parse True/False literals.""" diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index a1d9685cc43dd..30fe46468601d 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -168,7 +168,7 @@ if x5["key"] is Key.A: reveal_type(x5) # N: Revealed type is 'TypedDict('__main__.TypedDict1', {'key': Literal[__main__.Key.A], 'foo': builtins.int})' else: reveal_type(x5) # N: Revealed type is 'TypedDict('__main__.TypedDict2', {'key': Literal[__main__.Key.B], 'foo': builtins.str})' -[builtins fixtures/tuple.pyi] +[builtins fixtures/narrowing.pyi] [case testNarrowingParentWithIsInstanceBasic] from dataclasses import dataclass @@ -233,7 +233,7 @@ if isinstance(x5["key"], int): reveal_type(x5) # N: Revealed type is 'TypedDict('__main__.TypedDict1', {'key': builtins.int})' else: reveal_type(x5) # N: Revealed type is 'TypedDict('__main__.TypedDict2', {'key': builtins.str})' -[builtins fixtures/isinstance.pyi] +[builtins fixtures/narrowing.pyi] [case testNarrowingParentMultipleKeys] # flags: --warn-unreachable diff --git a/test-data/unit/fixtures/narrowing.pyi b/test-data/unit/fixtures/narrowing.pyi new file mode 100644 index 0000000000000..89ee011c1c805 --- /dev/null +++ b/test-data/unit/fixtures/narrowing.pyi @@ -0,0 +1,20 @@ +# Builtins stub used in check-narrowing test cases. +from typing import Generic, Sequence, Tuple, Type, TypeVar, Union + + +Tco = TypeVar('Tco', covariant=True) +KT = TypeVar("KT") +VT = TypeVar("VT") + +class object: + def __init__(self) -> None: pass + +class type: pass +class tuple(Sequence[Tco], Generic[Tco]): pass +class function: pass +class ellipsis: pass +class int: pass +class str: pass +class dict(Generic[KT, VT]): pass + +def isinstance(x: object, t: Union[Type[object], Tuple[Type[object], ...]]) -> bool: pass From 62b8497cddf9ff49476d65049ff8299393c73bfa Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Sun, 11 Oct 2020 15:15:01 +0300 Subject: [PATCH 08/15] Fix test --- test-data/unit/deps.test | 1 - 1 file changed, 1 deletion(-) diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test index 68fb4fafa2fc0..d833a05e201a0 100644 --- a/test-data/unit/deps.test +++ b/test-data/unit/deps.test @@ -1434,7 +1434,6 @@ class B(A): [out] -> , m - -> -> -> , m.B.__init__ -> From 7b26c678ba5e29620d1e1df0d833619a60e82320 Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Sun, 11 Oct 2020 15:15:11 +0300 Subject: [PATCH 09/15] Fix mypy errors --- mypy/plugins/attrs.py | 3 +-- mypy/plugins/dataclasses.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 33cf5519ec7bf..0234088d4a407 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -652,8 +652,7 @@ def _add_init(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute], def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext') -> None: attr_name = '__attrs_attrs__' any_type = AnyType(TypeOfAny.explicit) - attribute_type = ctx.api.named_type_or_none('attr.Attribute', [any_type]) - attribute_type = attribute_type or any_type + attribute_type = ctx.api.named_type_or_none('attr.Attribute', [any_type]) or any_type var = Var(name=attr_name, type=ctx.api.named_type('__builtins__.tuple', [ attribute_type, ])) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 43ebb0c891f8c..df3b380782397 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -13,7 +13,7 @@ add_method, _get_decorator_bool_argument, deserialize_and_fixup_type, ) from mypy.types import ( - Type, Instance, NoneType, TypeVarDef, TypeVarType, get_proper_type, + ProperType, Type, Instance, NoneType, TypeVarDef, TypeVarType, get_proper_type, AnyType, TypeOfAny, ) from mypy.server.trigger import make_wildcard_trigger @@ -348,8 +348,7 @@ def _freeze(self, attributes: List[DataclassAttribute]) -> None: def _add_dataclass_fields_magic_attribute(self) -> None: attr_name = '__dataclass_fields__' any_type = AnyType(TypeOfAny.explicit) - field_type = self._ctx.api.named_type_or_none('dataclasses.Field', [any_type]) - field_type = field_type or any_type + field_type = self._ctx.api.named_type_or_none('dataclasses.Field', [any_type]) or any_type attr_type = self._ctx.api.named_type('__builtins__.dict', [ self._ctx.api.named_type('__builtins__.str'), field_type, From a5bcda1831797521ca8a1eede456a62658eae67a Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Sun, 11 Oct 2020 15:16:49 +0300 Subject: [PATCH 10/15] Fix flake8 errors --- mypy/plugin.py | 4 +++- mypy/plugins/dataclasses.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mypy/plugin.py b/mypy/plugin.py index 4fa89c9133225..6be636100b06e 100644 --- a/mypy/plugin.py +++ b/mypy/plugin.py @@ -253,7 +253,9 @@ def named_type(self, qualified_name: str, args: Optional[List[Type]] = None) -> raise NotImplementedError @abstractmethod - def named_type_or_none(self, qualified_name: str, args: Optional[List[Type]] = None) -> Optional[Instance]: + def named_type_or_none(self, + qualified_name: str, + args: Optional[List[Type]] = None) -> Optional[Instance]: """Construct an instance of a builtin type with given type arguments.""" raise NotImplementedError diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index df3b380782397..932391283ba03 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -13,7 +13,7 @@ add_method, _get_decorator_bool_argument, deserialize_and_fixup_type, ) from mypy.types import ( - ProperType, Type, Instance, NoneType, TypeVarDef, TypeVarType, get_proper_type, + Type, Instance, NoneType, TypeVarDef, TypeVarType, get_proper_type, AnyType, TypeOfAny, ) from mypy.server.trigger import make_wildcard_trigger From 09320375295db15d7d9697c93910b0aabd19e918 Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Sun, 17 Jan 2021 18:44:55 +0300 Subject: [PATCH 11/15] Fix tests --- test-data/unit/check-dataclasses.test | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index c44129a6fad2d..a77d0a04cf643 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -498,6 +498,7 @@ sub = Sub(attr=1) reveal_type(sub) # N: Revealed type is '__main__.Sub' reveal_type(sub.attr) # N: Revealed type is 'Any' +[builtins fixtures/dataclasses.pyi] [case testDataclassGenericSubtype] from dataclasses import dataclass @@ -523,6 +524,7 @@ sub_str = Sub[str](attr='ok') reveal_type(sub_str) # N: Revealed type is '__main__.Sub[builtins.str*]' reveal_type(sub_str.attr) # N: Revealed type is 'builtins.str*' +[builtins fixtures/dataclasses.pyi] [case testDataclassGenericInheritance] from dataclasses import dataclass @@ -548,6 +550,7 @@ reveal_type(sub.one) # N: Revealed type is 'builtins.int*' reveal_type(sub.two) # N: Revealed type is 'builtins.str*' reveal_type(sub.three) # N: Revealed type is 'builtins.float*' +[builtins fixtures/dataclasses.pyi] [case testDataclassMultiGenericInheritance] from dataclasses import dataclass @@ -574,6 +577,7 @@ reveal_type(sub) # N: Revealed type is '__main__.Sub' reveal_type(sub.base_attr) # N: Revealed type is 'builtins.int*' reveal_type(sub.middle_attr) # N: Revealed type is 'builtins.str*' +[builtins fixtures/dataclasses.pyi] [case testDataclassGenericsClassmethod] # flags: --python-version 3.6 From 2d3a6ca262573aba87d9d6cab6c04b17d7267a40 Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Fri, 13 Aug 2021 12:13:21 +0300 Subject: [PATCH 12/15] Generate precise type for `__attrs_attrs__` in attrs plugin --- mypy/plugin.py | 7 ++++++- mypy/plugins/attrs.py | 21 +++++++++++++-------- test-data/unit/check-attr.test | 5 +++-- test-data/unit/check-dataclasses.test | 15 +++++++++------ test-data/unit/foo.py | 22 ++++++++++++++++++++++ 5 files changed, 53 insertions(+), 17 deletions(-) create mode 100644 test-data/unit/foo.py diff --git a/mypy/plugin.py b/mypy/plugin.py index b45dfabda7c2c..4cd769427ce19 100644 --- a/mypy/plugin.py +++ b/mypy/plugin.py @@ -258,7 +258,12 @@ def named_type(self, qualified_name: str, args: Optional[List[Type]] = None) -> def named_type_or_none(self, qualified_name: str, args: Optional[List[Type]] = None) -> Optional[Instance]: - """Construct an instance of a builtin type with given type arguments.""" + """Construct an instance of a type with given type arguments. + + Return None if a type could not be constructed for the qualified + type name. This is possible when the qualified name includes a + module name and the module has not been imported. + """ raise NotImplementedError @abstractmethod diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 03ba0ccc049cd..2a60d1bc454c0 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -21,8 +21,8 @@ deserialize_and_fixup_type ) from mypy.types import ( - Type, AnyType, TypeOfAny, CallableType, NoneType, TypeVarType, - Overloaded, UnionType, FunctionLike, get_proper_type + TupleType, Type, AnyType, TypeOfAny, CallableType, NoneType, TypeVarType, + Overloaded, UnionType, FunctionLike, get_proper_type, ) from mypy.typeops import make_simplified_union, map_type_from_supertype from mypy.typevars import fill_typevars @@ -300,7 +300,7 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext', ctx.api.defer() return - _add_attrs_magic_attribute(ctx) + _add_attrs_magic_attribute(ctx, raw_attr_types=[info.get(attr.name).type for attr in attributes]) # Save the attributes so that subclasses can reuse them. ctx.cls.info.metadata['attrs'] = { @@ -705,13 +705,18 @@ def _add_init(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute], adder.add_method('__init__', args, NoneType()) -def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext') -> None: +def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext', + raw_attr_types: 'List[Type]') -> None: attr_name = '__attrs_attrs__' any_type = AnyType(TypeOfAny.explicit) - attribute_type = ctx.api.named_type_or_none('attr.Attribute', [any_type]) or any_type - var = Var(name=attr_name, type=ctx.api.named_type('__builtins__.tuple', [ - attribute_type, - ])) + attributes_types = [ + ctx.api.named_type_or_none('attr.Attribute', [attr_type or any_type]) or any_type + for attr_type in raw_attr_types + ] + fallback_type = ctx.api.named_type('__builtins__.tuple', [ + ctx.api.named_type_or_none('attr.Attribute', [any_type]), + ]) + var = Var(name=attr_name, type=TupleType(attributes_types, fallback=fallback_type)) var.info = ctx.cls.info var._fullname = ctx.cls.info.fullname + '.' + attr_name ctx.cls.info.names[attr_name] = SymbolTableNode( diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index c343a6c61e146..f8dcfd811e013 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-attr.test @@ -1396,8 +1396,9 @@ import attr @attr.s class A: - pass + b: int = attr.ib() + c: str = attr.ib() -reveal_type(A.__attrs_attrs__) # N: Revealed type is 'builtins.tuple[attr.Attribute[Any]]' +reveal_type(A.__attrs_attrs__) # N: Revealed type is "Tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str]]" [builtins fixtures/attr.pyi] diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index f9ec34064c25b..31eb0da762022 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -329,7 +329,7 @@ Application('name', rating=123) # E: Too many positional arguments for "Applica Application(name=123, rating='name') # E: Argument "name" to "Application" has incompatible type "int"; expected "str" # E: Argument "rating" to "Application" has incompatible type "str"; expected "int" Application(rating='name', name=123) # E: Argument "rating" to "Application" has incompatible type "str"; expected "int" # E: Argument "name" to "Application" has incompatible type "int"; expected "str" -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesOrderingKwOnlyOnField] # flags: --python-version 3.10 @@ -348,7 +348,7 @@ Application('name') # E: Missing named argument "rating" for "Application" Application('name', 123) # E: Too many positional arguments for "Application" Application(123, rating='name') # E: Argument 1 to "Application" has incompatible type "int"; expected "str" # E: Argument "rating" to "Application" has incompatible type "str"; expected "int" -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesOrderingKwOnlyOnFieldFalse] # flags: --python-version 3.10 @@ -365,7 +365,7 @@ Application('name', rating=123) Application() # E: Missing positional argument "name" in call to "Application" Application('name') # E: Too few arguments for "Application" -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesOrderingKwOnlyWithSentinel] # flags: --python-version 3.10 @@ -384,7 +384,7 @@ Application('name') # E: Too many positional arguments for "Application" # E: M Application('name', 123) # E: Too many positional arguments for "Application" Application('name', rating=123) # E: Too many positional arguments for "Application" -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesOrderingKwOnlyWithSentinelAndFieldOverride] # flags: --python-version 3.10 @@ -402,7 +402,7 @@ Application('name') # E: Too many positional arguments for "Application" # E: T Application('name', 123) # E: Too many positional arguments for "Application" Application('name', rating=123) # E: Too many positional arguments for "Application" -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesOrderingKwOnlyWithSentinelAndSubclass] # flags: --python-version 3.10 @@ -429,6 +429,8 @@ D(123, "World") # E: Argument 1 to "D" has incompatible type "int"; expected "s D("Hello", False) # E: Argument 2 to "D" has incompatible type "bool"; expected "str" D(123, False) # E: Argument 1 to "D" has incompatible type "int"; expected "str" # E: Argument 2 to "D" has incompatible type "bool"; expected "str" +[builtins fixtures/dataclasses.pyi] + [case testDataclassesOrderingKwOnlyWithMultipleSentinel] # flags: --python-version 3.10 from dataclasses import dataclass, field, KW_ONLY @@ -441,7 +443,7 @@ class Base: __: KW_ONLY # E: There may not be more than one field with the KW_ONLY type w: int = 1 -[builtins fixtures/list.pyi] +[builtins fixtures/dataclasses.pyi] [case testDataclassesClassmethods] # flags: --python-version 3.7 @@ -1249,6 +1251,7 @@ reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> __main__.B" [builtins fixtures/dataclasses.pyi] [case testDataclassHasAttributeWithFields] +# flags: --python-version 3.7 from dataclasses import dataclass @dataclass diff --git a/test-data/unit/foo.py b/test-data/unit/foo.py new file mode 100644 index 0000000000000..53059a1fefd62 --- /dev/null +++ b/test-data/unit/foo.py @@ -0,0 +1,22 @@ +from typing import Tuple + +import attr + + +@attr.s(auto_attribs=True) +class A: + bar: int + baz: str + +reveal_type(A.__attrs_attrs__) +reveal_type(A.__attrs_attrs__[0]) +reveal_type(A.__attrs_attrs__[1]) +reveal_type(A.__attrs_attrs__[2]) + + + +t: Tuple[int, ...] +reveal_type(t) +t2: Tuple[int, str] +reveal_type(t2) + From e932c500c87172996431e1692f025a84e562cf2c Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Fri, 13 Aug 2021 13:11:24 +0300 Subject: [PATCH 13/15] Fix self mypy check --- mypy/plugins/attrs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 2a60d1bc454c0..9822b441df58e 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -300,7 +300,7 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext', ctx.api.defer() return - _add_attrs_magic_attribute(ctx, raw_attr_types=[info.get(attr.name).type for attr in attributes]) + _add_attrs_magic_attribute(ctx, raw_attr_types=[info[attr.name].type for attr in attributes]) # Save the attributes so that subclasses can reuse them. ctx.cls.info.metadata['attrs'] = { @@ -706,15 +706,15 @@ def _add_init(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute], def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext', - raw_attr_types: 'List[Type]') -> None: + raw_attr_types: 'List[Optional[Type]]') -> None: attr_name = '__attrs_attrs__' any_type = AnyType(TypeOfAny.explicit) - attributes_types = [ + 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 ] fallback_type = ctx.api.named_type('__builtins__.tuple', [ - ctx.api.named_type_or_none('attr.Attribute', [any_type]), + 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)) var.info = ctx.cls.info From be97c260879dca0116ebdf337447e13ee3424fc2 Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Mon, 16 Aug 2021 07:48:16 +0300 Subject: [PATCH 14/15] Fix tests --- test-data/unit/check-dataclasses.test | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 31eb0da762022..62959488aa27c 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1258,7 +1258,7 @@ from dataclasses import dataclass class A: pass -reveal_type(A.__dataclass_fields__) # N: Revealed type is 'builtins.dict[builtins.str, dataclasses.Field[Any]]' +reveal_type(A.__dataclass_fields__) # N: Revealed type is "builtins.dict[builtins.str, dataclasses.Field[Any]]" [builtins fixtures/dict.pyi] @@ -1279,6 +1279,8 @@ reveal_type(a.x) # N: Revealed type is "def (builtins.int) -> builtins.int" reveal_type(a.y) # N: Revealed type is "def (builtins.int) -> builtins.int" reveal_type(A.y) # N: Revealed type is "def (builtins.int) -> builtins.int" +[builtins fixtures/dataclasses.pyi] + [case testDataclassCallableFieldAssignment] # flags: --python-version 3.7 from dataclasses import dataclass @@ -1296,3 +1298,5 @@ def x2(s: str) -> str: a = A(lambda i:i) a.x = x a.x = x2 # E: Incompatible types in assignment (expression has type "Callable[[str], str]", variable has type "Callable[[int], int]") + +[builtins fixtures/dataclasses.pyi] From 528d58743d00672941f8c8dc0b35873dd0cc334a Mon Sep 17 00:00:00 2001 From: Timofey Kukushkin Date: Fri, 20 Aug 2021 19:44:08 +0300 Subject: [PATCH 15/15] Delete stray file --- test-data/unit/foo.py | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 test-data/unit/foo.py diff --git a/test-data/unit/foo.py b/test-data/unit/foo.py deleted file mode 100644 index 53059a1fefd62..0000000000000 --- a/test-data/unit/foo.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Tuple - -import attr - - -@attr.s(auto_attribs=True) -class A: - bar: int - baz: str - -reveal_type(A.__attrs_attrs__) -reveal_type(A.__attrs_attrs__[0]) -reveal_type(A.__attrs_attrs__[1]) -reveal_type(A.__attrs_attrs__[2]) - - - -t: Tuple[int, ...] -reveal_type(t) -t2: Tuple[int, str] -reveal_type(t2) -