Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -218,60 +218,99 @@ reveal_type(TestWithBase(1) < TestWithBase(2)) # revealed: bool

### `kw_only_default`

When provided, sets the default value for the `kw_only` parameter of `field()`.
When provided, sets the default value for the `kw_only` parameter of dataclass fields:

```py
from typing import dataclass_transform
from dataclasses import field

@dataclass_transform(kw_only_default=True)
def create_model(*, kw_only: bool = True): ...

@create_model()
class A:
class Model1:
name: str

a = A(name="Harry")
reveal_type(Model1.__init__) # revealed: (self: Model1, *, name: str) -> None

Model1(name="Harry")
# error: [missing-argument]
# error: [too-many-positional-arguments]
a = A("Harry")
Model1("Harry")
```

This can be overridden by setting `kw_only=False` when applying the decorator:

```py
@create_model(kw_only=False)
class CustomerModel:
id: int
class Model1KwOnlyFalse:
name: str

c = CustomerModel(1, "Harry")
reveal_type(Model1KwOnlyFalse.__init__) # revealed: (self: Model1KwOnlyFalse, name: str) -> None

Model1KwOnlyFalse(name="Harry")
Model1KwOnlyFalse("Harry")
```

This also works for metaclass-based transformers:

```py
@dataclass_transform(kw_only_default=True)
class ModelMeta(type): ...
class ModelMeta(type):
def __new__(
cls,
name,
bases,
namespace,
*,
kw_only: bool = True,
): ...

class ModelBase(metaclass=ModelMeta): ...

class TestMeta(ModelBase):
class Model2(ModelBase):
name: str

reveal_type(TestMeta.__init__) # revealed: (self: TestMeta, *, name: str) -> None
reveal_type(Model2.__init__) # revealed: (self: Model2, *, name: str) -> None

Model2(name="Harry")
# error: [missing-argument]
# error: [too-many-positional-arguments]
Model2("Harry")

class Model2KwOnlyFalse(ModelBase, kw_only=False):
name: str

reveal_type(Model2KwOnlyFalse.__init__) # revealed: (self: Model2KwOnlyFalse, name: str) -> None

Model2KwOnlyFalse(name="Harry")
Model2KwOnlyFalse("Harry")
```

And for base-class-based transformers:

```py
@dataclass_transform(kw_only_default=True)
class ModelBase: ...
class ModelBase:
def __init_subclass__(cls, kw_only: bool = False) -> None:
pass

class Model3(ModelBase):
name: str

reveal_type(Model3.__init__) # revealed: (self: Model3, *, name: str) -> None

class TestBase(ModelBase):
Model3(name="Harry")
# error: [missing-argument]
# error: [too-many-positional-arguments]
Model3("Harry")

class Model3KwOnlyFalse(ModelBase, kw_only=False):
name: str

reveal_type(TestBase.__init__) # revealed: (self: TestBase, *, name: str) -> None
reveal_type(Model3KwOnlyFalse.__init__) # revealed: (self: Model3KwOnlyFalse, name: str) -> None

Model3KwOnlyFalse(name="Harry")
Model3KwOnlyFalse("Harry")
```

### `frozen_default`
Expand Down
17 changes: 3 additions & 14 deletions crates/ty_python_semantic/src/types/class/static_literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ use crate::{
diagnostic::INVALID_DATACLASS_OVERRIDE,
enums::{enum_metadata, is_enum_class_by_inheritance, try_unwrap_nonmember_value},
function::{
DataclassTransformerFlags, DataclassTransformerParams, KnownFunction,
is_implicit_classmethod, is_implicit_staticmethod,
DataclassTransformerParams, KnownFunction, is_implicit_classmethod,
is_implicit_staticmethod,
},
generics::Specialization,
infer::infer_unpack_types,
Expand Down Expand Up @@ -2291,18 +2291,7 @@ impl<'db> StaticClassLiteral<'db> {
..
} = field.kind
{
let class_kw_only_default = self
.dataclass_params(db)
.is_some_and(|params| params.flags(db).contains(DataclassFlags::KW_ONLY))
// TODO this next part should not be necessary, if we were properly
// initializing `dataclass_params` from the dataclass-transform params, for
// metaclass and base-class-based dataclass-transformers.
|| matches!(
field_policy,
CodeGeneratorKind::DataclassLike(Some(transformer_params))
if transformer_params.flags(db).contains(DataclassTransformerFlags::KW_ONLY_DEFAULT)
);
*kw = Some(class_kw_only_default);
*kw = Some(self.has_dataclass_param(db, field_policy, DataclassFlags::KW_ONLY));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the TODO was added (in d2aabea) before we added the self.has_dataclass_param() method (in 938c1c5) that handles merging the two sources of dataclass params for you? And the bug here was caused by the fact that we forgot to update this callsite to use the new method?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's exactly right.

}

attributes.insert(symbol.name().clone(), field);
Expand Down
Loading