[ty] Add new diagnostic for invalid dataclass field orders#19825
[ty] Add new diagnostic for invalid dataclass field orders#19825sharkdp merged 3 commits intoastral-sh:mainfrom
Conversation
Typing conformance results improved 🎉The percentage of diagnostics emitted that were expected errors increased from 82.22% to 82.27%. The percentage of expected errors that received a diagnostic increased from 72.71% to 72.99%. Summary
True positives addedDetails
|
|
3e9dd1b to
781f4f2
Compare
9eee579 to
a4fe617
Compare
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
d904f5d to
6937115
Compare
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
carljm
left a comment
There was a problem hiding this comment.
Thank you! The updates look great, and I agree we don't need a new abstraction layer (yet?) -- it's fine to just ensure that the fields returned from own_fields already have a fully correct kw_only value. My main concern is just that we be clear about the contract: do the Field objects returned from own_fields represent exactly what is explicitly specified in that Field definition, or do they represent our full understanding of the field, including what's inherited from dataclass arguments and dataclass-transform arguments? I think it's fine if it's the latter, so long as that's consistent.
Sorry for requesting changes again -- in many cases we are happy to land PRs with remaining TODOs, but adding new false positives in the ecosystem is something we are very reluctant to do, and at the moment this PR adds a lot of them. It turns out we need to improve our support for other dataclass features significantly in order to add this diagnostic without false positives.
crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md
Outdated
Show resolved
Hide resolved
This comment was marked as resolved.
This comment was marked as resolved.
714f8d5 to
32fae78
Compare
This comment was marked as resolved.
This comment was marked as resolved.
|
Thank you! The conformance suite results look great here now! The ecosystem results look mostly great :) The one case where it looks like we are still adding false positives is that we don't support Ideally, we would support |
|
I'll fix the |
carljm
left a comment
There was a problem hiding this comment.
Code here looks great, just a few minor comments! I think once we resolve the field_specifiers false positives, this is ready to go.
crates/ty_python_semantic/resources/mdtest/dataclasses/dataclass_transform.md
Outdated
Show resolved
Hide resolved
crates/ty_python_semantic/resources/mdtest/dataclasses/dataclass_transform.md
Show resolved
Hide resolved
| .is_some_and(|params| params.contains(DataclassParams::KW_ONLY)) | ||
| || self.try_metaclass(db).is_ok_and(|(_, transformer_params)| { | ||
| transformer_params.is_some_and(|params| { | ||
| params.contains(DataclassTransformerParams::KW_ONLY_DEFAULT) | ||
| }) | ||
| }) | ||
| { | ||
| // `@dataclass(kw_only=True)` or `@dataclass_transform(kw_only_default=True)` | ||
| field.kw_only = Some(true); |
There was a problem hiding this comment.
There's a test (I commented above) that I would expect this logic to fix, but it doesn't fix it. And I think I understand why. Currently DataclassParams is just a bitset, but this isn't really adequate when we have to compose it with DataclassTransformerParams. With the bitset representation, we can't distinguish "no kw_only argument" from kw_only=False -- they are both represented by the bit being off. I think we will need to change DataclassParams so that at least kw_only can be tri-valued. Because "no kw_only arg" should default to the dataclass-transform's kw_only_default value, but kw_only=False should potentially override it.
I think this doesn't need to be fixed in this PR, but it does impact the correctness of this code, so I think we should add a TODO comment here.
| .is_some_and(|params| params.contains(DataclassParams::KW_ONLY)) | |
| || self.try_metaclass(db).is_ok_and(|(_, transformer_params)| { | |
| transformer_params.is_some_and(|params| { | |
| params.contains(DataclassTransformerParams::KW_ONLY_DEFAULT) | |
| }) | |
| }) | |
| { | |
| // `@dataclass(kw_only=True)` or `@dataclass_transform(kw_only_default=True)` | |
| field.kw_only = Some(true); | |
| // TODO `kw_only=False` should override `kw_only_default=True`, this will require | |
| // a tri-valued representation for `DataclassParams::KW_ONLY` | |
| .is_some_and(|params| params.contains(DataclassParams::KW_ONLY)) | |
| || self.try_metaclass(db).is_ok_and(|(_, transformer_params)| { | |
| transformer_params.is_some_and(|params| { | |
| params.contains(DataclassTransformerParams::KW_ONLY_DEFAULT) | |
| }) | |
| }) | |
| { | |
| // `@dataclass(kw_only=True)` or `@dataclass_transform(kw_only_default=True)` | |
| field.kw_only = Some(true); |
There was a problem hiding this comment.
With the bitset representation, we can't distinguish "no kw_only argument" from kw_only=False -- they are both represented by the bit being off. I think we will need to change DataclassParams so that at least kw_only can be tri-valued. Because "no kw_only arg" should default to the dataclass-transform's kw_only_default value, but kw_only=False should potentially override it.
This issue has been resolved in the meantime (and I added some new tests cases for it). We still use a bitset, but we always set it to the final "computed" value. So for kw_only_default=True and no kw_only parameter in the field-specifier, we set kw_only=True. When kw_only_default and kw_only are unset, we set it to the implicit default (kw_only=False).
c2c37b0 to
4826d75
Compare
|
I started rebasing this branch, but the changes on |
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
unused-type-ignore-comment |
1 | 18 | 0 |
dataclass-field-order |
13 | 0 | 0 |
possibly-missing-attribute |
0 | 3 | 1 |
invalid-argument-type |
1 | 2 | 0 |
invalid-await |
0 | 2 | 0 |
unresolved-attribute |
0 | 0 | 2 |
invalid-return-type |
0 | 0 | 1 |
no-matching-overload |
0 | 1 | 0 |
| Total | 15 | 26 | 4 |
We do now support I'll look into resolving the remaining review comments soon. |
4826d75 to
3c6d820
Compare
| tags: list[str] = field(default_factory=list) | ||
| email: str = field(kw_only=True) | ||
| internal_notes: str = field(alias="notes") | ||
| internal_notes: str = field(alias="notes", default="") |
There was a problem hiding this comment.
The new check founds some bugs 😄
|
The new diagnostics for |
588367a to
2ebd8d0
Compare
The fix in 2ebd8d0 got rid of some of these. This leaves us with:
These would require an attrs plugin. https://github.com/python-attrs/attrs/blob/c44b8b0052ca3a4594219efa702129e1537d8966/tests/test_hooks.py#L152
These seem correct to me. The fields are preceded by a @dataclass
class C:
x: Final[int] = 1
y: str… and do consider |
sharkdp
left a comment
There was a problem hiding this comment.
Ok, this is ready to go. Thank you very much @PrettyWood and @carljm for the majority of the work here.
We should merge #23069 first (currently included in this PR) and then I will rebase one final time.
Co-authored-by: Carl Meyer <[email protected]> Co-authored-by: David Peter <[email protected]>
2ebd8d0 to
c0ca8bd
Compare
Summary
Emit diagnostic when defining a field without a default after a field with a default, see existing TODO
Part of astral-sh/ty#111
Test Plan
Updated Markdown tests