Compare commits

...

6 Commits

Author SHA1 Message Date
David Peter
7d3ad59970 Merge remote-tracking branch 'origin/main' into david/dataclass-final-fields 2025-07-08 14:35:54 +02:00
David Peter
9a4b85d845 [ty] Add tests for dataclass fields annotated with Final (#19202)
## Summary

Adds some tests for dataclass fields that are annotated with `Final`
(see comment
[here](https://github.com/astral-sh/ruff/pull/15768#issuecomment-3044737645)).
Turns out that nothing is needed here, everything already works as
expected (apart from the fact that we can assign to `Final` fields,
which is tracked in https://github.com/astral-sh/ty/issues/158

## Test Plan

New Markdown tests
2025-07-08 12:33:46 +00:00
David Peter
d133d7ab03 Add comment 2025-07-08 14:30:30 +02:00
David Peter
7d642e6416 Add test for 'Final'-qualified field without a default 2025-07-08 14:25:19 +02:00
David Peter
6d8c84bde9 [ty] Clarify diagnostic message (#19203)
This diagnostic message was missing the word "type"
2025-07-08 14:21:20 +02:00
David Peter
b92a283f35 [ty] Add tests for dataclass fields annotated with Final 2025-07-08 13:23:19 +02:00
4 changed files with 33 additions and 2 deletions

View File

@@ -1787,7 +1787,7 @@ date.day = 8
date.month = 4
date.year = 2025
# error: [unresolved-attribute] "Can not assign object of `Literal["UTC"]` to attribute `tz` on type `Date` with custom `__setattr__` method."
# error: [unresolved-attribute] "Can not assign object of type `Literal["UTC"]` to attribute `tz` on type `Date` with custom `__setattr__` method."
date.tz = "UTC"
```

View File

@@ -444,6 +444,33 @@ To do
To do
## `Final` fields
Dataclass fields can be annotated with `Final`, which means that the field cannot be reassigned
after the instance is created. Fields that are additionally annotated with `ClassVar` are not part
of the `__init__` signature.
```py
from dataclasses import dataclass
from typing import Final, ClassVar
@dataclass
class C:
# a `Final` annotation without a right-hand side is not allowed in normal classes,
# but valid for dataclasses. The field will be initialized in the synthesized
# `__init__` method
instance_variable_no_default: Final[int]
instance_variable: Final[int] = 1
class_variable1: ClassVar[Final[int]] = 1
class_variable2: ClassVar[Final[int]] = 1
reveal_type(C.__init__) # revealed: (self: C, instance_variable_no_default: int, instance_variable: int = Literal[1]) -> None
c = C(1)
# TODO: this should be an error
c.instance_variable = 2
```
## Inheritance
### Normal class inheriting from a dataclass

View File

@@ -162,6 +162,10 @@ from typing import Final
# TODO: This should be an error
NO_RHS: Final
class C:
# TODO: This should be an error
NO_RHS: Final
```
[`typing.final`]: https://docs.python.org/3/library/typing.html#typing.Final

View File

@@ -3495,7 +3495,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target)
{
builder.into_diagnostic(format_args!(
"Can not assign object of `{}` to attribute \
"Can not assign object of type `{}` to attribute \
`{attribute}` on type `{}` with \
custom `__setattr__` method.",
value_ty.display(db),