diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 2b4982a36bb6..99d4ef56a540 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -546,7 +546,6 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: # in the parent. We can implement this via a dict without disrupting the attr order # because dicts preserve insertion order in Python 3.7+. found_attrs: dict[str, DataclassAttribute] = {} - found_dataclass_supertype = False for info in reversed(cls.info.mro[1:-1]): if "dataclass_tag" in info.metadata and "dataclass" not in info.metadata: # We haven't processed the base class yet. Need another pass. @@ -556,7 +555,6 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: # Each class depends on the set of attributes in its dataclass ancestors. self._api.add_plugin_dependency(make_wildcard_trigger(info.fullname)) - found_dataclass_supertype = True for data in info.metadata["dataclass"]["attributes"]: name: str = data["name"] @@ -720,8 +718,7 @@ def collect_attributes(self) -> list[DataclassAttribute] | None: ) all_attrs = list(found_attrs.values()) - if found_dataclass_supertype: - all_attrs.sort(key=lambda a: a.kw_only) + all_attrs.sort(key=lambda a: a.kw_only) # Third, ensure that arguments without a default don't follow # arguments that have a default and that the KW_ONLY sentinel diff --git a/test-data/unit/check-dataclass-transform.test b/test-data/unit/check-dataclass-transform.test index 8213f8df282a..9cc9c03448d6 100644 --- a/test-data/unit/check-dataclass-transform.test +++ b/test-data/unit/check-dataclass-transform.test @@ -265,7 +265,7 @@ class Foo: Foo(a=5, b_=1) # E: Unexpected keyword argument "a" for "Foo" Foo(a_=1, b_=1, noinit=1) # E: Unexpected keyword argument "noinit" for "Foo" -Foo(1, 2, 3) # E: Too many positional arguments for "Foo" +Foo(1, 2, 3) # (a, b, unused1) foo = Foo(1, 2, kwonly=3) reveal_type(foo.noinit) # N: Revealed type is "builtins.int" reveal_type(foo.unused1) # N: Revealed type is "builtins.int" diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index dbcb4c82072c..b978a2e8428d 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -460,14 +460,16 @@ from dataclasses import dataclass, field, KW_ONLY class Application: _: KW_ONLY name: str = 'Unnamed' - rating: int = field(kw_only=False) # E: Attributes without a default cannot follow attributes with one + rating: int = field(kw_only=False) Application(name='name', rating=5) -Application() # E: Missing positional argument "name" in call to "Application" -Application('name') # E: Too many positional arguments for "Application" # E: Too few arguments for "Application" -Application('name', 123) # E: Too many positional arguments for "Application" -Application('name', rating=123) # E: Too many positional arguments for "Application" - +Application() # E: Missing positional argument "rating" in call to "Application" +Application(123) +Application('name') # E: Argument 1 to "Application" has incompatible type "str"; expected "int" +Application('name', 123) # E: Too many positional arguments for "Application" \ + # E: Argument 1 to "Application" has incompatible type "str"; expected "int" \ + # E: Argument 2 to "Application" has incompatible type "int"; expected "str" +Application(123, rating=123) # E: "Application" gets multiple values for keyword argument "rating" [builtins fixtures/dataclasses.pyi] [case testDataclassesOrderingKwOnlyWithSentinelAndSubclass] @@ -2610,3 +2612,30 @@ class B2(B1): # E: A NamedTuple cannot be a dataclass pass [builtins fixtures/tuple.pyi] + +[case testDataclassKwOnlyArgsLast] +from dataclasses import dataclass, field + +@dataclass +class User: + id: int = field(kw_only=True) + name: str + +User("Foo", id=0) +[builtins fixtures/tuple.pyi] + +[case testDataclassKwOnlyArgsDefaultAllowedNonLast] +from dataclasses import dataclass, field + +@dataclass +class User: + id: int = field(kw_only=True, default=0) + name: str + +User() # E: Missing positional argument "name" in call to "User" +User("") +User(0) # E: Argument 1 to "User" has incompatible type "int"; expected "str" +User("", 0) # E: Too many positional arguments for "User" +User("", id=0) +User("", name="") # E: "User" gets multiple values for keyword argument "name" +[builtins fixtures/tuple.pyi]