[ty] Allow Final variable assignments in __post_init__#24529
[ty] Allow Final variable assignments in __post_init__#24529charliermarsh merged 6 commits intomainfrom
Final variable assignments in __post_init__#24529Conversation
Typing conformance resultsThe percentage of diagnostics emitted that were expected errors held steady at 87.72%. The percentage of expected errors that received a diagnostic held steady at 82.85%. The number of fully passing files held steady at 74/132. SummaryHow are test cases classified?Each test case represents one expected error annotation or a group of annotations sharing a tag. Counts are per test case, not per diagnostic — multiple diagnostics on the same line count as one. Required annotations (
True positives changed (5)5 diagnostics
|
Memory usage reportMemory usage unchanged ✅ |
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-assignment |
0 | 1 | 0 |
| Total | 0 | 1 | 0 |
Raw diff:
meson (https://github.com/mesonbuild/meson)
- mesonbuild/options.py:330:9 error[invalid-assignment] Cannot assign to final attribute `default` on type `Self@__post_init__`: `Final` attributes can only be assigned in the class body or `__init__`| @dataclass | ||
| class C: | ||
| def __post_init__(self): | ||
| self.x: Final[int] = 1 |
There was a problem hiding this comment.
Can we add another test here where a field is (accidentally) redeclared? Because I think this should probably be an error? (the developer might think that the field is now final, when it's not)
@dataclass
class C:
an_actual_field: str
def __post_init__(self):
self.an_actual_field: Final[str] = "bar"If that's hard to do, it may be fine to leave it as a future-work TODO.
| object_ty | ||
| .nominal_class(db) | ||
| .or_else(|| object_ty.to_class_type(db)) | ||
| .and_then(|class_ty| class_ty.static_class_literal(db)) | ||
| .is_some_and(|(class_literal, _)| class_literal.is_dataclass_like(db)) |
There was a problem hiding this comment.
What kind of case is covered by .or_else(|| object_ty.to_class_type(db))? Isn't that conflating instance types with class-literal types somehow? I think I would have expected something like
| object_ty | |
| .nominal_class(db) | |
| .or_else(|| object_ty.to_class_type(db)) | |
| .and_then(|class_ty| class_ty.static_class_literal(db)) | |
| .is_some_and(|(class_literal, _)| class_literal.is_dataclass_like(db)) | |
| object_ty.nominal_class(db).is_some_and(|cls| { | |
| cls.static_class_literal(db) | |
| .is_some_and(|(class_literal, _)| class_literal.is_dataclass_like(db)) | |
| }) |
There was a problem hiding this comment.
Ah ok, after changing this, it looks like this was necessary to catch these kinds of cases in the conformance suite (just for the diagnostic message in this case): https://github.com/python/typing/blob/1df1565c69730d88ce6877009d268ba1d602af1e/conformance/tests/dataclasses_final.py#L27
| diagnostic.set_primary_message(if allows_post_init { | ||
| "`Final` attributes can only be assigned in the class body, `__init__`, or `__post_init__` on dataclass-like classes" | ||
| } else { | ||
| "`Final` attributes can only be assigned in the class body or `__init__`" | ||
| }); |
|
Haven't reviewed the PR and don't know if it makes anything easier here, but FWIW I don't think we need to restrict this to dataclass-likes. |
Thanks, yes. I still think it's valuable to distinguish dataclass-likes and normal classes if only for purposes of special-casing the diagnostic message. I'd rather not mention |
b15914c to
4aa98bf
Compare
* main: [ty] Fix bad diagnostic range for incorrect implicit `__init_subclass__` calls (#24541) [ty] Add a `SupportedPythonVersion` enum (#24412) [ty] Ignore unsupported editor-selected Python versions (#24498) [ty] Add snapshots for `__init_subclass__` diagnostics (#24539) [ty] Minor fix in tests (#24538) [ty] Allow `Final` variable assignments in `__post_init__` (#24529) [ty] Expand test suite for assignment errors (#24537) [ty] Use `map`, not `__map`, as the name of the mapping parameter in `TypedDict` `__init__` methods (#24535) [ty] Rework logic for synthesizing `TypedDict` methods (#24534) [flake8-bandit] Fix S103 false positives and negatives in mask analysis (#24424) [ty] mdtest.py: update dependencies (#24533) Rename patterns and arguments source order iterator method (#24532) [ty] Omit invalid keyword arguments from `TypedDict` signature (#24522) [ty] support super() in metaclass methods (#24483) [ty] Synthesize `__init__` for `TypedDict` (#24476)
* main: Bump typing conformance suite commit to latest upstream (#24553) [ty] Reject deleting`Final` attributes (#24508) [ty] Respect property deleters in attribute deletion checks (#24500) [ty] stop unioning Unknown into types of un-annotated attributes (#24531) [ty] Fix bad diagnostic range for incorrect implicit `__init_subclass__` calls (#24541) [ty] Add a `SupportedPythonVersion` enum (#24412) [ty] Ignore unsupported editor-selected Python versions (#24498) [ty] Add snapshots for `__init_subclass__` diagnostics (#24539) [ty] Minor fix in tests (#24538) [ty] Allow `Final` variable assignments in `__post_init__` (#24529) [ty] Expand test suite for assignment errors (#24537) [ty] Use `map`, not `__map`, as the name of the mapping parameter in `TypedDict` `__init__` methods (#24535) [ty] Rework logic for synthesizing `TypedDict` methods (#24534) [flake8-bandit] Fix S103 false positives and negatives in mask analysis (#24424) [ty] mdtest.py: update dependencies (#24533) Rename patterns and arguments source order iterator method (#24532) [ty] Omit invalid keyword arguments from `TypedDict` signature (#24522) [ty] support super() in metaclass methods (#24483) [ty] Synthesize `__init__` for `TypedDict` (#24476)
Summary
As long as the
__post_init__is in a dataclass, e.g., the following is accepted:Closes astral-sh/ty#3247.