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