@@ -3446,20 +3446,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
3446
3446
| Type :: AlwaysTruthy
3447
3447
| Type :: AlwaysFalsy
3448
3448
| Type :: TypeIs ( _) => {
3449
- let is_read_only = || {
3450
- let dataclass_params = match object_ty {
3451
- Type :: NominalInstance ( instance) => match instance. class {
3452
- ClassType :: NonGeneric ( cls) => cls. dataclass_params ( self . db ( ) ) ,
3453
- ClassType :: Generic ( cls) => {
3454
- cls. origin ( self . db ( ) ) . dataclass_params ( self . db ( ) )
3455
- }
3456
- } ,
3457
- _ => None ,
3458
- } ;
3459
-
3460
- dataclass_params. is_some_and ( |params| params. contains ( DataclassParams :: FROZEN ) )
3461
- } ;
3462
-
3463
3449
// First, try to call the `__setattr__` dunder method. If this is present/defined, overrides
3464
3450
// assigning the attributed by the normal mechanism.
3465
3451
let setattr_dunder_call_result = object_ty. try_call_dunder_with_policy (
@@ -3476,11 +3462,41 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
3476
3462
if let Some ( builder) =
3477
3463
self . context . report_lint ( & INVALID_ASSIGNMENT , target)
3478
3464
{
3479
- builder. into_diagnostic ( format_args ! (
3480
- "Cannot assign to attribute `{attribute}` on type `{}` \
3481
- whose `__setattr__` method returns `Never`/`NoReturn`",
3482
- object_ty. display( db)
3483
- ) ) ;
3465
+ let is_setattr_synthesized = match object_ty
3466
+ . class_member_with_policy (
3467
+ db,
3468
+ "__setattr__" . into ( ) ,
3469
+ MemberLookupPolicy :: MRO_NO_OBJECT_FALLBACK ,
3470
+ ) {
3471
+ PlaceAndQualifiers {
3472
+ place : Place :: Type ( attr_ty, _) ,
3473
+ qualifiers : _,
3474
+ } => attr_ty. is_callable_type ( ) ,
3475
+ _ => false ,
3476
+ } ;
3477
+
3478
+ let member_exists =
3479
+ !object_ty. member ( db, attribute) . place . is_unbound ( ) ;
3480
+
3481
+ let msg = if !member_exists {
3482
+ format ! (
3483
+ "Can not assign to unresolved attribute `{attribute}` on type `{}`" ,
3484
+ object_ty. display( db)
3485
+ )
3486
+ } else if is_setattr_synthesized {
3487
+ format ! (
3488
+ "Property `{attribute}` defined in `{}` is read-only" ,
3489
+ object_ty. display( db)
3490
+ )
3491
+ } else {
3492
+ format ! (
3493
+ "Cannot assign to attribute `{attribute}` on type `{}` \
3494
+ whose `__setattr__` method returns `Never`/`NoReturn`",
3495
+ object_ty. display( db)
3496
+ )
3497
+ } ;
3498
+
3499
+ builder. into_diagnostic ( msg) ;
3484
3500
}
3485
3501
}
3486
3502
false
@@ -3530,85 +3546,71 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
3530
3546
place : Place :: Type ( meta_attr_ty, meta_attr_boundness) ,
3531
3547
qualifiers : _,
3532
3548
} => {
3533
- if is_read_only ( ) {
3534
- if emit_diagnostics {
3535
- if let Some ( builder) =
3536
- self . context . report_lint ( & INVALID_ASSIGNMENT , target)
3537
- {
3538
- builder. into_diagnostic ( format_args ! (
3539
- "Property `{attribute}` defined in `{ty}` is read-only" ,
3540
- ty = object_ty. display( self . db( ) ) ,
3541
- ) ) ;
3542
- }
3543
- }
3544
- false
3545
- } else {
3546
- let assignable_to_meta_attr =
3547
- if let Place :: Type ( meta_dunder_set, _) =
3548
- meta_attr_ty. class_member ( db, "__set__" . into ( ) ) . place
3549
- {
3550
- let successful_call = meta_dunder_set
3551
- . try_call (
3552
- db,
3553
- & CallArguments :: positional ( [
3554
- meta_attr_ty,
3555
- object_ty,
3556
- value_ty,
3557
- ] ) ,
3558
- )
3559
- . is_ok ( ) ;
3560
-
3561
- if !successful_call && emit_diagnostics {
3562
- if let Some ( builder) = self
3563
- . context
3564
- . report_lint ( & INVALID_ASSIGNMENT , target)
3565
- {
3566
- // TODO: Here, it would be nice to emit an additional diagnostic that explains why the call failed
3567
- builder. into_diagnostic ( format_args ! (
3549
+ let assignable_to_meta_attr =
3550
+ if let Place :: Type ( meta_dunder_set, _) =
3551
+ meta_attr_ty. class_member ( db, "__set__" . into ( ) ) . place
3552
+ {
3553
+ let successful_call = meta_dunder_set
3554
+ . try_call (
3555
+ db,
3556
+ & CallArguments :: positional ( [
3557
+ meta_attr_ty,
3558
+ object_ty,
3559
+ value_ty,
3560
+ ] ) ,
3561
+ )
3562
+ . is_ok ( ) ;
3563
+
3564
+ if !successful_call && emit_diagnostics {
3565
+ if let Some ( builder) = self
3566
+ . context
3567
+ . report_lint ( & INVALID_ASSIGNMENT , target)
3568
+ {
3569
+ // TODO: Here, it would be nice to emit an additional diagnostic that explains why the call failed
3570
+ builder. into_diagnostic ( format_args ! (
3568
3571
"Invalid assignment to data descriptor attribute \
3569
3572
`{attribute}` on type `{}` with custom `__set__` method",
3570
3573
object_ty. display( db)
3571
3574
) ) ;
3572
- }
3573
3575
}
3576
+ }
3574
3577
3575
- successful_call
3578
+ successful_call
3579
+ } else {
3580
+ ensure_assignable_to ( meta_attr_ty)
3581
+ } ;
3582
+
3583
+ let assignable_to_instance_attribute =
3584
+ if meta_attr_boundness == Boundness :: PossiblyUnbound {
3585
+ let ( assignable, boundness) = if let Place :: Type (
3586
+ instance_attr_ty,
3587
+ instance_attr_boundness,
3588
+ ) =
3589
+ object_ty. instance_member ( db, attribute) . place
3590
+ {
3591
+ (
3592
+ ensure_assignable_to ( instance_attr_ty) ,
3593
+ instance_attr_boundness,
3594
+ )
3576
3595
} else {
3577
- ensure_assignable_to ( meta_attr_ty )
3596
+ ( true , Boundness :: PossiblyUnbound )
3578
3597
} ;
3579
3598
3580
- let assignable_to_instance_attribute =
3581
- if meta_attr_boundness == Boundness :: PossiblyUnbound {
3582
- let ( assignable, boundness) = if let Place :: Type (
3583
- instance_attr_ty,
3584
- instance_attr_boundness,
3585
- ) =
3586
- object_ty. instance_member ( db, attribute) . place
3587
- {
3588
- (
3589
- ensure_assignable_to ( instance_attr_ty) ,
3590
- instance_attr_boundness,
3591
- )
3592
- } else {
3593
- ( true , Boundness :: PossiblyUnbound )
3594
- } ;
3595
-
3596
- if boundness == Boundness :: PossiblyUnbound {
3597
- report_possibly_unbound_attribute (
3598
- & self . context ,
3599
- target,
3600
- attribute,
3601
- object_ty,
3602
- ) ;
3603
- }
3599
+ if boundness == Boundness :: PossiblyUnbound {
3600
+ report_possibly_unbound_attribute (
3601
+ & self . context ,
3602
+ target,
3603
+ attribute,
3604
+ object_ty,
3605
+ ) ;
3606
+ }
3604
3607
3605
- assignable
3606
- } else {
3607
- true
3608
- } ;
3608
+ assignable
3609
+ } else {
3610
+ true
3611
+ } ;
3609
3612
3610
- assignable_to_meta_attr && assignable_to_instance_attribute
3611
- }
3613
+ assignable_to_meta_attr && assignable_to_instance_attribute
3612
3614
}
3613
3615
3614
3616
PlaceAndQualifiers {
@@ -3627,22 +3629,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
3627
3629
) ;
3628
3630
}
3629
3631
3630
- if is_read_only ( ) {
3631
- if emit_diagnostics {
3632
- if let Some ( builder) = self
3633
- . context
3634
- . report_lint ( & INVALID_ASSIGNMENT , target)
3635
- {
3636
- builder. into_diagnostic ( format_args ! (
3637
- "Property `{attribute}` defined in `{ty}` is read-only" ,
3638
- ty = object_ty. display( self . db( ) ) ,
3639
- ) ) ;
3640
- }
3641
- }
3642
- false
3643
- } else {
3644
- ensure_assignable_to ( instance_attr_ty)
3645
- }
3632
+ ensure_assignable_to ( instance_attr_ty)
3646
3633
} else {
3647
3634
if emit_diagnostics {
3648
3635
if let Some ( builder) =
0 commit comments