File tree Expand file tree Collapse file tree
crates/ty_python_semantic Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -1657,6 +1657,47 @@ config["host"] = "127.0.0.1"
16571657config[" port" ] = 80
16581658```
16591659
1660+ ## ` update() ` with ` ReadOnly ` items
1661+
1662+ ` update() ` also cannot write to ` ReadOnly ` items, unless the source key is bottom-typed and
1663+ therefore cannot be present:
1664+
1665+ ``` py
1666+ from typing_extensions import Never, NotRequired, ReadOnly, TypedDict
1667+
1668+ class ReadOnlyPerson (TypedDict ):
1669+ id : ReadOnly[int ]
1670+ age: int
1671+
1672+ class AgePatch (TypedDict , total = False ):
1673+ age: int
1674+
1675+ class IdPatch (TypedDict , total = False ):
1676+ id : int
1677+
1678+ class ImpossibleIdPatch (TypedDict , total = False ):
1679+ id : NotRequired[Never]
1680+
1681+ person: ReadOnlyPerson = {" id" : 1 , " age" : 30 }
1682+ age_patch: AgePatch = {" age" : 31 }
1683+ id_patch: IdPatch = {" id" : 2 }
1684+ impossible_id_patch: ImpossibleIdPatch = {}
1685+
1686+ person.update(age_patch)
1687+
1688+ # error: [invalid-argument-type]
1689+ person.update(id_patch)
1690+
1691+ # error: [invalid-argument-type]
1692+ # error: [invalid-argument-type]
1693+ person.update({" id" : 2 })
1694+
1695+ # error: [invalid-argument-type]
1696+ person.update(id = 2 )
1697+
1698+ person.update(impossible_id_patch)
1699+ ```
1700+
16601701## Methods on ` TypedDict `
16611702
16621703``` py
Original file line number Diff line number Diff line change @@ -2027,14 +2027,14 @@ pub(super) fn synthesize_typed_dict_update_member<'db>(
20272027 instance_ty : Type < ' db > ,
20282028 keyword_parameters : & [ Parameter < ' db > ] ,
20292029) -> Type < ' db > {
2030- let partial_ty = if let Type :: TypedDict ( typed_dict) = instance_ty {
2031- Type :: TypedDict ( typed_dict. to_partial ( db) )
2030+ let update_patch_ty = if let Type :: TypedDict ( typed_dict) = instance_ty {
2031+ Type :: TypedDict ( typed_dict. to_update_patch ( db) )
20322032 } else {
20332033 instance_ty
20342034 } ;
20352035
20362036 let value_ty = UnionBuilder :: new ( db)
2037- . add ( partial_ty )
2037+ . add ( update_patch_ty )
20382038 . add ( KnownClass :: Iterable . to_specialized_instance (
20392039 db,
20402040 & [ Type :: heterogeneous_tuple (
Original file line number Diff line number Diff line change @@ -1978,15 +1978,20 @@ impl<'db> StaticClassLiteral<'db> {
19781978 ) ) )
19791979 }
19801980 ( CodeGeneratorKind :: TypedDict , "update" ) => {
1981- let keyword_parameters: Vec < _ > = self
1982- . fields ( db, specialization, field_policy)
1983- . iter ( )
1984- . map ( |( name, field) | {
1985- Parameter :: keyword_only ( name. clone ( ) )
1986- . with_annotated_type ( field. declared_ty )
1987- . with_default_type ( field. declared_ty )
1988- } )
1989- . collect ( ) ;
1981+ let keyword_parameters: Vec < _ > = if let Type :: TypedDict ( typed_dict) = instance_ty {
1982+ typed_dict
1983+ . to_update_patch ( db)
1984+ . items ( db)
1985+ . iter ( )
1986+ . map ( |( name, field) | {
1987+ Parameter :: keyword_only ( name. clone ( ) )
1988+ . with_annotated_type ( field. declared_ty )
1989+ . with_default_type ( field. declared_ty )
1990+ } )
1991+ . collect ( )
1992+ } else {
1993+ Vec :: new ( )
1994+ } ;
19901995
19911996 Some ( synthesize_typed_dict_update_member (
19921997 db,
Original file line number Diff line number Diff line change @@ -151,6 +151,27 @@ impl<'db> TypedDictType<'db> {
151151 Self :: from_patch_items ( db, items)
152152 }
153153
154+ /// Returns a patch version of this `TypedDict` for `TypedDict.update()`.
155+ ///
156+ /// All fields become optional, and read-only fields become bottom-typed. This preserves the
157+ /// PEP 705 rule that `update()` must reject any source that can write a read-only key, while
158+ /// still accepting `NotRequired[Never]` placeholders for keys that cannot be present.
159+ pub ( crate ) fn to_update_patch ( self , db : & ' db dyn Db ) -> Self {
160+ let items: TypedDictSchema < ' db > = self
161+ . items ( db)
162+ . iter ( )
163+ . map ( |( name, field) | {
164+ let mut field = field. clone ( ) . with_required ( false ) ;
165+ if field. is_read_only ( ) {
166+ field. declared_ty = Type :: Never ;
167+ }
168+ ( name. clone ( ) , field)
169+ } )
170+ . collect ( ) ;
171+
172+ Self :: from_patch_items ( db, items)
173+ }
174+
154175 pub fn definition ( self , db : & ' db dyn Db ) -> Option < Definition < ' db > > {
155176 match self {
156177 TypedDictType :: Class ( defining_class) => defining_class. definition ( db) ,
You can’t perform that action at this time.
0 commit comments