use internal `TypedDictSchema` type for functional `TypedDict` constructor

This commit is contained in:
Ibraheem Ahmed 2025-10-07 15:35:33 -04:00
parent f2a25b0fd7
commit b753851379
10 changed files with 131 additions and 84 deletions

View File

@ -97,10 +97,18 @@ from typing import TypedDict
from typing_extensions import Required, NotRequired from typing_extensions import Required, NotRequired
Person = TypedDict("Person", {"name": Required[str], "age": int | None}) Person = TypedDict("Person", {"name": Required[str], "age": int | None})
reveal_type(Person) # revealed: typing.TypedDict reveal_type(Person) # revealed: typing.TypedDict
``` ```
The `TypedDict` schema must be passed directly as the second argument:
```py
fields = {"name": str}
# error: [invalid-argument-type] "Argument is incorrect: Expected `_TypedDictSchema`, found `dict[Unknown | str, Unknown | <class 'str'>]`"
Other = TypedDict("Other", fields)
```
New inhabitants can be created from dict literals. When accessing keys, the correct types should be New inhabitants can be created from dict literals. When accessing keys, the correct types should be
inferred based on the `TypedDict` definition: inferred based on the `TypedDict` definition:

View File

@ -66,7 +66,7 @@ use crate::types::mro::{Mro, MroError, MroIterator};
pub(crate) use crate::types::narrow::infer_narrowing_constraint; pub(crate) use crate::types::narrow::infer_narrowing_constraint;
use crate::types::signatures::{ParameterForm, walk_signature}; use crate::types::signatures::{ParameterForm, walk_signature};
use crate::types::tuple::TupleSpec; use crate::types::tuple::TupleSpec;
use crate::types::typed_dict::SynthesizedTypedDictType; use crate::types::typed_dict::{SynthesizedTypedDictType, TypedDictSchema};
pub(crate) use crate::types::typed_dict::{TypedDictParams, TypedDictType, walk_typed_dict_type}; pub(crate) use crate::types::typed_dict::{TypedDictParams, TypedDictType, walk_typed_dict_type};
use crate::types::variance::{TypeVarVariance, VarianceInferable}; use crate::types::variance::{TypeVarVariance, VarianceInferable};
use crate::types::visitor::any_over_type; use crate::types::visitor::any_over_type;
@ -1523,6 +1523,11 @@ impl<'db> Type<'db> {
.has_relation_to_impl(db, right, relation, visitor) .has_relation_to_impl(db, right, relation, visitor)
} }
(
Type::KnownInstance(KnownInstanceType::TypedDictSchema(_)),
Type::SpecialForm(SpecialFormType::TypedDictSchema),
) => ConstraintSet::from(true),
// Dynamic is only a subtype of `object` and only a supertype of `Never`; both were // Dynamic is only a subtype of `object` and only a supertype of `Never`; both were
// handled above. It's always assignable, though. // handled above. It's always assignable, though.
// //
@ -4767,10 +4772,9 @@ impl<'db> Type<'db> {
Parameter::positional_only(Some(Name::new_static("typename"))) Parameter::positional_only(Some(Name::new_static("typename")))
.with_annotated_type(KnownClass::Str.to_instance(db)), .with_annotated_type(KnownClass::Str.to_instance(db)),
Parameter::positional_only(Some(Name::new_static("fields"))) Parameter::positional_only(Some(Name::new_static("fields")))
// We infer this type as an anonymous `TypedDict` instance, such that the .with_annotated_type(Type::SpecialForm(
// complete `TypeDict` instance can be constructed from it after. Note that SpecialFormType::TypedDictSchema,
// `typing.TypedDict` is not otherwise allowed in type-form expressions. ))
.with_annotated_type(Type::SpecialForm(SpecialFormType::TypedDict))
.with_default_type(Type::any()), .with_default_type(Type::any()),
Parameter::keyword_only(Name::new_static("total")) Parameter::keyword_only(Name::new_static("total"))
.with_annotated_type(KnownClass::Bool.to_instance(db)) .with_annotated_type(KnownClass::Bool.to_instance(db))
@ -5678,6 +5682,12 @@ impl<'db> Type<'db> {
KnownInstanceType::TypedDictType(typed_dict) => { KnownInstanceType::TypedDictType(typed_dict) => {
Ok(Type::TypedDict(TypedDictType::Synthesized(*typed_dict))) Ok(Type::TypedDict(TypedDictType::Synthesized(*typed_dict)))
} }
KnownInstanceType::TypedDictSchema(_) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(
*self, scope_id
)],
fallback_type: Type::unknown(),
}),
KnownInstanceType::Deprecated(_) => Err(InvalidTypeExpressionError { KnownInstanceType::Deprecated(_) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Deprecated], invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Deprecated],
fallback_type: Type::unknown(), fallback_type: Type::unknown(),
@ -5768,6 +5778,12 @@ impl<'db> Type<'db> {
], ],
fallback_type: Type::unknown(), fallback_type: Type::unknown(),
}), }),
SpecialFormType::TypedDictSchema => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec_inline![
InvalidTypeExpression::InvalidType(*self, scope_id)
],
fallback_type: Type::unknown(),
}),
SpecialFormType::Literal SpecialFormType::Literal
| SpecialFormType::Union | SpecialFormType::Union
@ -6875,6 +6891,10 @@ pub enum KnownInstanceType<'db> {
/// A single instance of `typing.TypedDict`. /// A single instance of `typing.TypedDict`.
TypedDictType(SynthesizedTypedDictType<'db>), TypedDictType(SynthesizedTypedDictType<'db>),
/// An internal type representing the dictionary literal argument to the functional `TypedDict`
/// constructor.
TypedDictSchema(TypedDictSchema<'db>),
/// A single instance of `warnings.deprecated` or `typing_extensions.deprecated` /// A single instance of `warnings.deprecated` or `typing_extensions.deprecated`
Deprecated(DeprecatedInstance<'db>), Deprecated(DeprecatedInstance<'db>),
@ -6905,7 +6925,9 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
KnownInstanceType::TypedDictType(typed_dict) => { KnownInstanceType::TypedDictType(typed_dict) => {
visitor.visit_typed_dict_type(db, TypedDictType::Synthesized(typed_dict)); visitor.visit_typed_dict_type(db, TypedDictType::Synthesized(typed_dict));
} }
KnownInstanceType::Deprecated(_) | KnownInstanceType::ConstraintSet(_) => { KnownInstanceType::Deprecated(_)
| KnownInstanceType::ConstraintSet(_)
| KnownInstanceType::TypedDictSchema(_) => {
// Nothing to visit // Nothing to visit
} }
KnownInstanceType::Field(field) => { KnownInstanceType::Field(field) => {
@ -6930,15 +6952,8 @@ impl<'db> KnownInstanceType<'db> {
Self::TypedDictType(typed_dict) => { Self::TypedDictType(typed_dict) => {
Self::TypedDictType(typed_dict.normalized_impl(db, visitor)) Self::TypedDictType(typed_dict.normalized_impl(db, visitor))
} }
Self::Deprecated(deprecated) => {
// Nothing to normalize
Self::Deprecated(deprecated)
}
Self::Field(field) => Self::Field(field.normalized_impl(db, visitor)), Self::Field(field) => Self::Field(field.normalized_impl(db, visitor)),
Self::ConstraintSet(set) => { Self::Deprecated(_) | Self::TypedDictSchema(_) | Self::ConstraintSet(_) => self,
// Nothing to normalize
Self::ConstraintSet(set)
}
} }
} }
@ -6951,6 +6966,7 @@ impl<'db> KnownInstanceType<'db> {
} }
Self::TypeAliasType(_) => KnownClass::TypeAliasType, Self::TypeAliasType(_) => KnownClass::TypeAliasType,
Self::TypedDictType(_) => KnownClass::TypedDictFallback, Self::TypedDictType(_) => KnownClass::TypedDictFallback,
Self::TypedDictSchema(_) => KnownClass::Object,
Self::Deprecated(_) => KnownClass::Deprecated, Self::Deprecated(_) => KnownClass::Deprecated,
Self::Field(_) => KnownClass::Field, Self::Field(_) => KnownClass::Field,
Self::ConstraintSet(_) => KnownClass::ConstraintSet, Self::ConstraintSet(_) => KnownClass::ConstraintSet,
@ -7008,6 +7024,7 @@ impl<'db> KnownInstanceType<'db> {
} }
} }
KnownInstanceType::TypedDictType(_) => f.write_str("typing.TypedDict"), KnownInstanceType::TypedDictType(_) => f.write_str("typing.TypedDict"),
KnownInstanceType::TypedDictSchema(_) => f.write_str("_TypedDictSchema"),
// This is a legacy `TypeVar` _outside_ of any generic class or function, so we render // This is a legacy `TypeVar` _outside_ of any generic class or function, so we render
// it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll // it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll
// have a `Type::TypeVar(_)`, which is rendered as the typevar's name. // have a `Type::TypeVar(_)`, which is rendered as the typevar's name.

View File

@ -1098,8 +1098,12 @@ impl<'db> Bindings<'db> {
}, },
Type::SpecialForm(SpecialFormType::TypedDict) => { Type::SpecialForm(SpecialFormType::TypedDict) => {
let [Some(name), Some(Type::TypedDict(typed_dict)), total, ..] = let [
overload.parameter_types() Some(name),
Some(Type::KnownInstance(KnownInstanceType::TypedDictSchema(schema))),
total,
..,
] = overload.parameter_types()
else { else {
continue; continue;
}; };
@ -1113,26 +1117,19 @@ impl<'db> Bindings<'db> {
let is_total = to_bool(total, true); let is_total = to_bool(total, true);
params.set(TypedDictParams::TOTAL, is_total); params.set(TypedDictParams::TOTAL, is_total);
let items = typed_dict.items(db); let items = schema
let items = items .items(db)
.iter() .iter()
.map(|(name, field)| { .map(|(name, field)| {
let FieldKind::TypedDict {
is_required,
is_read_only,
} = field.kind
else {
unreachable!()
};
let field = Field { let field = Field {
single_declaration: None,
declared_ty: field.declared_ty,
kind: FieldKind::TypedDict { kind: FieldKind::TypedDict {
is_read_only, is_read_only: field.is_read_only,
// If there is no explicit `Required`/`NotRequired` qualifier, use // If there is no explicit `Required` or `NotRequired` qualifier, use
// the `total` parameter. // the `total` parameter.
is_required: is_required.unwrap_or(is_total).into(), is_required: field.is_required.unwrap_or(is_total),
}, },
..field.clone()
}; };
(name.clone(), field) (name.clone(), field)
@ -1142,7 +1139,7 @@ impl<'db> Bindings<'db> {
overload.set_return_type(Type::KnownInstance( overload.set_return_type(Type::KnownInstance(
KnownInstanceType::TypedDictType(SynthesizedTypedDictType::new( KnownInstanceType::TypedDictType(SynthesizedTypedDictType::new(
db, db,
Some(Name::new(name.value(db))), Name::new(name.value(db)),
params, params,
items, items,
)), )),

View File

@ -1286,7 +1286,7 @@ pub(crate) enum FieldKind<'db> {
/// `TypedDict` field metadata /// `TypedDict` field metadata
TypedDict { TypedDict {
/// Whether this field is required /// Whether this field is required
is_required: Truthiness, is_required: bool,
/// Whether this field is marked read-only /// Whether this field is marked read-only
is_read_only: bool, is_read_only: bool,
}, },
@ -1313,7 +1313,7 @@ impl<'db> Field<'db> {
FieldKind::Dataclass { FieldKind::Dataclass {
init, default_ty, .. init, default_ty, ..
} => default_ty.is_none() && *init, } => default_ty.is_none() && *init,
FieldKind::TypedDict { is_required, .. } => is_required.is_always_true(), FieldKind::TypedDict { is_required, .. } => *is_required,
} }
} }
@ -2566,7 +2566,7 @@ impl<'db> ClassLiteral<'db> {
}; };
FieldKind::TypedDict { FieldKind::TypedDict {
is_required: Truthiness::from(is_required), is_required,
is_read_only: attr.is_read_only(), is_read_only: attr.is_read_only(),
} }
} }

View File

@ -170,6 +170,7 @@ impl<'db> ClassBase<'db> {
KnownInstanceType::TypeAliasType(_) KnownInstanceType::TypeAliasType(_)
| KnownInstanceType::TypeVar(_) | KnownInstanceType::TypeVar(_)
| KnownInstanceType::TypedDictType(_) | KnownInstanceType::TypedDictType(_)
| KnownInstanceType::TypedDictSchema(_)
| KnownInstanceType::Deprecated(_) | KnownInstanceType::Deprecated(_)
| KnownInstanceType::Field(_) | KnownInstanceType::Field(_)
| KnownInstanceType::ConstraintSet(_) => None, | KnownInstanceType::ConstraintSet(_) => None,
@ -201,7 +202,8 @@ impl<'db> ClassBase<'db> {
| SpecialFormType::TypeOf | SpecialFormType::TypeOf
| SpecialFormType::CallableTypeOf | SpecialFormType::CallableTypeOf
| SpecialFormType::AlwaysTruthy | SpecialFormType::AlwaysTruthy
| SpecialFormType::AlwaysFalsy => None, | SpecialFormType::AlwaysFalsy
| SpecialFormType::TypedDictSchema => None,
SpecialFormType::Any => Some(Self::Dynamic(DynamicType::Any)), SpecialFormType::Any => Some(Self::Dynamic(DynamicType::Any)),
SpecialFormType::Unknown => Some(Self::unknown()), SpecialFormType::Unknown => Some(Self::unknown()),

View File

@ -517,13 +517,7 @@ impl Display for DisplayRepresentation<'_> {
.0 .0
.display_with(self.db, self.settings.clone()) .display_with(self.db, self.settings.clone())
.fmt(f), .fmt(f),
TypedDictType::Synthesized(synthesized) => { TypedDictType::Synthesized(synthesized) => synthesized.name(self.db).fmt(f),
let name = synthesized
.name(self.db)
.expect("cannot have incomplete `TypedDict` in type expression");
write!(f, "{name}")
}
}, },
Type::TypeAlias(alias) => f.write_str(alias.name(self.db)), Type::TypeAlias(alias) => f.write_str(alias.name(self.db)),

View File

@ -46,9 +46,7 @@ use crate::semantic_index::{
}; };
use crate::types::call::bind::MatchingOverloadIndex; use crate::types::call::bind::MatchingOverloadIndex;
use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind}; use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind};
use crate::types::class::{ use crate::types::class::{CodeGeneratorKind, FieldKind, MetaclassErrorKind, MethodDecorator};
CodeGeneratorKind, Field, FieldKind, MetaclassErrorKind, MethodDecorator,
};
use crate::types::context::{InNoTypeCheck, InferContext}; use crate::types::context::{InNoTypeCheck, InferContext};
use crate::types::cyclic::CycleDetector; use crate::types::cyclic::CycleDetector;
use crate::types::diagnostic::{ use crate::types::diagnostic::{
@ -86,7 +84,8 @@ use crate::types::signatures::Signature;
use crate::types::subclass_of::SubclassOfInner; use crate::types::subclass_of::SubclassOfInner;
use crate::types::tuple::{Tuple, TupleLength, TupleSpec, TupleType}; use crate::types::tuple::{Tuple, TupleLength, TupleSpec, TupleType};
use crate::types::typed_dict::{ use crate::types::typed_dict::{
TypedDictAssignmentKind, validate_typed_dict_constructor, validate_typed_dict_dict_literal, TypedDictAssignmentKind, TypedDictSchema, TypedDictSchemaField,
validate_typed_dict_constructor, validate_typed_dict_dict_literal,
validate_typed_dict_key_assignment, validate_typed_dict_key_assignment,
}; };
use crate::types::visitor::any_over_type; use crate::types::visitor::any_over_type;
@ -97,7 +96,7 @@ use crate::types::{
Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness, Type, Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness, Type,
TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers, TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers,
TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarInstance, TypeVarKind, TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarInstance, TypeVarKind,
TypedDictType, UnionBuilder, UnionType, binding_type, todo_type, UnionBuilder, UnionType, binding_type, todo_type,
}; };
use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic}; use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
use crate::unpack::{EvaluationMode, UnpackPosition}; use crate::unpack::{EvaluationMode, UnpackPosition};
@ -5478,8 +5477,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
return ty; return ty;
} }
// Infer the dictionary literal passed to the `TypedDict` constructor. // Infer the dictionary literal passed to the functional `TypedDict` constructor.
if let Some(ty) = self.infer_typed_dict_constructor_literal(dict, tcx) { if let Some(ty) = self.infer_typed_dict_schema(dict, tcx) {
return ty; return ty;
} }
@ -5631,8 +5630,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.map(|_| Type::TypedDict(typed_dict)) .map(|_| Type::TypedDict(typed_dict))
} }
// Infer the dictionary literal passed to the `TypedDict` constructor. // Infer the dictionary literal passed to the functional `TypedDict` constructor.
fn infer_typed_dict_constructor_literal( fn infer_typed_dict_schema(
&mut self, &mut self,
dict: &ast::ExprDict, dict: &ast::ExprDict,
tcx: TypeContext<'db>, tcx: TypeContext<'db>,
@ -5643,7 +5642,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
items, items,
} = dict; } = dict;
let Some(Type::SpecialForm(SpecialFormType::TypedDict)) = tcx.annotation else { let Some(Type::SpecialForm(SpecialFormType::TypedDictSchema)) = tcx.annotation else {
return None; return None;
}; };
@ -5673,22 +5672,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Truthiness::Ambiguous Truthiness::Ambiguous
}; };
let field = Field { let field = TypedDictSchemaField {
single_declaration: None, is_required,
declared_ty: field_ty.inner_type(), declared_ty: field_ty.inner_type(),
kind: FieldKind::TypedDict { is_read_only: field_ty.qualifiers.contains(TypeQualifiers::READ_ONLY),
is_required,
is_read_only: field_ty.qualifiers.contains(TypeQualifiers::READ_ONLY),
},
}; };
typed_dict_items.insert(ast::name::Name::new(key.value(self.db())), field); typed_dict_items.insert(ast::name::Name::new(key.value(self.db())), field);
} }
// Create an anonymous `TypedDict` from the items, to be completed by the `TypedDict` constructor binding. Some(Type::KnownInstance(KnownInstanceType::TypedDictSchema(
Some(Type::TypedDict(TypedDictType::from_items( TypedDictSchema::new(self.db(), typed_dict_items),
self.db(),
typed_dict_items,
))) )))
} }

View File

@ -769,6 +769,15 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
} }
Type::unknown() Type::unknown()
} }
KnownInstanceType::TypedDictSchema(_) => {
self.infer_type_expression(slice);
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
builder.into_diagnostic(format_args!(
"`_TypedDictSchema` is not allowed in type expressions",
));
}
Type::unknown()
}
KnownInstanceType::TypeVar(_) => { KnownInstanceType::TypeVar(_) => {
self.infer_type_expression(slice); self.infer_type_expression(slice);
todo_type!("TypeVar annotations") todo_type!("TypeVar annotations")
@ -1383,7 +1392,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
SpecialFormType::Tuple => { SpecialFormType::Tuple => {
Type::tuple(self.infer_tuple_type_expression(arguments_slice)) Type::tuple(self.infer_tuple_type_expression(arguments_slice))
} }
SpecialFormType::Generic | SpecialFormType::Protocol => { SpecialFormType::Generic
| SpecialFormType::Protocol
| SpecialFormType::TypedDictSchema => {
self.infer_expression(arguments_slice, TypeContext::default()); self.infer_expression(arguments_slice, TypeContext::default());
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
builder.into_diagnostic(format_args!( builder.into_diagnostic(format_args!(

View File

@ -127,6 +127,10 @@ pub enum SpecialFormType {
/// Typeshed defines this symbol as a class, but this isn't accurate: it's actually a factory function /// Typeshed defines this symbol as a class, but this isn't accurate: it's actually a factory function
/// at runtime. We therefore represent it as a special form internally. /// at runtime. We therefore represent it as a special form internally.
NamedTuple, NamedTuple,
/// An internal type representing the dictionary literal argument to the functional `TypedDict`
/// constructor.
TypedDictSchema,
} }
impl SpecialFormType { impl SpecialFormType {
@ -179,7 +183,9 @@ impl SpecialFormType {
| Self::ChainMap | Self::ChainMap
| Self::OrderedDict => KnownClass::StdlibAlias, | Self::OrderedDict => KnownClass::StdlibAlias,
Self::Unknown | Self::AlwaysTruthy | Self::AlwaysFalsy => KnownClass::Object, Self::Unknown | Self::AlwaysTruthy | Self::AlwaysFalsy | Self::TypedDictSchema => {
KnownClass::Object
}
Self::NamedTuple => KnownClass::FunctionType, Self::NamedTuple => KnownClass::FunctionType,
} }
@ -264,6 +270,8 @@ impl SpecialFormType {
| Self::Intersection | Self::Intersection
| Self::TypeOf | Self::TypeOf
| Self::CallableTypeOf => module.is_ty_extensions(), | Self::CallableTypeOf => module.is_ty_extensions(),
Self::TypedDictSchema => false,
} }
} }
@ -324,7 +332,8 @@ impl SpecialFormType {
| Self::ReadOnly | Self::ReadOnly
| Self::Protocol | Self::Protocol
| Self::Any | Self::Any
| Self::Generic => false, | Self::Generic
| Self::TypedDictSchema => false,
} }
} }
@ -375,6 +384,7 @@ impl SpecialFormType {
SpecialFormType::Protocol => "typing.Protocol", SpecialFormType::Protocol => "typing.Protocol",
SpecialFormType::Generic => "typing.Generic", SpecialFormType::Generic => "typing.Generic",
SpecialFormType::NamedTuple => "typing.NamedTuple", SpecialFormType::NamedTuple => "typing.NamedTuple",
SpecialFormType::TypedDictSchema => "_TypedDictSchema",
} }
} }
} }

View File

@ -18,7 +18,7 @@ use crate::types::generics::GenericContext;
use crate::types::variance::TypeVarVariance; use crate::types::variance::TypeVarVariance;
use crate::types::{ use crate::types::{
BoundTypeVarInstance, CallableSignature, CallableType, KnownClass, NormalizedVisitor, BoundTypeVarInstance, CallableSignature, CallableType, KnownClass, NormalizedVisitor,
Parameter, Parameters, Signature, StringLiteralType, SubclassOfType, UnionType, Parameter, Parameters, Signature, StringLiteralType, SubclassOfType, Truthiness, UnionType,
}; };
use crate::{Db, FxOrderMap}; use crate::{Db, FxOrderMap};
@ -62,19 +62,6 @@ impl<'db> TypedDictType<'db> {
TypedDictType::FromClass(class) TypedDictType::FromClass(class)
} }
/// Returns an anonymous (incomplete) `TypedDictType` from its items.
///
/// This is used to instantiate a `TypedDictType` from the dictionary literal passed to a
/// `typing.TypedDict` constructor (functional form for creating `TypedDict`s).
pub(crate) fn from_items(db: &'db dyn Db, items: FxOrderMap<Name, Field<'db>>) -> Self {
TypedDictType::Synthesized(SynthesizedTypedDictType::new(
db,
None,
TypedDictParams::default(),
items,
))
}
pub(crate) fn items(&self, db: &'db dyn Db) -> Cow<'db, FxOrderMap<Name, Field<'db>>> { pub(crate) fn items(&self, db: &'db dyn Db) -> Cow<'db, FxOrderMap<Name, Field<'db>>> {
match self { match self {
TypedDictType::Synthesized(synthesized) => Cow::Borrowed(synthesized.items(db)), TypedDictType::Synthesized(synthesized) => Cow::Borrowed(synthesized.items(db)),
@ -393,12 +380,39 @@ impl<'db> TypedDictType<'db> {
} }
} }
/// An internal type representing the dictionary literal argument to the functional `TypedDict`
/// constructor.
#[salsa::interned(debug, heap_size=TypedDictSchema::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct TypedDictSchema<'db> {
pub(crate) items: FxOrderMap<Name, TypedDictSchemaField<'db>>,
}
// The Salsa heap is tracked separately.
impl get_size2::GetSize for TypedDictSchema<'_> {}
impl<'db> TypedDictSchema<'db> {
fn heap_size((items,): &(FxOrderMap<Name, TypedDictSchemaField<'db>>,)) -> usize {
ruff_memory_usage::order_map_heap_size(items)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
pub struct TypedDictSchemaField<'db> {
/// The declared type of the field
pub(crate) declared_ty: Type<'db>,
/// Whether this field is required.
pub(crate) is_required: Truthiness,
/// Whether this field is marked read-only.
pub(crate) is_read_only: bool,
}
#[salsa::interned(debug, heap_size=SynthesizedTypedDictType::heap_size)] #[salsa::interned(debug, heap_size=SynthesizedTypedDictType::heap_size)]
#[derive(PartialOrd, Ord)] #[derive(PartialOrd, Ord)]
pub struct SynthesizedTypedDictType<'db> { pub struct SynthesizedTypedDictType<'db> {
// The dictionary literal passed to the `TypedDict` constructor is inferred as pub(crate) name: Name,
// an anonymous (incomplete) `SynthesizedTypedDictType`.
pub(crate) name: Option<Name>,
pub(crate) params: TypedDictParams, pub(crate) params: TypedDictParams,
@ -441,7 +455,7 @@ impl<'db> SynthesizedTypedDictType<'db> {
} }
fn heap_size( fn heap_size(
(name, params, items): &(Option<Name>, TypedDictParams, FxOrderMap<Name, Field<'db>>), (name, params, items): &(Name, TypedDictParams, FxOrderMap<Name, Field<'db>>),
) -> usize { ) -> usize {
ruff_memory_usage::heap_size(name) ruff_memory_usage::heap_size(name)
+ ruff_memory_usage::heap_size(params) + ruff_memory_usage::heap_size(params)