mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 14:21:24 +00:00
[ty] Support dataclasses.KW_ONLY
(#18677)
This commit is contained in:
parent
c3aa965546
commit
2b15f1d240
2 changed files with 71 additions and 2 deletions
|
@ -713,6 +713,49 @@ But calling `asdict` on the class object is not allowed:
|
||||||
asdict(Foo)
|
asdict(Foo)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `dataclasses.KW_ONLY`
|
||||||
|
|
||||||
|
If an attribute is annotated with `dataclasses.KW_ONLY`, it is not added to the synthesized
|
||||||
|
`__init__` of the class. Instead, this special marker annotation causes Python at runtime to ensure
|
||||||
|
that all annotations following it have keyword-only parameters generated for them in the class's
|
||||||
|
synthesized `__init__` method.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.10"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from dataclasses import dataclass, field, KW_ONLY
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class C:
|
||||||
|
x: int
|
||||||
|
_: KW_ONLY
|
||||||
|
y: str
|
||||||
|
|
||||||
|
# error: [missing-argument]
|
||||||
|
# error: [too-many-positional-arguments]
|
||||||
|
C(3, "")
|
||||||
|
|
||||||
|
C(3, y="")
|
||||||
|
```
|
||||||
|
|
||||||
|
Using `KW_ONLY` to annotate more than one field in a dataclass causes a `TypeError` to be raised at
|
||||||
|
runtime:
|
||||||
|
|
||||||
|
```py
|
||||||
|
@dataclass
|
||||||
|
class Fails:
|
||||||
|
a: int
|
||||||
|
b: KW_ONLY
|
||||||
|
c: str
|
||||||
|
|
||||||
|
# TODO: we should emit an error here
|
||||||
|
# (two different names with `KW_ONLY` annotations in the same dataclass means the class fails at runtime)
|
||||||
|
d: KW_ONLY
|
||||||
|
```
|
||||||
|
|
||||||
## Other special cases
|
## Other special cases
|
||||||
|
|
||||||
### `dataclasses.dataclass`
|
### `dataclasses.dataclass`
|
||||||
|
|
|
@ -1313,9 +1313,21 @@ impl<'db> ClassLiteral<'db> {
|
||||||
let field_policy = CodeGeneratorKind::from_class(db, self)?;
|
let field_policy = CodeGeneratorKind::from_class(db, self)?;
|
||||||
|
|
||||||
let signature_from_fields = |mut parameters: Vec<_>| {
|
let signature_from_fields = |mut parameters: Vec<_>| {
|
||||||
|
let mut kw_only_field_seen = false;
|
||||||
for (name, (mut attr_ty, mut default_ty)) in
|
for (name, (mut attr_ty, mut default_ty)) in
|
||||||
self.fields(db, specialization, field_policy)
|
self.fields(db, specialization, field_policy)
|
||||||
{
|
{
|
||||||
|
if attr_ty
|
||||||
|
.into_nominal_instance()
|
||||||
|
.is_some_and(|instance| instance.class.is_known(db, KnownClass::KwOnly))
|
||||||
|
{
|
||||||
|
// Attributes annotated with `dataclass.KW_ONLY` are not present in the synthesized
|
||||||
|
// `__init__` method, ; they only used to indicate that the parameters after this are
|
||||||
|
// keyword-only.
|
||||||
|
kw_only_field_seen = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// The descriptor handling below is guarded by this fully-static check, because dynamic
|
// The descriptor handling below is guarded by this fully-static check, because dynamic
|
||||||
// types like `Any` are valid (data) descriptors: since they have all possible attributes,
|
// types like `Any` are valid (data) descriptors: since they have all possible attributes,
|
||||||
// they also have a (callable) `__set__` method. The problem is that we can't determine
|
// they also have a (callable) `__set__` method. The problem is that we can't determine
|
||||||
|
@ -1360,8 +1372,12 @@ impl<'db> ClassLiteral<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut parameter =
|
let mut parameter = if kw_only_field_seen {
|
||||||
Parameter::positional_or_keyword(name).with_annotated_type(attr_ty);
|
Parameter::keyword_only(name)
|
||||||
|
} else {
|
||||||
|
Parameter::positional_or_keyword(name)
|
||||||
|
}
|
||||||
|
.with_annotated_type(attr_ty);
|
||||||
|
|
||||||
if let Some(default_ty) = default_ty {
|
if let Some(default_ty) = default_ty {
|
||||||
parameter = parameter.with_default_type(default_ty);
|
parameter = parameter.with_default_type(default_ty);
|
||||||
|
@ -2149,6 +2165,7 @@ pub enum KnownClass {
|
||||||
NotImplementedType,
|
NotImplementedType,
|
||||||
// dataclasses
|
// dataclasses
|
||||||
Field,
|
Field,
|
||||||
|
KwOnly,
|
||||||
// _typeshed._type_checker_internals
|
// _typeshed._type_checker_internals
|
||||||
NamedTupleFallback,
|
NamedTupleFallback,
|
||||||
}
|
}
|
||||||
|
@ -2234,6 +2251,7 @@ impl<'db> KnownClass {
|
||||||
| Self::NotImplementedType
|
| Self::NotImplementedType
|
||||||
| Self::Classmethod
|
| Self::Classmethod
|
||||||
| Self::Field
|
| Self::Field
|
||||||
|
| Self::KwOnly
|
||||||
| Self::NamedTupleFallback => Truthiness::Ambiguous,
|
| Self::NamedTupleFallback => Truthiness::Ambiguous,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2309,6 +2327,7 @@ impl<'db> KnownClass {
|
||||||
| Self::NotImplementedType
|
| Self::NotImplementedType
|
||||||
| Self::UnionType
|
| Self::UnionType
|
||||||
| Self::Field
|
| Self::Field
|
||||||
|
| Self::KwOnly
|
||||||
| Self::NamedTupleFallback => false,
|
| Self::NamedTupleFallback => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2385,6 +2404,7 @@ impl<'db> KnownClass {
|
||||||
}
|
}
|
||||||
Self::NotImplementedType => "_NotImplementedType",
|
Self::NotImplementedType => "_NotImplementedType",
|
||||||
Self::Field => "Field",
|
Self::Field => "Field",
|
||||||
|
Self::KwOnly => "KW_ONLY",
|
||||||
Self::NamedTupleFallback => "NamedTupleFallback",
|
Self::NamedTupleFallback => "NamedTupleFallback",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2615,6 +2635,7 @@ impl<'db> KnownClass {
|
||||||
| Self::Deque
|
| Self::Deque
|
||||||
| Self::OrderedDict => KnownModule::Collections,
|
| Self::OrderedDict => KnownModule::Collections,
|
||||||
Self::Field => KnownModule::Dataclasses,
|
Self::Field => KnownModule::Dataclasses,
|
||||||
|
Self::KwOnly => KnownModule::Dataclasses,
|
||||||
Self::NamedTupleFallback => KnownModule::TypeCheckerInternals,
|
Self::NamedTupleFallback => KnownModule::TypeCheckerInternals,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2679,6 +2700,7 @@ impl<'db> KnownClass {
|
||||||
| Self::NamedTuple
|
| Self::NamedTuple
|
||||||
| Self::NewType
|
| Self::NewType
|
||||||
| Self::Field
|
| Self::Field
|
||||||
|
| Self::KwOnly
|
||||||
| Self::NamedTupleFallback => false,
|
| Self::NamedTupleFallback => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2745,6 +2767,7 @@ impl<'db> KnownClass {
|
||||||
| Self::NamedTuple
|
| Self::NamedTuple
|
||||||
| Self::NewType
|
| Self::NewType
|
||||||
| Self::Field
|
| Self::Field
|
||||||
|
| Self::KwOnly
|
||||||
| Self::NamedTupleFallback => false,
|
| Self::NamedTupleFallback => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2818,6 +2841,7 @@ impl<'db> KnownClass {
|
||||||
}
|
}
|
||||||
"_NotImplementedType" => Self::NotImplementedType,
|
"_NotImplementedType" => Self::NotImplementedType,
|
||||||
"Field" => Self::Field,
|
"Field" => Self::Field,
|
||||||
|
"KW_ONLY" => Self::KwOnly,
|
||||||
"NamedTupleFallback" => Self::NamedTupleFallback,
|
"NamedTupleFallback" => Self::NamedTupleFallback,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
@ -2874,6 +2898,7 @@ impl<'db> KnownClass {
|
||||||
| Self::AsyncGeneratorType
|
| Self::AsyncGeneratorType
|
||||||
| Self::WrapperDescriptorType
|
| Self::WrapperDescriptorType
|
||||||
| Self::Field
|
| Self::Field
|
||||||
|
| Self::KwOnly
|
||||||
| Self::NamedTupleFallback => module == self.canonical_module(db),
|
| Self::NamedTupleFallback => module == self.canonical_module(db),
|
||||||
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
|
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
|
||||||
Self::SpecialForm
|
Self::SpecialForm
|
||||||
|
@ -3079,6 +3104,7 @@ mod tests {
|
||||||
KnownClass::UnionType => PythonVersion::PY310,
|
KnownClass::UnionType => PythonVersion::PY310,
|
||||||
KnownClass::BaseExceptionGroup | KnownClass::ExceptionGroup => PythonVersion::PY311,
|
KnownClass::BaseExceptionGroup | KnownClass::ExceptionGroup => PythonVersion::PY311,
|
||||||
KnownClass::GenericAlias => PythonVersion::PY39,
|
KnownClass::GenericAlias => PythonVersion::PY39,
|
||||||
|
KnownClass::KwOnly => PythonVersion::PY310,
|
||||||
_ => PythonVersion::PY37,
|
_ => PythonVersion::PY37,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue