mirror of https://github.com/astral-sh/ruff
nits
This commit is contained in:
parent
98a0b77174
commit
2959ff19bc
|
|
@ -1,6 +1,6 @@
|
|||
# Unsupported special types
|
||||
|
||||
We do not understand the functional syntax for creating `NamedTuple`s, `TypedDict`s or `Enum`s yet.
|
||||
We do not understand the functional syntax for creating `NamedTuple`s or `Enum`s yet.
|
||||
But we also do not emit false positives when these are used in type expressions.
|
||||
|
||||
```py
|
||||
|
|
|
|||
|
|
@ -4764,6 +4764,9 @@ impl<'db> Type<'db> {
|
|||
Parameter::positional_only(Some(Name::new_static("typename")))
|
||||
.with_annotated_type(KnownClass::Str.to_instance(db)),
|
||||
Parameter::positional_only(Some(Name::new_static("fields")))
|
||||
// We infer this type as an anonymous `TypedDict` instance, such that the
|
||||
// complete `TypeDict` instance can be constructed from it after. Note that
|
||||
// `typing.TypedDict` is not otherwise allowed in type-form expressions.
|
||||
.with_annotated_type(Type::SpecialForm(SpecialFormType::TypedDict))
|
||||
.with_default_type(Type::any()),
|
||||
Parameter::keyword_only(Name::new_static("total"))
|
||||
|
|
@ -4858,6 +4861,7 @@ impl<'db> Type<'db> {
|
|||
Type::KnownInstance(KnownInstanceType::TypedDictType(typed_dict)) => Binding::single(
|
||||
self,
|
||||
Signature::new(
|
||||
// TODO: List more specific parameter types here for better code completion.
|
||||
Parameters::new([Parameter::keyword_variadic(Name::new_static("kwargs"))
|
||||
.with_annotated_type(Type::any())]),
|
||||
Some(Type::TypedDict(typed_dict)),
|
||||
|
|
|
|||
|
|
@ -5473,11 +5473,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
items,
|
||||
} = dict;
|
||||
|
||||
// Validate `TypedDict` dictionary literal assignments.
|
||||
// Infer `TypedDict` dictionary literal assignments.
|
||||
if let Some(ty) = self.infer_typed_dict_expression(dict, tcx) {
|
||||
return ty;
|
||||
}
|
||||
|
||||
// Infer the dictionary literal passed to the `TypedDict` constructor.
|
||||
if let Some(ty) = self.infer_typed_dict_constructor_literal(dict, tcx) {
|
||||
return ty;
|
||||
}
|
||||
|
||||
// Avoid false positives for the functional `TypedDict` form, which is currently
|
||||
// unsupported.
|
||||
if let Some(Type::Dynamic(DynamicType::Todo(_))) = tcx.annotation {
|
||||
|
|
@ -5603,16 +5608,52 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
items,
|
||||
} = dict;
|
||||
|
||||
// Evaluate the dictionary literal passed to the `TypedDict` constructor.
|
||||
if let Some(Type::SpecialForm(SpecialFormType::TypedDict)) = tcx.annotation {
|
||||
let typed_dict = tcx.annotation.and_then(Type::into_typed_dict)?;
|
||||
let typed_dict_items = typed_dict.items(self.db());
|
||||
|
||||
for item in items {
|
||||
self.infer_optional_expression(item.key.as_ref(), TypeContext::default());
|
||||
|
||||
if let Some(ast::Expr::StringLiteral(ref key)) = item.key
|
||||
&& let Some(key) = key.as_single_part_string()
|
||||
&& let Some(field) = typed_dict_items.get(key.as_str())
|
||||
{
|
||||
self.infer_expression(&item.value, TypeContext::new(Some(field.declared_ty)));
|
||||
} else {
|
||||
self.infer_expression(&item.value, TypeContext::default());
|
||||
}
|
||||
}
|
||||
|
||||
validate_typed_dict_dict_literal(&self.context, typed_dict, dict, dict.into(), |expr| {
|
||||
self.expression_type(expr)
|
||||
})
|
||||
.ok()
|
||||
.map(|_| Type::TypedDict(typed_dict))
|
||||
}
|
||||
|
||||
// Infer the dictionary literal passed to the `TypedDict` constructor.
|
||||
fn infer_typed_dict_constructor_literal(
|
||||
&mut self,
|
||||
dict: &ast::ExprDict,
|
||||
tcx: TypeContext<'db>,
|
||||
) -> Option<Type<'db>> {
|
||||
let ast::ExprDict {
|
||||
range: _,
|
||||
node_index: _,
|
||||
items,
|
||||
} = dict;
|
||||
|
||||
let Some(Type::SpecialForm(SpecialFormType::TypedDict)) = tcx.annotation else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut typed_dict_items = FxOrderMap::default();
|
||||
|
||||
for item in items {
|
||||
let Some(Type::StringLiteral(key)) =
|
||||
self.infer_optional_expression(item.key.as_ref(), TypeContext::default())
|
||||
else {
|
||||
// Emit a diagnostic here? We seem to support non-string literals.
|
||||
unimplemented!()
|
||||
continue;
|
||||
};
|
||||
|
||||
let field_ty = self.infer_typed_dict_field_type_expression(&item.value);
|
||||
|
|
@ -5641,35 +5682,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
typed_dict_items.insert(ast::name::Name::new(key.value(self.db())), field);
|
||||
}
|
||||
|
||||
// Create an incomplete synthesized `TypedDictType`, to be completed by the `TypedDict`
|
||||
// constructor binding.
|
||||
return Some(Type::TypedDict(TypedDictType::from_items(
|
||||
// Create an anonymous `TypedDict` from the items, to be completed by the `TypedDict` constructor binding.
|
||||
Some(Type::TypedDict(TypedDictType::from_items(
|
||||
self.db(),
|
||||
typed_dict_items,
|
||||
)));
|
||||
}
|
||||
|
||||
let typed_dict = tcx.annotation.and_then(Type::into_typed_dict)?;
|
||||
let typed_dict_items = typed_dict.items(self.db());
|
||||
|
||||
for item in items {
|
||||
self.infer_optional_expression(item.key.as_ref(), TypeContext::default());
|
||||
|
||||
if let Some(ast::Expr::StringLiteral(ref key)) = item.key
|
||||
&& let Some(key) = key.as_single_part_string()
|
||||
&& let Some(field) = typed_dict_items.get(key.as_str())
|
||||
{
|
||||
self.infer_expression(&item.value, TypeContext::new(Some(field.declared_ty)));
|
||||
} else {
|
||||
self.infer_expression(&item.value, TypeContext::default());
|
||||
}
|
||||
}
|
||||
|
||||
validate_typed_dict_dict_literal(&self.context, typed_dict, dict, dict.into(), |expr| {
|
||||
self.expression_type(expr)
|
||||
})
|
||||
.ok()
|
||||
.map(|_| Type::TypedDict(typed_dict))
|
||||
)))
|
||||
}
|
||||
|
||||
fn infer_typed_dict_field_type_expression(
|
||||
|
|
|
|||
|
|
@ -62,10 +62,10 @@ impl<'db> TypedDictType<'db> {
|
|||
TypedDictType::FromClass(class)
|
||||
}
|
||||
|
||||
/// Returns an incomplete `TypedDictType` from its items.
|
||||
/// Returns an anonymous (incomplete) `TypedDictType` from its items.
|
||||
///
|
||||
/// This is used to instantiate a `TypedDictType` from the dictionary literal passed to a
|
||||
/// `TypedDict` constructor.
|
||||
/// `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,
|
||||
|
|
@ -397,7 +397,7 @@ impl<'db> TypedDictType<'db> {
|
|||
#[derive(PartialOrd, Ord)]
|
||||
pub struct SynthesizedTypedDictType<'db> {
|
||||
// The dictionary literal passed to the `TypedDict` constructor is inferred as
|
||||
// a nameless `SynthesizedTypedDictType`.
|
||||
// an anonymous (incomplete) `SynthesizedTypedDictType`.
|
||||
pub(crate) name: Option<Name>,
|
||||
|
||||
pub(crate) params: TypedDictParams,
|
||||
|
|
|
|||
Loading…
Reference in New Issue