Skip to content

Commit a80f813

Browse files
committed
[ty] support kw_only=True for dataclasses
astral-sh/ty#111
1 parent 6a2d358 commit a80f813

File tree

3 files changed

+50
-4
lines changed

3 files changed

+50
-4
lines changed

crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,45 @@ To do
465465

466466
### `kw_only`
467467

468-
To do
468+
An error is emitted if a dataclass is defined with `kw_only=True` and positional arguments are
469+
passed to the constructor.
470+
471+
```toml
472+
[environment]
473+
python-version = "3.10"
474+
```
475+
476+
```py
477+
from dataclasses import dataclass
478+
479+
@dataclass(kw_only=True)
480+
class A:
481+
x: int
482+
y: int
483+
484+
# error: [missing-argument] "No arguments provided for required parameters `x`, `y`"
485+
# error: [too-many-positional-arguments] "Too many positional arguments: expected 0, got 2"
486+
a = A(1, 2)
487+
a = A(x=1, y=2)
488+
```
489+
490+
### `kw_only` - Python < 3.10
491+
492+
For Python < 3.10, `kw_only` is not supported.
493+
494+
```toml
495+
[environment]
496+
python-version = "3.9"
497+
```
498+
499+
```py
500+
from dataclasses import dataclass
501+
502+
@dataclass(kw_only=True) # TODO: Emit a diagnostic here
503+
class A:
504+
x: int
505+
y: int
506+
```
469507

470508
### `slots`
471509

crates/ty_python_semantic/src/types/call/bind.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use ruff_db::parsed::parsed_module;
1212
use smallvec::{SmallVec, smallvec, smallvec_inline};
1313

1414
use super::{Argument, CallArguments, CallError, CallErrorKind, InferContext, Signature, Type};
15+
use crate::Program;
1516
use crate::db::Db;
1617
use crate::dunder_all::dunder_all_names;
1718
use crate::place::{Boundness, Place};
@@ -33,7 +34,7 @@ use crate::types::{
3334
WrapperDescriptorKind, enums, ide_support, todo_type,
3435
};
3536
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
36-
use ruff_python_ast as ast;
37+
use ruff_python_ast::{self as ast, PythonVersion};
3738

3839
/// Binding information for a possible union of callables. At a call site, the arguments must be
3940
/// compatible with _all_ of the types in the union for the call to be valid.
@@ -860,7 +861,11 @@ impl<'db> Bindings<'db> {
860861
params |= DataclassParams::MATCH_ARGS;
861862
}
862863
if to_bool(kw_only, false) {
863-
params |= DataclassParams::KW_ONLY;
864+
if Program::get(db).python_version(db) >= PythonVersion::PY310 {
865+
params |= DataclassParams::KW_ONLY;
866+
} else {
867+
// TODO: emit diagnostic
868+
}
864869
}
865870
if to_bool(slots, false) {
866871
params |= DataclassParams::SLOTS;

crates/ty_python_semantic/src/types/class.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1843,7 +1843,10 @@ impl<'db> ClassLiteral<'db> {
18431843
}
18441844
}
18451845

1846-
let mut parameter = if kw_only_field_seen || name == "__replace__" {
1846+
let mut parameter = if kw_only_field_seen
1847+
|| name == "__replace__"
1848+
|| has_dataclass_param(DataclassParams::KW_ONLY)
1849+
{
18471850
Parameter::keyword_only(field_name)
18481851
} else {
18491852
Parameter::positional_or_keyword(field_name)

0 commit comments

Comments
 (0)