@@ -3066,23 +3066,19 @@ impl<'db> TypeInferenceBuilder<'db> {
30663066 | Type :: TypeVar ( ..)
30673067 | Type :: AlwaysTruthy
30683068 | Type :: AlwaysFalsy => {
3069- if let Type :: NominalInstance ( instance ) = object_ty {
3070- let dataclass_params = match instance. class ( ) {
3069+ let dataclass_params = match object_ty {
3070+ Type :: NominalInstance ( instance ) => match instance. class ( ) {
30713071 ClassType :: NonGeneric ( cls) => cls. dataclass_params ( self . db ( ) ) ,
3072- ClassType :: Generic ( _) => None ,
3073- } ;
3074- let frozen = dataclass_params
3075- . is_some_and ( |params| params. contains ( DataclassParams :: FROZEN ) ) ;
3076- if frozen && emit_diagnostics {
3077- if let Some ( builder) = self . context . report_lint ( & INVALID_ASSIGNMENT , target)
3078- {
3079- builder. into_diagnostic ( format_args ! (
3080- "Property `{attribute}` defined in `{ty}` is read-only" ,
3081- ty = object_ty. display( self . db( ) ) ,
3082- ) ) ;
3072+ ClassType :: Generic ( cls) => {
3073+ cls. origin ( self . db ( ) ) . dataclass_params ( self . db ( ) )
30833074 }
3084- }
3085- }
3075+ } ,
3076+ _ => None ,
3077+ } ;
3078+
3079+ let read_only =
3080+ dataclass_params. is_some_and ( |params| params. contains ( DataclassParams :: FROZEN ) ) ;
3081+
30863082 match object_ty. class_member ( db, attribute. into ( ) ) {
30873083 meta_attr @ SymbolAndQualifiers { .. } if meta_attr. is_class_var ( ) => {
30883084 if emit_diagnostics {
@@ -3102,68 +3098,83 @@ impl<'db> TypeInferenceBuilder<'db> {
31023098 symbol : Symbol :: Type ( meta_attr_ty, meta_attr_boundness) ,
31033099 qualifiers : _,
31043100 } => {
3105- let assignable_to_meta_attr = if let Symbol :: Type ( meta_dunder_set, _) =
3106- meta_attr_ty. class_member ( db, "__set__" . into ( ) ) . symbol
3107- {
3108- let successful_call = meta_dunder_set
3109- . try_call (
3110- db,
3111- & CallArgumentTypes :: positional ( [
3112- meta_attr_ty,
3113- object_ty,
3114- value_ty,
3115- ] ) ,
3116- )
3117- . is_ok ( ) ;
3118-
3119- if !successful_call && emit_diagnostics {
3101+ if read_only {
3102+ if emit_diagnostics {
31203103 if let Some ( builder) =
31213104 self . context . report_lint ( & INVALID_ASSIGNMENT , target)
31223105 {
3123- // TODO: Here, it would be nice to emit an additional diagnostic that explains why the call failed
31243106 builder. into_diagnostic ( format_args ! (
3125- "Invalid assignment to data descriptor attribute \
3126- `{attribute}` on type `{}` with custom `__set__` method",
3127- object_ty. display( db)
3107+ "Property `{attribute}` defined in `{ty}` is read-only" ,
3108+ ty = object_ty. display( self . db( ) ) ,
31283109 ) ) ;
31293110 }
31303111 }
3131-
3132- successful_call
3112+ false
31333113 } else {
3134- ensure_assignable_to ( meta_attr_ty)
3135- } ;
3114+ let assignable_to_meta_attr = if let Symbol :: Type ( meta_dunder_set, _) =
3115+ meta_attr_ty. class_member ( db, "__set__" . into ( ) ) . symbol
3116+ {
3117+ let successful_call = meta_dunder_set
3118+ . try_call (
3119+ db,
3120+ & CallArgumentTypes :: positional ( [
3121+ meta_attr_ty,
3122+ object_ty,
3123+ value_ty,
3124+ ] ) ,
3125+ )
3126+ . is_ok ( ) ;
31363127
3137- let assignable_to_instance_attribute = if meta_attr_boundness
3138- == Boundness :: PossiblyUnbound
3139- {
3140- let ( assignable, boundness) =
3141- if let Symbol :: Type ( instance_attr_ty, instance_attr_boundness) =
3142- object_ty. instance_member ( db, attribute) . symbol
3143- {
3144- (
3145- ensure_assignable_to ( instance_attr_ty) ,
3128+ if !successful_call && emit_diagnostics {
3129+ if let Some ( builder) =
3130+ self . context . report_lint ( & INVALID_ASSIGNMENT , target)
3131+ {
3132+ // TODO: Here, it would be nice to emit an additional diagnostic that explains why the call failed
3133+ builder. into_diagnostic ( format_args ! (
3134+ "Invalid assignment to data descriptor attribute \
3135+ `{attribute}` on type `{}` with custom `__set__` method",
3136+ object_ty. display( db)
3137+ ) ) ;
3138+ }
3139+ }
3140+
3141+ successful_call
3142+ } else {
3143+ ensure_assignable_to ( meta_attr_ty)
3144+ } ;
3145+
3146+ let assignable_to_instance_attribute =
3147+ if meta_attr_boundness == Boundness :: PossiblyUnbound {
3148+ let ( assignable, boundness) = if let Symbol :: Type (
3149+ instance_attr_ty,
31463150 instance_attr_boundness,
3147- )
3148- } else {
3149- ( true , Boundness :: PossiblyUnbound )
3150- } ;
3151+ ) =
3152+ object_ty. instance_member ( db, attribute) . symbol
3153+ {
3154+ (
3155+ ensure_assignable_to ( instance_attr_ty) ,
3156+ instance_attr_boundness,
3157+ )
3158+ } else {
3159+ ( true , Boundness :: PossiblyUnbound )
3160+ } ;
31513161
3152- if boundness == Boundness :: PossiblyUnbound {
3153- report_possibly_unbound_attribute (
3154- & self . context ,
3155- target,
3156- attribute,
3157- object_ty,
3158- ) ;
3159- }
3162+ if boundness == Boundness :: PossiblyUnbound {
3163+ report_possibly_unbound_attribute (
3164+ & self . context ,
3165+ target,
3166+ attribute,
3167+ object_ty,
3168+ ) ;
3169+ }
31603170
3161- assignable
3162- } else {
3163- true
3164- } ;
3171+ assignable
3172+ } else {
3173+ true
3174+ } ;
31653175
3166- assignable_to_meta_attr && assignable_to_instance_attribute
3176+ assignable_to_meta_attr && assignable_to_instance_attribute
3177+ }
31673178 }
31683179
31693180 SymbolAndQualifiers {
@@ -3182,7 +3193,22 @@ impl<'db> TypeInferenceBuilder<'db> {
31823193 ) ;
31833194 }
31843195
3185- ensure_assignable_to ( instance_attr_ty)
3196+ if read_only {
3197+ // TODO(thejchap): illustrating missing diagnostic
3198+ // if emit_diagnostics {
3199+ // if let Some(builder) =
3200+ // self.context.report_lint(&INVALID_ASSIGNMENT, target)
3201+ // {
3202+ // builder.into_diagnostic(format_args!(
3203+ // "Property `{attribute}` defined in `{ty}` is read-only",
3204+ // ty = object_ty.display(self.db()),
3205+ // ));
3206+ // }
3207+ // }
3208+ false
3209+ } else {
3210+ ensure_assignable_to ( instance_attr_ty)
3211+ }
31863212 } else {
31873213 let result = object_ty. try_call_dunder_with_policy (
31883214 db,
0 commit comments