[ty] Emit diagnostic for NamedTuple and TypedDict decorated with dataclass#22672
[ty] Emit diagnostic for NamedTuple and TypedDict decorated with dataclass#22672charliermarsh merged 7 commits intomainfrom
NamedTuple and TypedDict decorated with dataclass#22672Conversation
e3555d1 to
e2b3a1c
Compare
Typing conformance resultsNo changes detected ✅ |
|
e2b3a1c to
47015ed
Compare
AlexWaygood
left a comment
There was a problem hiding this comment.
Nice. I wonder if we should also flag enums and protocols decorated with @dataclass. Those also indicate that the user is probably very confused. And the enum docs explicitly lay out that adding @dataclass to an enum class is not supported
|
|
||
| # TODO: This should emit `invalid-named-tuple` but currently emits `no-matching-overload` | ||
| # due to overload resolution not matching `DynamicNamedTuple` against `type[_T]`. | ||
| # error: [no-matching-overload] |
There was a problem hiding this comment.
This is turning out to be fairly annoying to fix.
There was a problem hiding this comment.
Looks like it's a pre-existing issue, though? I think fine to defer it for this PR?
There was a problem hiding this comment.
👍 Yeah that was my plan.
0615efb to
d7ece7e
Compare
d7ece7e to
b450573
Compare
| /// Applying `@dataclass` to a class that inherits from `NamedTuple`, `TypedDict`, | ||
| /// `Enum`, or `Protocol` is invalid: | ||
| /// | ||
| /// - `NamedTuple` and `TypedDict` classes will raise an exception at runtime when |
There was a problem hiding this comment.
| /// - `NamedTuple` and `TypedDict` classes will raise an exception at runtime when | |
| /// - `NamedTuple` and `TypedDict` classes will often raise an exception at runtime when |
since you can avoid the TypedDict exception if you're very careful and use keyword arguments to instantiate the class:
>>> from typing import TypedDict
>>> from dataclasses import dataclass
>>> @dataclass
... class Foo(TypedDict):
... x: int
...
>>> Foo(x=42)
{'x': 42}| class.name(self.db()), | ||
| )); | ||
| diagnostic.info( | ||
| "An exception will be raised when instantiating the class at runtime", |
There was a problem hiding this comment.
| "An exception will be raised when instantiating the class at runtime", | |
| "An exception may be raised when instantiating the class at runtime", |
a6579bc to
3fd1211
Compare
|
|
||
| ### Protocol classes | ||
|
|
||
| Applying `@dataclass` to a protocol class is invalid because protocols define interfaces and cannot |
There was a problem hiding this comment.
| Applying `@dataclass` to a protocol class is invalid because protocols define interfaces and cannot | |
| Applying `@dataclass` to a protocol class is invalid because protocols define abstract interfaces and cannot |
crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md
Outdated
Show resolved
Hide resolved
| y: str | ||
| ``` | ||
|
|
||
| The same error occurs with `dataclasses.dataclass` used as a function call: |
There was a problem hiding this comment.
| The same error occurs with `dataclasses.dataclass` used as a function call: | |
| The same error occurs with `dataclasses.dataclass` used with parentheses: |
| } | ||
| if class.is_typed_dict(self.db()) { |
There was a problem hiding this comment.
| } | |
| if class.is_typed_dict(self.db()) { | |
| } else if class.is_typed_dict(self.db()) { |
## Summary Fixes some TODOs introduced in #22672 around cases like the following: ```python from collections import namedtuple from dataclasses import dataclass NT = namedtuple("NT", "x y") # error: [invalid-dataclass] "Cannot use `dataclass()` on a `NamedTuple` class" dataclass(NT) ``` On main, `dataclass(NT)` emits `# error: [no-matching-overload]` instead, which is wrong -- the overload does match! On main, the logic proceeds as follows: 1. `dataclass` has two overloads: - `dataclass(cls: type[_T], ...) -> type[_T]` - `dataclass(cls: None = None, ...) -> Callable[[type[_T]], type[_T]]` 2. When `dataclass(NT)` is called: - Arity check: Both overloads accept one positional argument, so both pass. - Type checking on first overload: `NT` matches `type[_T]`... but then `invalid_dataclass_target()` runs and adds `InvalidDataclassApplication` error - Type checking on second overload: `NT` doesn't match `None`, so we have a type error. 3. After type checking, both overloads have errors. 4. `matching_overload_index()` filters by `overload.as_result().is_ok()`, which checks if `errors.is_empty()`. Since both overloads have errors, neither matches... 5. We emit the "No overload matches arguments" error. Instead, we now differentiate between non-matching errors, and errors that occur when we _do_ match, but the call has some other semantic failure.
Summary
Closes astral-sh/ty#2515.
Closes astral-sh/ty#2527.