mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-19 03:48:29 +00:00
[ty] Add synthetic members to completions on dataclasses (#21446)
## Summary Add synthetic members to completions on dataclasses and dataclass instances. Also, while we're at it, add support for `__weakref__` and `__match_args__`. closes https://github.com/astral-sh/ty/issues/1542 ## Test Plan New Markdown tests
This commit is contained in:
parent
66e9d57797
commit
696d7a5d68
5 changed files with 342 additions and 39 deletions
|
|
@ -461,7 +461,51 @@ del frozen.x # TODO this should emit an [invalid-assignment]
|
||||||
|
|
||||||
### `match_args`
|
### `match_args`
|
||||||
|
|
||||||
To do
|
If `match_args` is set to `True` (the default), the `__match_args__` attribute is a tuple created
|
||||||
|
from the list of non keyword-only parameters to the synthesized `__init__` method (even if
|
||||||
|
`__init__` is not actually generated).
|
||||||
|
|
||||||
|
```py
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class WithMatchArgs:
|
||||||
|
normal_a: str
|
||||||
|
normal_b: int
|
||||||
|
kw_only: int = field(kw_only=True)
|
||||||
|
|
||||||
|
reveal_type(WithMatchArgs.__match_args__) # revealed: tuple[Literal["normal_a"], Literal["normal_b"]]
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class KwOnlyDefaultMatchArgs:
|
||||||
|
normal_a: str = field(kw_only=False)
|
||||||
|
normal_b: int = field(kw_only=False)
|
||||||
|
kw_only: int
|
||||||
|
|
||||||
|
reveal_type(KwOnlyDefaultMatchArgs.__match_args__) # revealed: tuple[Literal["normal_a"], Literal["normal_b"]]
|
||||||
|
|
||||||
|
@dataclass(match_args=True)
|
||||||
|
class ExplicitMatchArgs:
|
||||||
|
normal: str
|
||||||
|
|
||||||
|
reveal_type(ExplicitMatchArgs.__match_args__) # revealed: tuple[Literal["normal"]]
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Empty: ...
|
||||||
|
|
||||||
|
reveal_type(Empty.__match_args__) # revealed: tuple[()]
|
||||||
|
```
|
||||||
|
|
||||||
|
When `match_args` is explicitly set to `False`, the `__match_args__` attribute is not available:
|
||||||
|
|
||||||
|
```py
|
||||||
|
@dataclass(match_args=False)
|
||||||
|
class NoMatchArgs:
|
||||||
|
x: int
|
||||||
|
y: str
|
||||||
|
|
||||||
|
NoMatchArgs.__match_args__ # error: [unresolved-attribute]
|
||||||
|
```
|
||||||
|
|
||||||
### `kw_only`
|
### `kw_only`
|
||||||
|
|
||||||
|
|
@ -623,7 +667,18 @@ reveal_type(B.__slots__) # revealed: tuple[Literal["x"], Literal["y"]]
|
||||||
|
|
||||||
### `weakref_slot`
|
### `weakref_slot`
|
||||||
|
|
||||||
To do
|
When a dataclass is defined with `weakref_slot=True`, the `__weakref__` attribute is generated. For
|
||||||
|
now, we do not attempt to infer a more precise type for it.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass(slots=True, weakref_slot=True)
|
||||||
|
class C:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
reveal_type(C.__weakref__) # revealed: Any | None
|
||||||
|
```
|
||||||
|
|
||||||
## `Final` fields
|
## `Final` fields
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -548,13 +548,20 @@ static_assert(not has_member(c, "dynamic_attr"))
|
||||||
|
|
||||||
### Dataclasses
|
### Dataclasses
|
||||||
|
|
||||||
So far, we do not include synthetic members of dataclasses.
|
#### Basic
|
||||||
|
|
||||||
|
For dataclasses, we make sure to include all synthesized members:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.9"
|
||||||
|
```
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from ty_extensions import has_member, static_assert
|
from ty_extensions import has_member, static_assert
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
@dataclass(order=True)
|
@dataclass
|
||||||
class Person:
|
class Person:
|
||||||
age: int
|
age: int
|
||||||
name: str
|
name: str
|
||||||
|
|
@ -562,13 +569,177 @@ class Person:
|
||||||
static_assert(has_member(Person, "name"))
|
static_assert(has_member(Person, "name"))
|
||||||
static_assert(has_member(Person, "age"))
|
static_assert(has_member(Person, "age"))
|
||||||
|
|
||||||
|
static_assert(has_member(Person, "__dataclass_fields__"))
|
||||||
|
static_assert(has_member(Person, "__dataclass_params__"))
|
||||||
|
|
||||||
# These are always available, since they are also defined on `object`:
|
# These are always available, since they are also defined on `object`:
|
||||||
static_assert(has_member(Person, "__init__"))
|
static_assert(has_member(Person, "__init__"))
|
||||||
static_assert(has_member(Person, "__repr__"))
|
static_assert(has_member(Person, "__repr__"))
|
||||||
static_assert(has_member(Person, "__eq__"))
|
static_assert(has_member(Person, "__eq__"))
|
||||||
|
static_assert(has_member(Person, "__ne__"))
|
||||||
|
|
||||||
# TODO: this should ideally be available:
|
# There are not available, unless `order=True` is set:
|
||||||
static_assert(has_member(Person, "__lt__")) # error: [static-assert-error]
|
static_assert(not has_member(Person, "__lt__"))
|
||||||
|
static_assert(not has_member(Person, "__le__"))
|
||||||
|
static_assert(not has_member(Person, "__gt__"))
|
||||||
|
static_assert(not has_member(Person, "__ge__"))
|
||||||
|
|
||||||
|
# These are not available, unless `slots=True`, `weakref_slot=True` are set:
|
||||||
|
static_assert(not has_member(Person, "__slots__"))
|
||||||
|
static_assert(not has_member(Person, "__weakref__"))
|
||||||
|
|
||||||
|
# Not available before Python 3.13:
|
||||||
|
static_assert(not has_member(Person, "__replace__"))
|
||||||
|
```
|
||||||
|
|
||||||
|
The same behavior applies to instances of dataclasses:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def _(person: Person):
|
||||||
|
static_assert(has_member(person, "name"))
|
||||||
|
static_assert(has_member(person, "age"))
|
||||||
|
|
||||||
|
static_assert(has_member(person, "__dataclass_fields__"))
|
||||||
|
static_assert(has_member(person, "__dataclass_params__"))
|
||||||
|
|
||||||
|
static_assert(has_member(person, "__init__"))
|
||||||
|
static_assert(has_member(person, "__repr__"))
|
||||||
|
static_assert(has_member(person, "__eq__"))
|
||||||
|
static_assert(has_member(person, "__ne__"))
|
||||||
|
|
||||||
|
static_assert(not has_member(person, "__lt__"))
|
||||||
|
static_assert(not has_member(person, "__le__"))
|
||||||
|
static_assert(not has_member(person, "__gt__"))
|
||||||
|
static_assert(not has_member(person, "__ge__"))
|
||||||
|
|
||||||
|
static_assert(not has_member(person, "__slots__"))
|
||||||
|
|
||||||
|
static_assert(not has_member(person, "__replace__"))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `__init__`, `__repr__` and `__eq__`
|
||||||
|
|
||||||
|
`__init__`, `__repr__` and `__eq__` are always available (via `object`), even when `init=False`,
|
||||||
|
`repr=False` and `eq=False` are set:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from ty_extensions import has_member, static_assert
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass(init=False, repr=False, eq=False)
|
||||||
|
class C:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
static_assert(has_member(C, "__init__"))
|
||||||
|
static_assert(has_member(C, "__repr__"))
|
||||||
|
static_assert(has_member(C, "__eq__"))
|
||||||
|
static_assert(has_member(C, "__ne__"))
|
||||||
|
static_assert(has_member(C(), "__init__"))
|
||||||
|
static_assert(has_member(C(), "__repr__"))
|
||||||
|
static_assert(has_member(C(), "__eq__"))
|
||||||
|
static_assert(has_member(C(), "__ne__"))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `order=True`
|
||||||
|
|
||||||
|
When `order=True` is set, comparison dunder methods become available:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from ty_extensions import has_member, static_assert
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass(order=True)
|
||||||
|
class C:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
static_assert(has_member(C, "__lt__"))
|
||||||
|
static_assert(has_member(C, "__le__"))
|
||||||
|
static_assert(has_member(C, "__gt__"))
|
||||||
|
static_assert(has_member(C, "__ge__"))
|
||||||
|
|
||||||
|
def _(c: C):
|
||||||
|
static_assert(has_member(c, "__lt__"))
|
||||||
|
static_assert(has_member(c, "__le__"))
|
||||||
|
static_assert(has_member(c, "__gt__"))
|
||||||
|
static_assert(has_member(c, "__ge__"))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `slots=True`
|
||||||
|
|
||||||
|
When `slots=True`, the corresponding dunder attribute becomes available:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from ty_extensions import has_member, static_assert
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass(slots=True)
|
||||||
|
class C:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
static_assert(has_member(C, "__slots__"))
|
||||||
|
static_assert(has_member(C(1), "__slots__"))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `weakref_slot=True`
|
||||||
|
|
||||||
|
When `weakref_slot=True`, the corresponding dunder attribute becomes available:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from ty_extensions import has_member, static_assert
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass(slots=True, weakref_slot=True)
|
||||||
|
class C:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
static_assert(has_member(C, "__weakref__"))
|
||||||
|
static_assert(has_member(C(1), "__weakref__"))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `__replace__` in Python 3.13+
|
||||||
|
|
||||||
|
Since Python 3.13, dataclasses have a `__replace__` method:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.13"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from ty_extensions import has_member, static_assert
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class C:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
static_assert(has_member(C, "__replace__"))
|
||||||
|
|
||||||
|
def _(c: C):
|
||||||
|
static_assert(has_member(c, "__replace__"))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `__match_args__`
|
||||||
|
|
||||||
|
Since Python 3.10, dataclasses have a `__match_args__` attribute:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.10"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from ty_extensions import has_member, static_assert
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class C:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
static_assert(has_member(C, "__match_args__"))
|
||||||
|
|
||||||
|
def _(c: C):
|
||||||
|
static_assert(has_member(c, "__match_args__"))
|
||||||
```
|
```
|
||||||
|
|
||||||
### Attributes not available at runtime
|
### Attributes not available at runtime
|
||||||
|
|
|
||||||
|
|
@ -2122,18 +2122,25 @@ impl<'db> ClassLiteral<'db> {
|
||||||
specialization: Option<Specialization<'db>>,
|
specialization: Option<Specialization<'db>>,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> Member<'db> {
|
) -> Member<'db> {
|
||||||
if name == "__dataclass_fields__" && self.dataclass_params(db).is_some() {
|
if self.dataclass_params(db).is_some() {
|
||||||
// Make this class look like a subclass of the `DataClassInstance` protocol
|
if name == "__dataclass_fields__" {
|
||||||
return Member {
|
// Make this class look like a subclass of the `DataClassInstance` protocol
|
||||||
inner: Place::declared(KnownClass::Dict.to_specialized_instance(
|
return Member {
|
||||||
db,
|
inner: Place::declared(KnownClass::Dict.to_specialized_instance(
|
||||||
[
|
db,
|
||||||
KnownClass::Str.to_instance(db),
|
[
|
||||||
KnownClass::Field.to_specialized_instance(db, [Type::any()]),
|
KnownClass::Str.to_instance(db),
|
||||||
],
|
KnownClass::Field.to_specialized_instance(db, [Type::any()]),
|
||||||
))
|
],
|
||||||
.with_qualifiers(TypeQualifiers::CLASS_VAR),
|
))
|
||||||
};
|
.with_qualifiers(TypeQualifiers::CLASS_VAR),
|
||||||
|
};
|
||||||
|
} else if name == "__dataclass_params__" {
|
||||||
|
// There is no typeshed class for this. For now, we model it as `Any`.
|
||||||
|
return Member {
|
||||||
|
inner: Place::declared(Type::any()).with_qualifiers(TypeQualifiers::CLASS_VAR),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if CodeGeneratorKind::NamedTuple.matches(db, self, specialization) {
|
if CodeGeneratorKind::NamedTuple.matches(db, self, specialization) {
|
||||||
|
|
@ -2368,6 +2375,39 @@ impl<'db> ClassLiteral<'db> {
|
||||||
|
|
||||||
Some(CallableType::function_like(db, signature))
|
Some(CallableType::function_like(db, signature))
|
||||||
}
|
}
|
||||||
|
(CodeGeneratorKind::DataclassLike(_), "__match_args__")
|
||||||
|
if Program::get(db).python_version(db) >= PythonVersion::PY310 =>
|
||||||
|
{
|
||||||
|
if !has_dataclass_param(DataclassFlags::MATCH_ARGS) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let kw_only_default = has_dataclass_param(DataclassFlags::KW_ONLY);
|
||||||
|
|
||||||
|
let fields = self.fields(db, specialization, field_policy);
|
||||||
|
let match_args = fields
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, field)| {
|
||||||
|
if let FieldKind::Dataclass { init, kw_only, .. } = &field.kind {
|
||||||
|
*init && !kw_only.unwrap_or(kw_only_default)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|(name, _)| Type::string_literal(db, name));
|
||||||
|
Some(Type::heterogeneous_tuple(db, match_args))
|
||||||
|
}
|
||||||
|
(CodeGeneratorKind::DataclassLike(_), "__weakref__") => {
|
||||||
|
if !has_dataclass_param(DataclassFlags::WEAKREF_SLOT)
|
||||||
|
|| !has_dataclass_param(DataclassFlags::SLOTS)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This could probably be `weakref | None`, but it does not seem important enough to
|
||||||
|
// model it precisely.
|
||||||
|
Some(UnionType::from_elements(db, [Type::any(), Type::none(db)]))
|
||||||
|
}
|
||||||
(CodeGeneratorKind::NamedTuple, name) if name != "__init__" => {
|
(CodeGeneratorKind::NamedTuple, name) if name != "__init__" => {
|
||||||
KnownClass::NamedTupleFallback
|
KnownClass::NamedTupleFallback
|
||||||
.to_class_literal(db)
|
.to_class_literal(db)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use crate::semantic_index::{
|
||||||
attribute_scopes, global_scope, place_table, semantic_index, use_def_map,
|
attribute_scopes, global_scope, place_table, semantic_index, use_def_map,
|
||||||
};
|
};
|
||||||
use crate::types::call::{CallArguments, MatchedArgument};
|
use crate::types::call::{CallArguments, MatchedArgument};
|
||||||
|
use crate::types::generics::Specialization;
|
||||||
use crate::types::signatures::Signature;
|
use crate::types::signatures::Signature;
|
||||||
use crate::types::{CallDunderError, UnionType};
|
use crate::types::{CallDunderError, UnionType};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
|
|
@ -28,6 +29,23 @@ use rustc_hash::FxHashSet;
|
||||||
pub use resolve_definition::{ImportAliasResolution, ResolvedDefinition, map_stub_definition};
|
pub use resolve_definition::{ImportAliasResolution, ResolvedDefinition, map_stub_definition};
|
||||||
use resolve_definition::{find_symbol_in_scope, resolve_definition};
|
use resolve_definition::{find_symbol_in_scope, resolve_definition};
|
||||||
|
|
||||||
|
// `__init__`, `__repr__`, `__eq__`, `__ne__` and `__hash__` are always included via `object`,
|
||||||
|
// so we don't need to list them here.
|
||||||
|
const SYNTHETIC_DATACLASS_ATTRIBUTES: &[&str] = &[
|
||||||
|
"__lt__",
|
||||||
|
"__le__",
|
||||||
|
"__gt__",
|
||||||
|
"__ge__",
|
||||||
|
"__replace__",
|
||||||
|
"__setattr__",
|
||||||
|
"__delattr__",
|
||||||
|
"__slots__",
|
||||||
|
"__weakref__",
|
||||||
|
"__match_args__",
|
||||||
|
"__dataclass_fields__",
|
||||||
|
"__dataclass_params__",
|
||||||
|
];
|
||||||
|
|
||||||
pub(crate) fn all_declarations_and_bindings<'db>(
|
pub(crate) fn all_declarations_and_bindings<'db>(
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
scope_id: ScopeId<'db>,
|
scope_id: ScopeId<'db>,
|
||||||
|
|
@ -119,13 +137,9 @@ impl<'db> AllMembers<'db> {
|
||||||
),
|
),
|
||||||
|
|
||||||
Type::NominalInstance(instance) => {
|
Type::NominalInstance(instance) => {
|
||||||
let class_literal = instance.class_literal(db);
|
let (class_literal, specialization) = instance.class(db).class_literal(db);
|
||||||
self.extend_with_instance_members(db, ty, class_literal);
|
self.extend_with_instance_members(db, ty, class_literal);
|
||||||
|
self.extend_with_synthetic_members(db, ty, class_literal, specialization);
|
||||||
// If this is a NamedTuple instance, include members from NamedTupleFallback
|
|
||||||
if CodeGeneratorKind::NamedTuple.matches(db, class_literal, None) {
|
|
||||||
self.extend_with_type(db, KnownClass::NamedTupleFallback.to_class_literal(db));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::NewTypeInstance(newtype) => {
|
Type::NewTypeInstance(newtype) => {
|
||||||
|
|
@ -146,10 +160,7 @@ impl<'db> AllMembers<'db> {
|
||||||
|
|
||||||
Type::ClassLiteral(class_literal) => {
|
Type::ClassLiteral(class_literal) => {
|
||||||
self.extend_with_class_members(db, ty, class_literal);
|
self.extend_with_class_members(db, ty, class_literal);
|
||||||
|
self.extend_with_synthetic_members(db, ty, class_literal, None);
|
||||||
if CodeGeneratorKind::NamedTuple.matches(db, class_literal, None) {
|
|
||||||
self.extend_with_type(db, KnownClass::NamedTupleFallback.to_class_literal(db));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Type::ClassLiteral(meta_class_literal) = ty.to_meta_type(db) {
|
if let Type::ClassLiteral(meta_class_literal) = ty.to_meta_type(db) {
|
||||||
self.extend_with_class_members(db, ty, meta_class_literal);
|
self.extend_with_class_members(db, ty, meta_class_literal);
|
||||||
|
|
@ -158,23 +169,15 @@ impl<'db> AllMembers<'db> {
|
||||||
|
|
||||||
Type::GenericAlias(generic_alias) => {
|
Type::GenericAlias(generic_alias) => {
|
||||||
let class_literal = generic_alias.origin(db);
|
let class_literal = generic_alias.origin(db);
|
||||||
if CodeGeneratorKind::NamedTuple.matches(db, class_literal, None) {
|
|
||||||
self.extend_with_type(db, KnownClass::NamedTupleFallback.to_class_literal(db));
|
|
||||||
}
|
|
||||||
self.extend_with_class_members(db, ty, class_literal);
|
self.extend_with_class_members(db, ty, class_literal);
|
||||||
|
self.extend_with_synthetic_members(db, ty, class_literal, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::SubclassOf(subclass_of_type) => {
|
Type::SubclassOf(subclass_of_type) => {
|
||||||
if let Some(class_type) = subclass_of_type.subclass_of().into_class() {
|
if let Some(class_type) = subclass_of_type.subclass_of().into_class() {
|
||||||
let class_literal = class_type.class_literal(db).0;
|
let (class_literal, specialization) = class_type.class_literal(db);
|
||||||
self.extend_with_class_members(db, ty, class_literal);
|
self.extend_with_class_members(db, ty, class_literal);
|
||||||
|
self.extend_with_synthetic_members(db, ty, class_literal, specialization);
|
||||||
if CodeGeneratorKind::NamedTuple.matches(db, class_literal, None) {
|
|
||||||
self.extend_with_type(
|
|
||||||
db,
|
|
||||||
KnownClass::NamedTupleFallback.to_class_literal(db),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -414,6 +417,36 @@ impl<'db> AllMembers<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extend_with_synthetic_members(
|
||||||
|
&mut self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
ty: Type<'db>,
|
||||||
|
class_literal: ClassLiteral<'db>,
|
||||||
|
specialization: Option<Specialization<'db>>,
|
||||||
|
) {
|
||||||
|
match CodeGeneratorKind::from_class(db, class_literal, specialization) {
|
||||||
|
Some(CodeGeneratorKind::NamedTuple) => {
|
||||||
|
if ty.is_nominal_instance() {
|
||||||
|
self.extend_with_type(db, KnownClass::NamedTupleFallback.to_instance(db));
|
||||||
|
} else {
|
||||||
|
self.extend_with_type(db, KnownClass::NamedTupleFallback.to_class_literal(db));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(CodeGeneratorKind::TypedDict) => {}
|
||||||
|
Some(CodeGeneratorKind::DataclassLike(_)) => {
|
||||||
|
for attr in SYNTHETIC_DATACLASS_ATTRIBUTES {
|
||||||
|
if let Place::Defined(synthetic_member, _, _) = ty.member(db, attr).place {
|
||||||
|
self.members.insert(Member {
|
||||||
|
name: Name::from(*attr),
|
||||||
|
ty: synthetic_member,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A member of a type with an optional definition.
|
/// A member of a type with an optional definition.
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,10 @@ impl<'db> Type<'db> {
|
||||||
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::ExactTuple(tuple)))
|
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::ExactTuple(tuple)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn is_nominal_instance(self) -> bool {
|
||||||
|
matches!(self, Type::NominalInstance(_))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) const fn as_nominal_instance(self) -> Option<NominalInstanceType<'db>> {
|
pub(crate) const fn as_nominal_instance(self) -> Option<NominalInstanceType<'db>> {
|
||||||
match self {
|
match self {
|
||||||
Type::NominalInstance(instance_type) => Some(instance_type),
|
Type::NominalInstance(instance_type) => Some(instance_type),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue