@@ -3113,87 +3113,163 @@ 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 ( kind, _) ) => {
3147+ println ! ( "{kind:?} {object_ty:?} {attribute:?} {value_ty:?}" ) ;
31183148 if emit_diagnostics {
31193149 if let Some ( builder) =
3120- self . context . report_lint ( & INVALID_ATTRIBUTE_ACCESS , target)
3150+ self . context . report_lint ( & UNRESOLVED_ATTRIBUTE , target)
31213151 {
31223152 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( ) ) ,
3153+ "Can not assign object of `{}` to attribute \
3154+ `{attribute}` on type `{}` with \
3155+ custom `__setattr__` method.",
3156+ value_ty. display( db) ,
3157+ object_ty. display( db)
31263158 ) ) ;
31273159 }
31283160 }
31293161 false
31303162 }
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 ! (
3163+ Err ( CallDunderError :: PossiblyUnbound ( _) ) => true ,
3164+ Err ( CallDunderError :: MethodNotAvailable ) => {
3165+ match object_ty. class_member ( db, attribute. into ( ) ) {
3166+ meta_attr @ SymbolAndQualifiers { .. } if meta_attr. is_class_var ( ) => {
3167+ if emit_diagnostics {
3168+ if let Some ( builder) =
3169+ self . context . report_lint ( & INVALID_ATTRIBUTE_ACCESS , target)
3170+ {
3171+ builder. into_diagnostic ( format_args ! (
3172+ "Cannot assign to ClassVar `{attribute}` \
3173+ from an instance of type `{ty}`",
3174+ ty = object_ty. display( self . db( ) ) ,
3175+ ) ) ;
3176+ }
3177+ }
3178+ false
3179+ }
3180+ SymbolAndQualifiers {
3181+ symbol : Symbol :: Type ( meta_attr_ty, meta_attr_boundness) ,
3182+ qualifiers : _,
3183+ } => {
3184+ if is_read_only ( ) {
3185+ if emit_diagnostics {
3186+ if let Some ( builder) =
3187+ self . context . report_lint ( & INVALID_ASSIGNMENT , target)
3188+ {
3189+ builder. into_diagnostic ( format_args ! (
31413190 "Property `{attribute}` defined in `{ty}` is read-only" ,
31423191 ty = object_ty. display( self . db( ) ) ,
31433192 ) ) ;
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 ( ) ;
3193+ }
3194+ }
3195+ false
3196+ } else {
3197+ let assignable_to_meta_attr =
3198+ if let Symbol :: Type ( meta_dunder_set, _) =
3199+ meta_attr_ty. class_member ( db, "__set__" . into ( ) ) . symbol
3200+ {
3201+ let successful_call = meta_dunder_set
3202+ . try_call (
3203+ db,
3204+ & CallArgumentTypes :: positional ( [
3205+ meta_attr_ty,
3206+ object_ty,
3207+ value_ty,
3208+ ] ) ,
3209+ )
3210+ . is_ok ( ) ;
31613211
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 ! (
3212+ if !successful_call && emit_diagnostics {
3213+ if let Some ( builder) = self
3214+ . context
3215+ . report_lint ( & INVALID_ASSIGNMENT , target)
3216+ {
3217+ // TODO: Here, it would be nice to emit an additional diagnostic that explains why the call failed
3218+ builder. into_diagnostic ( format_args ! (
31683219 "Invalid assignment to data descriptor attribute \
31693220 `{attribute}` on type `{}` with custom `__set__` method",
31703221 object_ty. display( db)
31713222 ) ) ;
3172- }
3173- }
3223+ }
3224+ }
31743225
3175- successful_call
3176- } else {
3177- ensure_assignable_to ( meta_attr_ty)
3178- } ;
3226+ successful_call
3227+ } else {
3228+ ensure_assignable_to ( meta_attr_ty)
3229+ } ;
31793230
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- } ;
3231+ let assignable_to_instance_attribute =
3232+ if meta_attr_boundness == Boundness :: PossiblyUnbound {
3233+ let ( assignable, boundness) = if let Symbol :: Type (
3234+ instance_attr_ty,
3235+ instance_attr_boundness,
3236+ ) =
3237+ object_ty. instance_member ( db, attribute) . symbol
3238+ {
3239+ (
3240+ ensure_assignable_to ( instance_attr_ty) ,
3241+ instance_attr_boundness,
3242+ )
3243+ } else {
3244+ ( true , Boundness :: PossiblyUnbound )
3245+ } ;
31953246
3196- if boundness == Boundness :: PossiblyUnbound {
3247+ if boundness == Boundness :: PossiblyUnbound {
3248+ report_possibly_unbound_attribute (
3249+ & self . context ,
3250+ target,
3251+ attribute,
3252+ object_ty,
3253+ ) ;
3254+ }
3255+
3256+ assignable
3257+ } else {
3258+ true
3259+ } ;
3260+
3261+ assignable_to_meta_attr && assignable_to_instance_attribute
3262+ }
3263+ }
3264+
3265+ SymbolAndQualifiers {
3266+ symbol : Symbol :: Unbound ,
3267+ ..
3268+ } => {
3269+ if let Symbol :: Type ( instance_attr_ty, instance_attr_boundness) =
3270+ object_ty. instance_member ( db, attribute) . symbol
3271+ {
3272+ if instance_attr_boundness == Boundness :: PossiblyUnbound {
31973273 report_possibly_unbound_attribute (
31983274 & self . context ,
31993275 target,
@@ -3202,79 +3278,23 @@ impl<'db> TypeInferenceBuilder<'db> {
32023278 ) ;
32033279 }
32043280
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 ! (
3281+ if is_read_only ( ) {
3282+ if emit_diagnostics {
3283+ if let Some ( builder) = self
3284+ . context
3285+ . report_lint ( & INVALID_ASSIGNMENT , target)
3286+ {
3287+ builder. into_diagnostic ( format_args ! (
32363288 "Property `{attribute}` defined in `{ty}` is read-only" ,
32373289 ty = object_ty. display( self . db( ) ) ,
32383290 ) ) ;
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- ) ) ;
3291+ }
32733292 }
3293+ false
3294+ } else {
3295+ ensure_assignable_to ( instance_attr_ty)
32743296 }
3275- false
3276- }
3277- Err ( CallDunderError :: MethodNotAvailable ) => {
3297+ } else {
32783298 if emit_diagnostics {
32793299 if let Some ( builder) =
32803300 self . context . report_lint ( & UNRESOLVED_ATTRIBUTE , target)
0 commit comments