Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
5 changes: 1 addition & 4 deletions mypy/plugins/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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"]
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-dataclass-transform.test
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
41 changes: 35 additions & 6 deletions test-data/unit/check-dataclasses.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]