@@ -3115,87 +3115,162 @@ impl<'db> TypeInferenceBuilder<'db> {
31153115 dataclass_params. is_some_and ( |params| params. contains ( DataclassParams :: FROZEN ) )
31163116 } ;
31173117
3118- match object_ty. class_member ( db, attribute. into ( ) ) {
3119- meta_attr @ SymbolAndQualifiers { .. } if meta_attr. is_class_var ( ) => {
3118+ // First, try to call the `__setattr__` dunder method. If this is present/defined, overrides
3119+ // assigning the attributed by the normal mechanism.
3120+ let setattr_dunder_call_result = object_ty. try_call_dunder_with_policy (
3121+ db,
3122+ "__setattr__" ,
3123+ & mut CallArgumentTypes :: positional ( [
3124+ Type :: StringLiteral ( StringLiteralType :: new ( db, Box :: from ( attribute) ) ) ,
3125+ value_ty,
3126+ ] ) ,
3127+ MemberLookupPolicy :: MRO_NO_OBJECT_FALLBACK ,
3128+ ) ;
3129+
3130+ match setattr_dunder_call_result {
3131+ Ok ( result) => match result. return_type ( db) {
3132+ Type :: Never => {
3133+ if emit_diagnostics {
3134+ if let Some ( builder) =
3135+ self . context . report_lint ( & INVALID_ASSIGNMENT , target)
3136+ {
3137+ builder. into_diagnostic ( format_args ! (
3138+ "Cannot assign to attribute `{attribute}` on type `{}` \
3139+ via `__setattr__` that returns `Never`",
3140+ object_ty. display( db)
3141+ ) ) ;
3142+ }
3143+ }
3144+ false
3145+ }
3146+ _ => true ,
3147+ } ,
3148+ Err ( CallDunderError :: CallError ( ..) ) => {
31203149 if emit_diagnostics {
31213150 if let Some ( builder) =
3122- self . context . report_lint ( & INVALID_ATTRIBUTE_ACCESS , target)
3151+ self . context . report_lint ( & UNRESOLVED_ATTRIBUTE , target)
31233152 {
31243153 builder. into_diagnostic ( format_args ! (
3125- "Cannot assign to ClassVar `{attribute}` \
3126- from an instance of type `{ty}`",
3127- ty = object_ty. display( self . db( ) ) ,
3154+ "Can not assign object of `{}` to attribute \
3155+ `{attribute}` on type `{}` with \
3156+ custom `__setattr__` method.",
3157+ value_ty. display( db) ,
3158+ object_ty. display( db)
31283159 ) ) ;
31293160 }
31303161 }
31313162 false
31323163 }
3133- SymbolAndQualifiers {
3134- symbol : Symbol :: Type ( meta_attr_ty, meta_attr_boundness) ,
3135- qualifiers : _,
3136- } => {
3137- if is_read_only ( ) {
3138- if emit_diagnostics {
3139- if let Some ( builder) =
3140- self . context . report_lint ( & INVALID_ASSIGNMENT , target)
3141- {
3142- builder. into_diagnostic ( format_args ! (
3164+ Err ( CallDunderError :: PossiblyUnbound ( _) ) => true ,
3165+ Err ( CallDunderError :: MethodNotAvailable ) => {
3166+ match object_ty. class_member ( db, attribute. into ( ) ) {
3167+ meta_attr @ SymbolAndQualifiers { .. } if meta_attr. is_class_var ( ) => {
3168+ if emit_diagnostics {
3169+ if let Some ( builder) =
3170+ self . context . report_lint ( & INVALID_ATTRIBUTE_ACCESS , target)
3171+ {
3172+ builder. into_diagnostic ( format_args ! (
3173+ "Cannot assign to ClassVar `{attribute}` \
3174+ from an instance of type `{ty}`",
3175+ ty = object_ty. display( self . db( ) ) ,
3176+ ) ) ;
3177+ }
3178+ }
3179+ false
3180+ }
3181+ SymbolAndQualifiers {
3182+ symbol : Symbol :: Type ( meta_attr_ty, meta_attr_boundness) ,
3183+ qualifiers : _,
3184+ } => {
3185+ if is_read_only ( ) {
3186+ if emit_diagnostics {
3187+ if let Some ( builder) =
3188+ self . context . report_lint ( & INVALID_ASSIGNMENT , target)
3189+ {
3190+ builder. into_diagnostic ( format_args ! (
31433191 "Property `{attribute}` defined in `{ty}` is read-only" ,
31443192 ty = object_ty. display( self . db( ) ) ,
31453193 ) ) ;
3146- }
3147- }
3148- false
3149- } else {
3150- let assignable_to_meta_attr = if let Symbol :: Type ( meta_dunder_set, _) =
3151- meta_attr_ty. class_member ( db, "__set__" . into ( ) ) . symbol
3152- {
3153- let successful_call = meta_dunder_set
3154- . try_call (
3155- db,
3156- & CallArgumentTypes :: positional ( [
3157- meta_attr_ty,
3158- object_ty,
3159- value_ty,
3160- ] ) ,
3161- )
3162- . is_ok ( ) ;
3194+ }
3195+ }
3196+ false
3197+ } else {
3198+ let assignable_to_meta_attr =
3199+ if let Symbol :: Type ( meta_dunder_set, _) =
3200+ meta_attr_ty. class_member ( db, "__set__" . into ( ) ) . symbol
3201+ {
3202+ let successful_call = meta_dunder_set
3203+ . try_call (
3204+ db,
3205+ & CallArgumentTypes :: positional ( [
3206+ meta_attr_ty,
3207+ object_ty,
3208+ value_ty,
3209+ ] ) ,
3210+ )
3211+ . is_ok ( ) ;
31633212
3164- if !successful_call && emit_diagnostics {
3165- if let Some ( builder) =
3166- self . context . report_lint ( & INVALID_ASSIGNMENT , target)
3167- {
3168- // TODO: Here, it would be nice to emit an additional diagnostic that explains why the call failed
3169- builder. into_diagnostic ( format_args ! (
3213+ if !successful_call && emit_diagnostics {
3214+ if let Some ( builder) = self
3215+ . context
3216+ . report_lint ( & INVALID_ASSIGNMENT , target)
3217+ {
3218+ // TODO: Here, it would be nice to emit an additional diagnostic that explains why the call failed
3219+ builder. into_diagnostic ( format_args ! (
31703220 "Invalid assignment to data descriptor attribute \
31713221 `{attribute}` on type `{}` with custom `__set__` method",
31723222 object_ty. display( db)
31733223 ) ) ;
3174- }
3175- }
3224+ }
3225+ }
31763226
3177- successful_call
3178- } else {
3179- ensure_assignable_to ( meta_attr_ty)
3180- } ;
3227+ successful_call
3228+ } else {
3229+ ensure_assignable_to ( meta_attr_ty)
3230+ } ;
31813231
3182- let assignable_to_instance_attribute =
3183- if meta_attr_boundness == Boundness :: PossiblyUnbound {
3184- let ( assignable, boundness) = if let Symbol :: Type (
3185- instance_attr_ty,
3186- instance_attr_boundness,
3187- ) =
3188- object_ty. instance_member ( db, attribute) . symbol
3189- {
3190- (
3191- ensure_assignable_to ( instance_attr_ty) ,
3192- instance_attr_boundness,
3193- )
3194- } else {
3195- ( true , Boundness :: PossiblyUnbound )
3196- } ;
3232+ let assignable_to_instance_attribute =
3233+ if meta_attr_boundness == Boundness :: PossiblyUnbound {
3234+ let ( assignable, boundness) = if let Symbol :: Type (
3235+ instance_attr_ty,
3236+ instance_attr_boundness,
3237+ ) =
3238+ object_ty. instance_member ( db, attribute) . symbol
3239+ {
3240+ (
3241+ ensure_assignable_to ( instance_attr_ty) ,
3242+ instance_attr_boundness,
3243+ )
3244+ } else {
3245+ ( true , Boundness :: PossiblyUnbound )
3246+ } ;
31973247
3198- if boundness == Boundness :: PossiblyUnbound {
3248+ if boundness == Boundness :: PossiblyUnbound {
3249+ report_possibly_unbound_attribute (
3250+ & self . context ,
3251+ target,
3252+ attribute,
3253+ object_ty,
3254+ ) ;
3255+ }
3256+
3257+ assignable
3258+ } else {
3259+ true
3260+ } ;
3261+
3262+ assignable_to_meta_attr && assignable_to_instance_attribute
3263+ }
3264+ }
3265+
3266+ SymbolAndQualifiers {
3267+ symbol : Symbol :: Unbound ,
3268+ ..
3269+ } => {
3270+ if let Symbol :: Type ( instance_attr_ty, instance_attr_boundness) =
3271+ object_ty. instance_member ( db, attribute) . symbol
3272+ {
3273+ if instance_attr_boundness == Boundness :: PossiblyUnbound {
31993274 report_possibly_unbound_attribute (
32003275 & self . context ,
32013276 target,
@@ -3204,79 +3279,23 @@ impl<'db> TypeInferenceBuilder<'db> {
32043279 ) ;
32053280 }
32063281
3207- assignable
3208- } else {
3209- true
3210- } ;
3211-
3212- assignable_to_meta_attr && assignable_to_instance_attribute
3213- }
3214- }
3215-
3216- SymbolAndQualifiers {
3217- symbol : Symbol :: Unbound ,
3218- ..
3219- } => {
3220- if let Symbol :: Type ( instance_attr_ty, instance_attr_boundness) =
3221- object_ty. instance_member ( db, attribute) . symbol
3222- {
3223- if instance_attr_boundness == Boundness :: PossiblyUnbound {
3224- report_possibly_unbound_attribute (
3225- & self . context ,
3226- target,
3227- attribute,
3228- object_ty,
3229- ) ;
3230- }
3231-
3232- if is_read_only ( ) {
3233- if emit_diagnostics {
3234- if let Some ( builder) =
3235- self . context . report_lint ( & INVALID_ASSIGNMENT , target)
3236- {
3237- builder. into_diagnostic ( format_args ! (
3282+ if is_read_only ( ) {
3283+ if emit_diagnostics {
3284+ if let Some ( builder) = self
3285+ . context
3286+ . report_lint ( & INVALID_ASSIGNMENT , target)
3287+ {
3288+ builder. into_diagnostic ( format_args ! (
32383289 "Property `{attribute}` defined in `{ty}` is read-only" ,
32393290 ty = object_ty. display( self . db( ) ) ,
32403291 ) ) ;
3241- }
3242- }
3243- false
3244- } else {
3245- ensure_assignable_to ( instance_attr_ty)
3246- }
3247- } else {
3248- let result = object_ty. try_call_dunder_with_policy (
3249- db,
3250- "__setattr__" ,
3251- & mut CallArgumentTypes :: positional ( [
3252- Type :: StringLiteral ( StringLiteralType :: new (
3253- db,
3254- Box :: from ( attribute) ,
3255- ) ) ,
3256- value_ty,
3257- ] ) ,
3258- MemberLookupPolicy :: MRO_NO_OBJECT_FALLBACK ,
3259- ) ;
3260-
3261- match result {
3262- Ok ( _) | Err ( CallDunderError :: PossiblyUnbound ( _) ) => true ,
3263- Err ( CallDunderError :: CallError ( ..) ) => {
3264- if emit_diagnostics {
3265- if let Some ( builder) =
3266- self . context . report_lint ( & UNRESOLVED_ATTRIBUTE , target)
3267- {
3268- builder. into_diagnostic ( format_args ! (
3269- "Can not assign object of `{}` to attribute \
3270- `{attribute}` on type `{}` with \
3271- custom `__setattr__` method.",
3272- value_ty. display( db) ,
3273- object_ty. display( db)
3274- ) ) ;
3292+ }
32753293 }
3294+ false
3295+ } else {
3296+ ensure_assignable_to ( instance_attr_ty)
32763297 }
3277- false
3278- }
3279- Err ( CallDunderError :: MethodNotAvailable ) => {
3298+ } else {
32803299 if emit_diagnostics {
32813300 if let Some ( builder) =
32823301 self . context . report_lint ( & UNRESOLVED_ATTRIBUTE , target)
0 commit comments