Skip to content

Commit 0b60dd9

Browse files
jnm2JohanLarsson
authored andcommitted
Fix false detection of backing field mutation
1 parent 7597af5 commit 0b60dd9

3 files changed

Lines changed: 104 additions & 10 deletions

File tree

PropertyChangedAnalyzers.Test/INPC002MutablePublicPropertyShouldNotify/Valid.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,5 +1018,56 @@ protected virtual void OnPropertyChanged(string propertyName)
10181018

10191019
RoslynAssert.Valid(Analyzer, Descriptor, code);
10201020
}
1021+
1022+
[Test]
1023+
public static void SettingPropertyOfAnUnrelatedInstance()
1024+
{
1025+
var code = @"
1026+
namespace ValidCode
1027+
{
1028+
using System.ComponentModel;
1029+
using System.Runtime.CompilerServices;
1030+
1031+
internal class Settings
1032+
{
1033+
public static Settings Default { get; }
1034+
1035+
public string Address { get; set; }
1036+
}
1037+
1038+
public class SomeViewModel : INotifyPropertyChanged
1039+
{
1040+
private string address;
1041+
1042+
public event PropertyChangedEventHandler? PropertyChanged;
1043+
1044+
public string Address
1045+
{
1046+
get => this.address;
1047+
set
1048+
{
1049+
if (!Set(ref this.address, value)) return;
1050+
1051+
Settings.Default.Address = value;
1052+
}
1053+
}
1054+
1055+
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
1056+
{
1057+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
1058+
}
1059+
1060+
protected bool Set<T>(ref T location, T value, [CallerMemberName] string propertyName = null)
1061+
{
1062+
if (RuntimeHelpers.Equals(location, value)) return false;
1063+
location = value;
1064+
OnPropertyChanged(propertyName);
1065+
return true;
1066+
}
1067+
}
1068+
}";
1069+
1070+
RoslynAssert.Valid(Analyzer, code);
1071+
}
10211072
}
10221073
}

PropertyChangedAnalyzers/Helpers/Setter.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,20 +110,19 @@ IdentifierNameSyntax identifierName
110110

111111
internal static AssignmentExpressionSyntax? AssignsValueToBackingField(AccessorDeclarationSyntax setter)
112112
{
113+
if (setter.FirstAncestor<PropertyDeclarationSyntax>() is not { } property || !property.TrySingleReturned(out var backingExpression))
114+
{
115+
return null;
116+
}
117+
113118
using var walker = AssignmentWalker.Borrow(setter);
119+
114120
foreach (var assignment in walker.Assignments)
115121
{
116-
if (assignment is { Right: IdentifierNameSyntax { Identifier: { ValueText: "value" } } })
122+
if (assignment is { Right: IdentifierNameSyntax { Identifier: { ValueText: "value" } } } &&
123+
MemberPath.Equals(backingExpression, assignment.Left))
117124
{
118-
switch (assignment.Left)
119-
{
120-
case IdentifierNameSyntax _:
121-
case MemberAccessExpressionSyntax { Expression: ThisExpressionSyntax _ }:
122-
case MemberAccessExpressionSyntax { Expression: IdentifierNameSyntax _ }:
123-
case MemberAccessExpressionSyntax { Expression: MemberAccessExpressionSyntax { Expression: IdentifierNameSyntax _ } }:
124-
case MemberAccessExpressionSyntax { Expression: MemberAccessExpressionSyntax { Expression: ThisExpressionSyntax _ } }:
125-
return assignment;
126-
}
125+
return assignment;
127126
}
128127
}
129128

ValidCode/NotMutations.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
namespace ValidCode
2+
{
3+
using System.ComponentModel;
4+
using System.Runtime.CompilerServices;
5+
6+
public class NotMutations : INotifyPropertyChanged
7+
{
8+
public event PropertyChangedEventHandler? PropertyChanged;
9+
10+
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
11+
{
12+
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
13+
}
14+
15+
protected bool Set<T>(ref T location, T value, [CallerMemberName] string? propertyName = null)
16+
{
17+
if (RuntimeHelpers.Equals(location, value)) return false;
18+
location = value;
19+
OnPropertyChanged(propertyName);
20+
return true;
21+
}
22+
23+
#pragma warning disable INPC001
24+
internal class Settings
25+
#pragma warning restore INPC001
26+
{
27+
public static Settings Default { get; } = new();
28+
29+
public int SamePropertyNameOnUnrelatedInstance { get; set; }
30+
}
31+
32+
private int samePropertyNameOnUnrelatedInstance;
33+
public int SamePropertyNameOnUnrelatedInstance
34+
{
35+
get => this.samePropertyNameOnUnrelatedInstance;
36+
set
37+
{
38+
if (!Set(ref samePropertyNameOnUnrelatedInstance, value)) return;
39+
40+
Settings.Default.SamePropertyNameOnUnrelatedInstance = value;
41+
}
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)