Skip to content

TCH003 false-positive when using dataclasses.KW_ONLY #12859

@flying-sheep

Description

@flying-sheep

Seen in Ruff 0.5.7

In the following code, from __future__ import annotations causes the KW_ONLY to become a string.
The dataclasses module can deal with that, but only if the object isn’t in a if TYPE_CHECKING block.
Ruff supports this for things like ClassVar¹, i.e. if a ClassVar is used in a dataclass, Ruff knows the from typing import ClassVar needs to come out of the if TYPE_CHECKING block. However the same doesn’t seem to be true here.

from __future__ import annotations

from dataclasses import KW_ONLY, dataclass

@dataclass
class Test:
    a: int
    _: KW_ONLY
    b: str

¹This is probably the place that should handle KW_ONLY

/// Returns `true` if an annotation will be inspected at runtime by the `dataclasses` module.
///
/// Specifically, detects whether an annotation is to either `dataclasses.InitVar` or
/// `typing.ClassVar` within a `@dataclass` class definition.
///
/// See: <https://docs.python.org/3/library/dataclasses.html#init-only-variables>
pub(crate) fn is_dataclass_meta_annotation(annotation: &Expr, semantic: &SemanticModel) -> bool {
if !semantic.seen_module(Modules::DATACLASSES) {
return false;
}
// Determine whether the assignment is in a `@dataclass` class definition.
if let ScopeKind::Class(class_def) = semantic.current_scope().kind {
if class_def.decorator_list.iter().any(|decorator| {
semantic
.resolve_qualified_name(map_callable(&decorator.expression))
.is_some_and(|qualified_name| {
matches!(qualified_name.segments(), ["dataclasses", "dataclass"])
})
}) {
// Determine whether the annotation is `typing.ClassVar` or `dataclasses.InitVar`.
return semantic
.resolve_qualified_name(map_subscript(annotation))
.is_some_and(|qualified_name| {
matches!(qualified_name.segments(), ["dataclasses", "InitVar"])
|| semantic.match_typing_qualified_name(&qualified_name, "ClassVar")
});
}
}
false
}

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions