Skip to content
Closed
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 @@ -461,7 +461,7 @@ The [`typing.dataclass_transform`] specification also allows classes (such as `d
to be listed in `field_specifiers`, but it is currently unclear how this should work, and other type
checkers do not seem to support this either.

### Basic example
### For function-based transformers

```py
from typing_extensions import dataclass_transform, Any
Expand All @@ -478,11 +478,36 @@ class Person:
name: str = fancy_field()
age: int | None = fancy_field(kw_only=True)

# TODO: Should be `(self: Person, name: str, *, age: int | None) -> None`
reveal_type(Person.__init__) # revealed: (self: Person, id: int = Any, name: str = Any, age: int | None = Any) -> None
reveal_type(Person.__init__) # revealed: (self: Person, name: str = Unknown, *, age: int | None = Unknown) -> None

alice = Person("Alice", age=30)

reveal_type(alice.id) # revealed: int
reveal_type(alice.name) # revealed: str
reveal_type(alice.age) # revealed: int | None
```

### For metaclass-based transformers

```py
from typing_extensions import dataclass_transform, Any

def fancy_field(*, init: bool = True, kw_only: bool = False) -> Any: ...
@dataclass_transform(field_specifiers=(fancy_field,))
class FancyMeta(type):
def __new__(cls, name, bases, namespace):
...
return super().__new__(cls, name, bases, namespace)

class FancyBase(metaclass=FancyMeta): ...

class Person(FancyBase):
id: int = fancy_field(init=False)
name: str = fancy_field()
age: int | None = fancy_field(kw_only=True)

reveal_type(Person.__init__) # revealed: (self: Person, name: str = Unknown, *, age: int | None = Unknown) -> None

# TODO: No error here
# error: [invalid-argument-type]
alice = Person("Alice", age=30)

reveal_type(alice.id) # revealed: int
Expand Down
83 changes: 56 additions & 27 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub(crate) use self::signatures::{CallableSignature, Parameter, Parameters, Sign
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
use crate::module_name::ModuleName;
use crate::module_resolver::{KnownModule, resolve_module};
use crate::place::{Boundness, Place, PlaceAndQualifiers, imported_symbol};
use crate::place::{Boundness, Place, PlaceAndQualifiers, imported_symbol, known_module_symbol};
use crate::semantic_index::definition::{Definition, DefinitionKind};
use crate::semantic_index::place::ScopedPlaceId;
use crate::semantic_index::scope::ScopeId;
Expand All @@ -50,7 +50,8 @@ pub use crate::types::display::DisplaySettings;
use crate::types::display::TupleSpecialization;
use crate::types::enums::{enum_metadata, is_single_member_enum};
use crate::types::function::{
DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction,
DataclassTransformerFlags, DataclassTransformerParams, FunctionSpans, FunctionType,
KnownFunction,
};
use crate::types::generics::{
GenericContext, InferableTypeVars, PartialSpecialization, Specialization, bind_typevar,
Expand Down Expand Up @@ -618,12 +619,12 @@ impl<'db> PropertyInstanceType<'db> {
}

bitflags! {
/// Used for the return type of `dataclass(…)` calls. Keeps track of the arguments
/// that were passed in. For the precise meaning of the fields, see [1].
/// Used to store metadata about a dataclass or dataclass-like class.
/// For the precise meaning of the fields, see [1].
///
/// [1]: https://docs.python.org/3/library/dataclasses.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DataclassParams: u16 {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct DataclassFlags: u16 {
const INIT = 0b0000_0000_0001;
const REPR = 0b0000_0000_0010;
const EQ = 0b0000_0000_0100;
Expand All @@ -634,51 +635,79 @@ bitflags! {
const KW_ONLY = 0b0000_1000_0000;
const SLOTS = 0b0001_0000_0000;
const WEAKREF_SLOT = 0b0010_0000_0000;
// This is not an actual argument from `dataclass(...)` but a flag signaling that no
// `field_specifiers` was specified for the `dataclass_transform`, see [1].
// [1]: https://typing.python.org/en/latest/spec/dataclasses.html#dataclass-transform-parameters
const NO_FIELD_SPECIFIERS = 0b0100_0000_0000;
}
}

impl get_size2::GetSize for DataclassParams {}
impl get_size2::GetSize for DataclassFlags {}

impl Default for DataclassParams {
impl Default for DataclassFlags {
fn default() -> Self {
Self::INIT | Self::REPR | Self::EQ | Self::MATCH_ARGS
}
}

impl From<DataclassTransformerParams> for DataclassParams {
fn from(params: DataclassTransformerParams) -> Self {
impl From<DataclassTransformerFlags> for DataclassFlags {
fn from(params: DataclassTransformerFlags) -> Self {
let mut result = Self::default();

result.set(
Self::EQ,
params.contains(DataclassTransformerParams::EQ_DEFAULT),
params.contains(DataclassTransformerFlags::EQ_DEFAULT),
);
result.set(
Self::ORDER,
params.contains(DataclassTransformerParams::ORDER_DEFAULT),
params.contains(DataclassTransformerFlags::ORDER_DEFAULT),
);
result.set(
Self::KW_ONLY,
params.contains(DataclassTransformerParams::KW_ONLY_DEFAULT),
params.contains(DataclassTransformerFlags::KW_ONLY_DEFAULT),
);
result.set(
Self::FROZEN,
params.contains(DataclassTransformerParams::FROZEN_DEFAULT),
);

result.set(
Self::NO_FIELD_SPECIFIERS,
!params.contains(DataclassTransformerParams::FIELD_SPECIFIERS),
params.contains(DataclassTransformerFlags::FROZEN_DEFAULT),
);

result
}
}

/// Metadata for a dataclass. Stored inside a `Type::DataclassDecorator(…)`
/// instance that we use as the return type of a `dataclasses.dataclass` and
/// dataclass-transformer decorator calls.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct DataclassParams<'db> {
flags: DataclassFlags,

#[returns(deref)]
field_specifiers: Box<[Type<'db>]>,
}

impl get_size2::GetSize for DataclassParams<'_> {}

impl<'db> DataclassParams<'db> {
fn default_params(db: &'db dyn Db) -> Self {
Self::from_flags(db, DataclassFlags::default())
}

fn from_flags(db: &'db dyn Db, flags: DataclassFlags) -> Self {
let dataclasses_field = known_module_symbol(db, KnownModule::Dataclasses, "field")
.place
.ignore_possibly_unbound()
.unwrap_or_else(|| Type::none(db));

Self::new(db, flags, vec![dataclasses_field].into_boxed_slice())
}

fn from_transformer_params(db: &'db dyn Db, params: DataclassTransformerParams<'db>) -> Self {
Self::new(
db,
DataclassFlags::from(params.flags(db)),
params.field_specifiers(db),
)
}
}

/// Representation of a type: a set of possible values at runtime.
///
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
Expand Down Expand Up @@ -719,9 +748,9 @@ pub enum Type<'db> {
/// A special callable that is returned by a `dataclass(…)` call. It is usually
/// used as a decorator. Note that this is only used as a return type for actual
/// `dataclass` calls, not for the argumentless `@dataclass` decorator.
DataclassDecorator(DataclassParams),
DataclassDecorator(DataclassParams<'db>),
/// A special callable that is returned by a `dataclass_transform(…)` call.
DataclassTransformer(DataclassTransformerParams),
DataclassTransformer(DataclassTransformerParams<'db>),
/// The type of an arbitrary callable object with a certain specified signature.
Callable(CallableType<'db>),
/// A specific module object
Expand Down Expand Up @@ -5440,7 +5469,7 @@ impl<'db> Type<'db> {
) -> Result<Bindings<'db>, CallError<'db>> {
self.bindings(db)
.match_parameters(db, argument_types)
.check_types(db, argument_types, &TypeContext::default())
.check_types(db, argument_types, &TypeContext::default(), &[])
}

/// Look up a dunder method on the meta-type of `self` and call it.
Expand Down Expand Up @@ -5492,7 +5521,7 @@ impl<'db> Type<'db> {
let bindings = dunder_callable
.bindings(db)
.match_parameters(db, argument_types)
.check_types(db, argument_types, &tcx)?;
.check_types(db, argument_types, &tcx, &[])?;
if boundness == Boundness::PossiblyUnbound {
return Err(CallDunderError::PossiblyUnbound(Box::new(bindings)));
}
Expand Down
Loading
Loading