mirror of https://github.com/astral-sh/ruff
use internal `TypedDictSchema` type for functional `TypedDict` constructor
This commit is contained in:
parent
f2a25b0fd7
commit
b753851379
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)),
|
)),
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()),
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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!(
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue