Skip to content

Commit 2959ff1

Browse files
committed
nits
1 parent 98a0b77 commit 2959ff1

File tree

4 files changed

+72
-51
lines changed

4 files changed

+72
-51
lines changed

crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_types.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Unsupported special types
22

3-
We do not understand the functional syntax for creating `NamedTuple`s, `TypedDict`s or `Enum`s yet.
3+
We do not understand the functional syntax for creating `NamedTuple`s or `Enum`s yet.
44
But we also do not emit false positives when these are used in type expressions.
55

66
```py

crates/ty_python_semantic/src/types.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4764,6 +4764,9 @@ impl<'db> Type<'db> {
47644764
Parameter::positional_only(Some(Name::new_static("typename")))
47654765
.with_annotated_type(KnownClass::Str.to_instance(db)),
47664766
Parameter::positional_only(Some(Name::new_static("fields")))
4767+
// We infer this type as an anonymous `TypedDict` instance, such that the
4768+
// complete `TypeDict` instance can be constructed from it after. Note that
4769+
// `typing.TypedDict` is not otherwise allowed in type-form expressions.
47674770
.with_annotated_type(Type::SpecialForm(SpecialFormType::TypedDict))
47684771
.with_default_type(Type::any()),
47694772
Parameter::keyword_only(Name::new_static("total"))
@@ -4858,6 +4861,7 @@ impl<'db> Type<'db> {
48584861
Type::KnownInstance(KnownInstanceType::TypedDictType(typed_dict)) => Binding::single(
48594862
self,
48604863
Signature::new(
4864+
// TODO: List more specific parameter types here for better code completion.
48614865
Parameters::new([Parameter::keyword_variadic(Name::new_static("kwargs"))
48624866
.with_annotated_type(Type::any())]),
48634867
Some(Type::TypedDict(typed_dict)),

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 64 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5473,11 +5473,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
54735473
items,
54745474
} = dict;
54755475

5476-
// Validate `TypedDict` dictionary literal assignments.
5476+
// Infer `TypedDict` dictionary literal assignments.
54775477
if let Some(ty) = self.infer_typed_dict_expression(dict, tcx) {
54785478
return ty;
54795479
}
54805480

5481+
// Infer the dictionary literal passed to the `TypedDict` constructor.
5482+
if let Some(ty) = self.infer_typed_dict_constructor_literal(dict, tcx) {
5483+
return ty;
5484+
}
5485+
54815486
// Avoid false positives for the functional `TypedDict` form, which is currently
54825487
// unsupported.
54835488
if let Some(Type::Dynamic(DynamicType::Todo(_))) = tcx.annotation {
@@ -5603,52 +5608,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
56035608
items,
56045609
} = dict;
56055610

5606-
// Evaluate the dictionary literal passed to the `TypedDict` constructor.
5607-
if let Some(Type::SpecialForm(SpecialFormType::TypedDict)) = tcx.annotation {
5608-
let mut typed_dict_items = FxOrderMap::default();
5609-
5610-
for item in items {
5611-
let Some(Type::StringLiteral(key)) =
5612-
self.infer_optional_expression(item.key.as_ref(), TypeContext::default())
5613-
else {
5614-
// Emit a diagnostic here? We seem to support non-string literals.
5615-
unimplemented!()
5616-
};
5617-
5618-
let field_ty = self.infer_typed_dict_field_type_expression(&item.value);
5619-
5620-
let is_required = if field_ty.qualifiers.contains(TypeQualifiers::REQUIRED) {
5621-
// Explicit Required[T] annotation - always required
5622-
Truthiness::AlwaysTrue
5623-
} else if field_ty.qualifiers.contains(TypeQualifiers::NOT_REQUIRED) {
5624-
// Explicit NotRequired[T] annotation - never required
5625-
Truthiness::AlwaysFalse
5626-
} else {
5627-
// No explicit qualifier - we don't have access to the `total` qualifier here,
5628-
// so we leave this to be filled in by the `TypedDict` constructor.
5629-
Truthiness::Ambiguous
5630-
};
5631-
5632-
let field = Field {
5633-
single_declaration: None,
5634-
declared_ty: field_ty.inner_type(),
5635-
kind: FieldKind::TypedDict {
5636-
is_required,
5637-
is_read_only: field_ty.qualifiers.contains(TypeQualifiers::READ_ONLY),
5638-
},
5639-
};
5640-
5641-
typed_dict_items.insert(ast::name::Name::new(key.value(self.db())), field);
5642-
}
5643-
5644-
// Create an incomplete synthesized `TypedDictType`, to be completed by the `TypedDict`
5645-
// constructor binding.
5646-
return Some(Type::TypedDict(TypedDictType::from_items(
5647-
self.db(),
5648-
typed_dict_items,
5649-
)));
5650-
}
5651-
56525611
let typed_dict = tcx.annotation.and_then(Type::into_typed_dict)?;
56535612
let typed_dict_items = typed_dict.items(self.db());
56545613

@@ -5672,6 +5631,64 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
56725631
.map(|_| Type::TypedDict(typed_dict))
56735632
}
56745633

5634+
// Infer the dictionary literal passed to the `TypedDict` constructor.
5635+
fn infer_typed_dict_constructor_literal(
5636+
&mut self,
5637+
dict: &ast::ExprDict,
5638+
tcx: TypeContext<'db>,
5639+
) -> Option<Type<'db>> {
5640+
let ast::ExprDict {
5641+
range: _,
5642+
node_index: _,
5643+
items,
5644+
} = dict;
5645+
5646+
let Some(Type::SpecialForm(SpecialFormType::TypedDict)) = tcx.annotation else {
5647+
return None;
5648+
};
5649+
5650+
let mut typed_dict_items = FxOrderMap::default();
5651+
5652+
for item in items {
5653+
let Some(Type::StringLiteral(key)) =
5654+
self.infer_optional_expression(item.key.as_ref(), TypeContext::default())
5655+
else {
5656+
continue;
5657+
};
5658+
5659+
let field_ty = self.infer_typed_dict_field_type_expression(&item.value);
5660+
5661+
let is_required = if field_ty.qualifiers.contains(TypeQualifiers::REQUIRED) {
5662+
// Explicit Required[T] annotation - always required
5663+
Truthiness::AlwaysTrue
5664+
} else if field_ty.qualifiers.contains(TypeQualifiers::NOT_REQUIRED) {
5665+
// Explicit NotRequired[T] annotation - never required
5666+
Truthiness::AlwaysFalse
5667+
} else {
5668+
// No explicit qualifier - we don't have access to the `total` qualifier here,
5669+
// so we leave this to be filled in by the `TypedDict` constructor.
5670+
Truthiness::Ambiguous
5671+
};
5672+
5673+
let field = Field {
5674+
single_declaration: None,
5675+
declared_ty: field_ty.inner_type(),
5676+
kind: FieldKind::TypedDict {
5677+
is_required,
5678+
is_read_only: field_ty.qualifiers.contains(TypeQualifiers::READ_ONLY),
5679+
},
5680+
};
5681+
5682+
typed_dict_items.insert(ast::name::Name::new(key.value(self.db())), field);
5683+
}
5684+
5685+
// Create an anonymous `TypedDict` from the items, to be completed by the `TypedDict` constructor binding.
5686+
Some(Type::TypedDict(TypedDictType::from_items(
5687+
self.db(),
5688+
typed_dict_items,
5689+
)))
5690+
}
5691+
56755692
fn infer_typed_dict_field_type_expression(
56765693
&mut self,
56775694
expr: &ast::Expr,

crates/ty_python_semantic/src/types/typed_dict.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ impl<'db> TypedDictType<'db> {
6262
TypedDictType::FromClass(class)
6363
}
6464

65-
/// Returns an incomplete `TypedDictType` from its items.
65+
/// Returns an anonymous (incomplete) `TypedDictType` from its items.
6666
///
6767
/// This is used to instantiate a `TypedDictType` from the dictionary literal passed to a
68-
/// `TypedDict` constructor.
68+
/// `typing.TypedDict` constructor (functional form for creating `TypedDict`s).
6969
pub(crate) fn from_items(db: &'db dyn Db, items: FxOrderMap<Name, Field<'db>>) -> Self {
7070
TypedDictType::Synthesized(SynthesizedTypedDictType::new(
7171
db,
@@ -397,7 +397,7 @@ impl<'db> TypedDictType<'db> {
397397
#[derive(PartialOrd, Ord)]
398398
pub struct SynthesizedTypedDictType<'db> {
399399
// The dictionary literal passed to the `TypedDict` constructor is inferred as
400-
// a nameless `SynthesizedTypedDictType`.
400+
// an anonymous (incomplete) `SynthesizedTypedDictType`.
401401
pub(crate) name: Option<Name>,
402402

403403
pub(crate) params: TypedDictParams,

0 commit comments

Comments
 (0)