@@ -1780,6 +1780,100 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
17801780 . is_some_and ( |metadata| metadata. fields . contains_key ( field_name) )
17811781 }
17821782
1783+ fn typed_dict_field_info (
1784+ & self ,
1785+ metadata : & ClassMetadata ,
1786+ field_name : & Name ,
1787+ field : & ClassField ,
1788+ ) -> Option < TypedDictField > {
1789+ metadata
1790+ . typed_dict_metadata ( )
1791+ . and_then ( |typed_dict| typed_dict. fields . get ( field_name) )
1792+ . and_then ( |is_total| field. clone ( ) . as_typed_dict_field_info ( * is_total) )
1793+ }
1794+
1795+ fn validate_typed_dict_field_override (
1796+ & self ,
1797+ child_cls : & Class ,
1798+ child_metadata : & ClassMetadata ,
1799+ parent_cls : & Class ,
1800+ parent_metadata : & ClassMetadata ,
1801+ field_name : & Name ,
1802+ child_field : & ClassField ,
1803+ parent_field : & ClassField ,
1804+ range : TextRange ,
1805+ errors : & ErrorCollector ,
1806+ ) -> bool {
1807+ let Some ( child_info) = self . typed_dict_field_info ( child_metadata, field_name, child_field)
1808+ else {
1809+ return true ;
1810+ } ;
1811+ let Some ( parent_info) =
1812+ self . typed_dict_field_info ( parent_metadata, field_name, parent_field)
1813+ else {
1814+ return true ;
1815+ } ;
1816+
1817+ let parent_mutable = !parent_info. is_read_only ( ) ;
1818+ let child_mutable = !child_info. is_read_only ( ) ;
1819+
1820+ if parent_mutable {
1821+ if !child_mutable {
1822+ self . error (
1823+ errors,
1824+ range,
1825+ ErrorInfo :: Kind ( ErrorKind :: BadTypedDictKey ) ,
1826+ format ! (
1827+ "TypedDict field `{field_name}` in `{}` cannot be marked read-only; parent TypedDict `{}` defines it as mutable" ,
1828+ child_cls. name( ) ,
1829+ parent_cls. name( )
1830+ ) ,
1831+ ) ;
1832+ return false ;
1833+ }
1834+ if parent_info. required && !child_info. required {
1835+ self . error (
1836+ errors,
1837+ range,
1838+ ErrorInfo :: Kind ( ErrorKind :: BadTypedDictKey ) ,
1839+ format ! (
1840+ "TypedDict field `{field_name}` in `{}` must remain required because parent TypedDict `{}` defines it as required" ,
1841+ child_cls. name( ) ,
1842+ parent_cls. name( )
1843+ ) ,
1844+ ) ;
1845+ return false ;
1846+ }
1847+ if !parent_info. required && child_info. required {
1848+ self . error (
1849+ errors,
1850+ range,
1851+ ErrorInfo :: Kind ( ErrorKind :: BadTypedDictKey ) ,
1852+ format ! (
1853+ "TypedDict field `{field_name}` in `{}` cannot be made required; parent TypedDict `{}` defines it as non-required" ,
1854+ child_cls. name( ) ,
1855+ parent_cls. name( )
1856+ ) ,
1857+ ) ;
1858+ return false ;
1859+ }
1860+ } else if parent_info. required && !child_info. required {
1861+ self . error (
1862+ errors,
1863+ range,
1864+ ErrorInfo :: Kind ( ErrorKind :: BadTypedDictKey ) ,
1865+ format ! (
1866+ "TypedDict field `{field_name}` in `{}` cannot be made non-required; parent TypedDict `{}` defines it as required" ,
1867+ child_cls. name( ) ,
1868+ parent_cls. name( )
1869+ ) ,
1870+ ) ;
1871+ return false ;
1872+ }
1873+
1874+ true
1875+ }
1876+
17831877 fn should_check_field_for_override_consistency ( & self , field_name : & Name ) -> bool {
17841878 // Object construction (`__new__`, `__init__`, `__init_subclass__`) should not participate in override checks
17851879 if field_name == & dunder:: NEW
@@ -1855,8 +1949,8 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
18551949 let mut got_attribute = None ;
18561950 let mut parent_attr_found = false ;
18571951 let mut parent_has_any = false ;
1858- let is_typed_dict_field =
1859- self . is_typed_dict_field ( & self . get_metadata_for_class ( cls ) , field_name) ;
1952+ let metadata = self . get_metadata_for_class ( cls ) ;
1953+ let is_typed_dict_field = self . is_typed_dict_field ( metadata . as_ref ( ) , field_name) ;
18601954
18611955 let bases_to_check: Box < dyn Iterator < Item = & ClassType > > = if bases. is_empty ( ) {
18621956 // If the class doesn't have any base type, we should just use `object` as base to ensure
@@ -1935,6 +2029,21 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
19352029 // keys but not regular fields.
19362030 continue ;
19372031 }
2032+ if is_typed_dict_field
2033+ && !self . validate_typed_dict_field_override (
2034+ cls,
2035+ metadata. as_ref ( ) ,
2036+ parent_cls,
2037+ parent_metadata. as_ref ( ) ,
2038+ field_name,
2039+ class_field,
2040+ & want_class_field,
2041+ range,
2042+ errors,
2043+ )
2044+ {
2045+ continue ;
2046+ }
19382047 // Substitute `Self` with derived class to support contravariant occurrences of `Self`
19392048 let want_attribute = self . as_instance_attribute (
19402049 & want_class_field,
@@ -2005,38 +2114,56 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
20052114
20062115 /// For classes with multiple inheritance, check that fields inherited from multiple base classes are consistent.
20072116 pub fn check_consistent_multiple_inheritance ( & self , cls : & Class , errors : & ErrorCollector ) {
2008- // Maps field from inherited class
2117+ struct InheritedFieldInfo {
2118+ class : Class ,
2119+ metadata : Arc < ClassMetadata > ,
2120+ field : ClassField ,
2121+ ty : Type ,
2122+ }
2123+
20092124 let mro = self . get_mro_for_class ( cls) ;
2010- let mut inherited_fields: SmallMap < & Name , Vec < ( & Name , Type ) > > = SmallMap :: new ( ) ;
2125+ let mut inherited_fields: SmallMap < & Name , Vec < InheritedFieldInfo > > = SmallMap :: new ( ) ;
20112126 let current_class_fields: SmallSet < _ > = cls. fields ( ) . collect ( ) ;
20122127
20132128 for parent_cls in mro. ancestors_no_object ( ) . iter ( ) {
2014- let class_fields = parent_cls. class_object ( ) . fields ( ) ;
2129+ let class_object = parent_cls. class_object ( ) ;
2130+ let class_fields = class_object. fields ( ) ;
2131+ let parent_metadata = self . get_metadata_for_class ( class_object) ;
20152132 for field in class_fields {
2016- // Skip fields that are not relevant for override consistency checks
20172133 if !self . should_check_field_for_override_consistency ( field) {
20182134 continue ;
20192135 }
2020- let key = KeyClassField ( parent_cls. class_object ( ) . index ( ) , field. clone ( ) ) ;
2136+ if current_class_fields. contains ( field) {
2137+ continue ;
2138+ }
2139+ let key = KeyClassField ( class_object. index ( ) , field. clone ( ) ) ;
20212140 let field_entry = self . get_from_class ( cls, & key) ;
2022- if let Some ( field_entry) = field_entry. as_ref ( ) {
2023- // Skip fields that are overridden in the current class,
2024- // they will have been checked by
2025- // `check_consistent_override_for_field` already
2026- if current_class_fields. contains ( field) {
2027- continue ;
2028- }
2141+ let Some ( field_entry) = field_entry. as_ref ( ) else {
2142+ continue ;
2143+ } ;
2144+ if let Some ( parent_member) = self . get_class_member ( class_object, field) {
2145+ let parent_field = Arc :: unwrap_or_clone ( parent_member. value . clone ( ) ) ;
20292146 inherited_fields
20302147 . entry ( field)
20312148 . or_default ( )
2032- . push ( ( parent_cls. name ( ) , field_entry. ty ( ) ) ) ;
2149+ . push ( InheritedFieldInfo {
2150+ class : class_object. dupe ( ) ,
2151+ metadata : parent_metadata. clone ( ) ,
2152+ field : parent_field,
2153+ ty : field_entry. ty ( ) ,
2154+ } ) ;
20332155 }
20342156 }
20352157 }
20362158
2037- for ( field_name, class_and_types) in inherited_fields. iter ( ) {
2038- if class_and_types. len ( ) > 1 {
2039- let types: Vec < Type > = class_and_types. iter ( ) . map ( |( _, ty) | ty. clone ( ) ) . collect ( ) ;
2159+ let child_metadata = self . get_metadata_for_class ( cls) ;
2160+
2161+ for ( field_name, inherited_field_infos_by_ancestor) in inherited_fields. iter ( ) {
2162+ if inherited_field_infos_by_ancestor. len ( ) > 1 {
2163+ let types: Vec < Type > = inherited_field_infos_by_ancestor
2164+ . iter ( )
2165+ . map ( |info| info. ty . clone ( ) )
2166+ . collect ( ) ;
20402167 if types. iter ( ) . any ( |ty| {
20412168 matches ! (
20422169 ty,
@@ -2046,8 +2173,6 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
20462173 | Type :: Overload ( _)
20472174 )
20482175 } ) {
2049- // TODO(fangyizhou): Handle bound methods and functions properly.
2050- // This is a leftover from https://github.com/facebook/pyrefly/pull/1196
20512176 continue ;
20522177 }
20532178 let intersect = self . intersects ( & types) ;
@@ -2058,11 +2183,11 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
20582183 ) ,
20592184 "Inherited types include:" . to_owned( )
20602185 ] ;
2061- for ( cls , ty ) in class_and_types . iter ( ) {
2186+ for info in inherited_field_infos_by_ancestor . iter ( ) {
20622187 error_msg. push ( format ! (
20632188 " `{}` from `{}`" ,
2064- self . for_display( ty. clone( ) ) ,
2065- cls
2189+ self . for_display( info . ty. clone( ) ) ,
2190+ info . class . name ( )
20662191 ) ) ;
20672192 }
20682193 errors. add (
@@ -2071,6 +2196,38 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
20712196 error_msg,
20722197 ) ;
20732198 }
2199+
2200+ if let Some ( child_member) = self . get_class_member ( cls, field_name) {
2201+ let child_field = Arc :: unwrap_or_clone ( child_member. value . clone ( ) ) ;
2202+ if self
2203+ . typed_dict_field_info ( child_metadata. as_ref ( ) , field_name, & child_field)
2204+ . is_some ( )
2205+ {
2206+ for info in inherited_field_infos_by_ancestor {
2207+ if self
2208+ . typed_dict_field_info (
2209+ info. metadata . as_ref ( ) ,
2210+ field_name,
2211+ & info. field ,
2212+ )
2213+ . is_some ( )
2214+ && !self . validate_typed_dict_field_override (
2215+ cls,
2216+ child_metadata. as_ref ( ) ,
2217+ & info. class ,
2218+ info. metadata . as_ref ( ) ,
2219+ field_name,
2220+ & child_field,
2221+ & info. field ,
2222+ cls. range ( ) ,
2223+ errors,
2224+ )
2225+ {
2226+ continue ;
2227+ }
2228+ }
2229+ }
2230+ }
20742231 }
20752232 }
20762233 }
0 commit comments