@@ -2924,23 +2924,19 @@ impl<'db> TypeInferenceBuilder<'db> {
29242924 | Type :: TypeVar ( ..)
29252925 | Type :: AlwaysTruthy
29262926 | Type :: AlwaysFalsy => {
2927- if let Type :: NominalInstance ( instance ) = object_ty {
2928- let dataclass_params = match instance. class ( ) {
2927+ let dataclass_params = match object_ty {
2928+ Type :: NominalInstance ( instance ) => match instance. class ( ) {
29292929 ClassType :: NonGeneric ( cls) => cls. dataclass_params ( self . db ( ) ) ,
2930- ClassType :: Generic ( _) => None ,
2931- } ;
2932- let frozen = dataclass_params
2933- . is_some_and ( |params| params. contains ( DataclassParams :: FROZEN ) ) ;
2934- if frozen && emit_diagnostics {
2935- if let Some ( builder) = self . context . report_lint ( & INVALID_ASSIGNMENT , target)
2936- {
2937- builder. into_diagnostic ( format_args ! (
2938- "Property `{attribute}` defined in `{ty}` is read-only" ,
2939- ty = object_ty. display( self . db( ) ) ,
2940- ) ) ;
2930+ ClassType :: Generic ( cls) => {
2931+ cls. origin ( self . db ( ) ) . dataclass_params ( self . db ( ) )
29412932 }
2942- }
2943- }
2933+ } ,
2934+ _ => None ,
2935+ } ;
2936+
2937+ let read_only =
2938+ dataclass_params. is_some_and ( |params| params. contains ( DataclassParams :: FROZEN ) ) ;
2939+
29442940 match object_ty. class_member ( db, attribute. into ( ) ) {
29452941 meta_attr @ SymbolAndQualifiers { .. } if meta_attr. is_class_var ( ) => {
29462942 if emit_diagnostics {
@@ -2960,68 +2956,83 @@ impl<'db> TypeInferenceBuilder<'db> {
29602956 symbol : Symbol :: Type ( meta_attr_ty, meta_attr_boundness) ,
29612957 qualifiers : _,
29622958 } => {
2963- let assignable_to_meta_attr = if let Symbol :: Type ( meta_dunder_set, _) =
2964- meta_attr_ty. class_member ( db, "__set__" . into ( ) ) . symbol
2965- {
2966- let successful_call = meta_dunder_set
2967- . try_call (
2968- db,
2969- & CallArgumentTypes :: positional ( [
2970- meta_attr_ty,
2971- object_ty,
2972- value_ty,
2973- ] ) ,
2974- )
2975- . is_ok ( ) ;
2976-
2977- if !successful_call && emit_diagnostics {
2959+ if read_only {
2960+ if emit_diagnostics {
29782961 if let Some ( builder) =
29792962 self . context . report_lint ( & INVALID_ASSIGNMENT , target)
29802963 {
2981- // TODO: Here, it would be nice to emit an additional diagnostic that explains why the call failed
29822964 builder. into_diagnostic ( format_args ! (
2983- "Invalid assignment to data descriptor attribute \
2984- `{attribute}` on type `{}` with custom `__set__` method",
2985- object_ty. display( db)
2965+ "Property `{attribute}` defined in `{ty}` is read-only" ,
2966+ ty = object_ty. display( self . db( ) ) ,
29862967 ) ) ;
29872968 }
29882969 }
2989-
2990- successful_call
2970+ false
29912971 } else {
2992- ensure_assignable_to ( meta_attr_ty)
2993- } ;
2972+ let assignable_to_meta_attr = if let Symbol :: Type ( meta_dunder_set, _) =
2973+ meta_attr_ty. class_member ( db, "__set__" . into ( ) ) . symbol
2974+ {
2975+ let successful_call = meta_dunder_set
2976+ . try_call (
2977+ db,
2978+ & CallArgumentTypes :: positional ( [
2979+ meta_attr_ty,
2980+ object_ty,
2981+ value_ty,
2982+ ] ) ,
2983+ )
2984+ . is_ok ( ) ;
29942985
2995- let assignable_to_instance_attribute = if meta_attr_boundness
2996- == Boundness :: PossiblyUnbound
2997- {
2998- let ( assignable, boundness) =
2999- if let Symbol :: Type ( instance_attr_ty, instance_attr_boundness) =
3000- object_ty. instance_member ( db, attribute) . symbol
3001- {
3002- (
3003- ensure_assignable_to ( instance_attr_ty) ,
2986+ if !successful_call && emit_diagnostics {
2987+ if let Some ( builder) =
2988+ self . context . report_lint ( & INVALID_ASSIGNMENT , target)
2989+ {
2990+ // TODO: Here, it would be nice to emit an additional diagnostic that explains why the call failed
2991+ builder. into_diagnostic ( format_args ! (
2992+ "Invalid assignment to data descriptor attribute \
2993+ `{attribute}` on type `{}` with custom `__set__` method",
2994+ object_ty. display( db)
2995+ ) ) ;
2996+ }
2997+ }
2998+
2999+ successful_call
3000+ } else {
3001+ ensure_assignable_to ( meta_attr_ty)
3002+ } ;
3003+
3004+ let assignable_to_instance_attribute =
3005+ if meta_attr_boundness == Boundness :: PossiblyUnbound {
3006+ let ( assignable, boundness) = if let Symbol :: Type (
3007+ instance_attr_ty,
30043008 instance_attr_boundness,
3005- )
3006- } else {
3007- ( true , Boundness :: PossiblyUnbound )
3008- } ;
3009+ ) =
3010+ object_ty. instance_member ( db, attribute) . symbol
3011+ {
3012+ (
3013+ ensure_assignable_to ( instance_attr_ty) ,
3014+ instance_attr_boundness,
3015+ )
3016+ } else {
3017+ ( true , Boundness :: PossiblyUnbound )
3018+ } ;
30093019
3010- if boundness == Boundness :: PossiblyUnbound {
3011- report_possibly_unbound_attribute (
3012- & self . context ,
3013- target,
3014- attribute,
3015- object_ty,
3016- ) ;
3017- }
3020+ if boundness == Boundness :: PossiblyUnbound {
3021+ report_possibly_unbound_attribute (
3022+ & self . context ,
3023+ target,
3024+ attribute,
3025+ object_ty,
3026+ ) ;
3027+ }
30183028
3019- assignable
3020- } else {
3021- true
3022- } ;
3029+ assignable
3030+ } else {
3031+ true
3032+ } ;
30233033
3024- assignable_to_meta_attr && assignable_to_instance_attribute
3034+ assignable_to_meta_attr && assignable_to_instance_attribute
3035+ }
30253036 }
30263037
30273038 SymbolAndQualifiers {
@@ -3040,7 +3051,22 @@ impl<'db> TypeInferenceBuilder<'db> {
30403051 ) ;
30413052 }
30423053
3043- ensure_assignable_to ( instance_attr_ty)
3054+ if read_only {
3055+ // TODO(thejchap): illustrating missing diagnostic
3056+ // if emit_diagnostics {
3057+ // if let Some(builder) =
3058+ // self.context.report_lint(&INVALID_ASSIGNMENT, target)
3059+ // {
3060+ // builder.into_diagnostic(format_args!(
3061+ // "Property `{attribute}` defined in `{ty}` is read-only",
3062+ // ty = object_ty.display(self.db()),
3063+ // ));
3064+ // }
3065+ // }
3066+ false
3067+ } else {
3068+ ensure_assignable_to ( instance_attr_ty)
3069+ }
30443070 } else {
30453071 let result = object_ty. try_call_dunder_with_policy (
30463072 db,
0 commit comments