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
|
# 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.
|
But we also do not emit false positives when these are used in type expressions.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
|
||||||
|
|
@ -4764,6 +4764,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
|
||||||
|
// 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_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"))
|
||||||
|
|
@ -4858,6 +4861,7 @@ impl<'db> Type<'db> {
|
||||||
Type::KnownInstance(KnownInstanceType::TypedDictType(typed_dict)) => Binding::single(
|
Type::KnownInstance(KnownInstanceType::TypedDictType(typed_dict)) => Binding::single(
|
||||||
self,
|
self,
|
||||||
Signature::new(
|
Signature::new(
|
||||||
|
// TODO: List more specific parameter types here for better code completion.
|
||||||
Parameters::new([Parameter::keyword_variadic(Name::new_static("kwargs"))
|
Parameters::new([Parameter::keyword_variadic(Name::new_static("kwargs"))
|
||||||
.with_annotated_type(Type::any())]),
|
.with_annotated_type(Type::any())]),
|
||||||
Some(Type::TypedDict(typed_dict)),
|
Some(Type::TypedDict(typed_dict)),
|
||||||
|
|
|
||||||
|
|
@ -5473,11 +5473,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
items,
|
items,
|
||||||
} = dict;
|
} = dict;
|
||||||
|
|
||||||
// Validate `TypedDict` dictionary literal assignments.
|
// Infer `TypedDict` dictionary literal assignments.
|
||||||
if let Some(ty) = self.infer_typed_dict_expression(dict, tcx) {
|
if let Some(ty) = self.infer_typed_dict_expression(dict, tcx) {
|
||||||
return ty;
|
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
|
// Avoid false positives for the functional `TypedDict` form, which is currently
|
||||||
// unsupported.
|
// unsupported.
|
||||||
if let Some(Type::Dynamic(DynamicType::Todo(_))) = tcx.annotation {
|
if let Some(Type::Dynamic(DynamicType::Todo(_))) = tcx.annotation {
|
||||||
|
|
@ -5603,16 +5608,52 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
items,
|
items,
|
||||||
} = dict;
|
} = dict;
|
||||||
|
|
||||||
// Evaluate the dictionary literal passed to the `TypedDict` constructor.
|
let typed_dict = tcx.annotation.and_then(Type::into_typed_dict)?;
|
||||||
if let Some(Type::SpecialForm(SpecialFormType::TypedDict)) = tcx.annotation {
|
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();
|
let mut typed_dict_items = FxOrderMap::default();
|
||||||
|
|
||||||
for item in items {
|
for item in items {
|
||||||
let Some(Type::StringLiteral(key)) =
|
let Some(Type::StringLiteral(key)) =
|
||||||
self.infer_optional_expression(item.key.as_ref(), TypeContext::default())
|
self.infer_optional_expression(item.key.as_ref(), TypeContext::default())
|
||||||
else {
|
else {
|
||||||
// Emit a diagnostic here? We seem to support non-string literals.
|
continue;
|
||||||
unimplemented!()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let field_ty = self.infer_typed_dict_field_type_expression(&item.value);
|
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);
|
typed_dict_items.insert(ast::name::Name::new(key.value(self.db())), field);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an incomplete synthesized `TypedDictType`, to be completed by the `TypedDict`
|
// Create an anonymous `TypedDict` from the items, to be completed by the `TypedDict` constructor binding.
|
||||||
// constructor binding.
|
Some(Type::TypedDict(TypedDictType::from_items(
|
||||||
return Some(Type::TypedDict(TypedDictType::from_items(
|
|
||||||
self.db(),
|
self.db(),
|
||||||
typed_dict_items,
|
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(
|
fn infer_typed_dict_field_type_expression(
|
||||||
|
|
|
||||||
|
|
@ -62,10 +62,10 @@ impl<'db> TypedDictType<'db> {
|
||||||
TypedDictType::FromClass(class)
|
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
|
/// 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 {
|
pub(crate) fn from_items(db: &'db dyn Db, items: FxOrderMap<Name, Field<'db>>) -> Self {
|
||||||
TypedDictType::Synthesized(SynthesizedTypedDictType::new(
|
TypedDictType::Synthesized(SynthesizedTypedDictType::new(
|
||||||
db,
|
db,
|
||||||
|
|
@ -397,7 +397,7 @@ impl<'db> TypedDictType<'db> {
|
||||||
#[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
|
// 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) name: Option<Name>,
|
||||||
|
|
||||||
pub(crate) params: TypedDictParams,
|
pub(crate) params: TypedDictParams,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue