diff --git a/crates/ty_python_semantic/resources/mdtest/dataclasses/fields.md b/crates/ty_python_semantic/resources/mdtest/dataclasses/fields.md index d1ac6dc0be..7b6a4369cc 100644 --- a/crates/ty_python_semantic/resources/mdtest/dataclasses/fields.md +++ b/crates/ty_python_semantic/resources/mdtest/dataclasses/fields.md @@ -11,7 +11,7 @@ class Member: role: str = field(default="user") tag: str | None = field(default=None, init=False) -# revealed: (self: Member, name: str, role: str = Literal["user"]) -> None +# revealed: (self: Member, name: str, role: str = str) -> None reveal_type(Member.__init__) alice = Member(name="Alice", role="admin") @@ -37,7 +37,7 @@ class Data: content: list[int] = field(default_factory=list) timestamp: datetime = field(default_factory=datetime.now, init=False) -# revealed: (self: Data, content: list[int] = list[Unknown]) -> None +# revealed: (self: Data, content: list[int] = list[int]) -> None reveal_type(Data.__init__) data = Data([1, 2, 3]) @@ -63,7 +63,8 @@ class Person: age: int | None = field(default=None, kw_only=True) role: str = field(default="user", kw_only=True) -# revealed: (self: Person, name: str, *, age: int | None = None, role: str = Literal["user"]) -> None +# TODO: this would ideally show a default value of `None` for `age` +# revealed: (self: Person, name: str, *, age: int | None = int | None, role: str = str) -> None reveal_type(Person.__init__) alice = Person(role="admin", name="Alice") @@ -82,7 +83,8 @@ def get_default() -> str: reveal_type(field(default=1)) # revealed: dataclasses.Field[Literal[1]] reveal_type(field(default=None)) # revealed: dataclasses.Field[None] -reveal_type(field(default_factory=get_default)) # revealed: dataclasses.Field[str] +# TODO: this could ideally be `dataclasses.Field[str]` with a better generics solver +reveal_type(field(default_factory=get_default)) # revealed: dataclasses.Field[Unknown] ``` ## dataclass_transform field_specifiers diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index abaa77cb49..bcac0c636c 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -977,13 +977,16 @@ impl<'db> Bindings<'db> { let kw_only = overload.parameter_type_by_name("kw_only").unwrap_or(None); - let default_ty = match (default, default_factory) { - (Some(default_ty), _) => Some(default_ty), - (_, Some(default_factory_ty)) => default_factory_ty - .try_call(db, &CallArguments::none()) - .ok() - .map(|binding| binding.return_type(db)), - _ => None, + // `dataclasses.field` and field-specifier functions of commonly used + // libraries like `pydantic`, `attrs`, and `SQLAlchemy` all return + // the default type for the field (or `Any`) instead of an actual `Field` + // instance, even if this is not what happens at runtime (see also below). + // We still make use of this fact and pretend that all field specifiers + // return the type of the default value: + let default_ty = if default.is_some() || default_factory.is_some() { + Some(overload.return_ty) + } else { + None }; let init = init