From d355191f28097b377b1855a7037615268cab85f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Fri, 3 Jan 2025 22:41:14 +0100 Subject: [PATCH 1/8] Add support for init, ref return --- .../ReactiveGeneratorTests.cs | 132 ++++++++++++++++++ ReactiveGenerator/ReactiveGenerator.cs | 110 ++++++++++----- 2 files changed, 209 insertions(+), 33 deletions(-) diff --git a/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs b/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs index 205874f..33665c5 100644 --- a/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs +++ b/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs @@ -1562,4 +1562,136 @@ public partial class TestClass return TestAndVerify(source); } + + [Fact] + public Task InitAccessorTest() + { + var source = @" + [Reactive] + public partial class Person + { + public partial string Name { get; init; } + public required partial string Id { get; init; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task RefReturnTest() + { + var source = @" + [Reactive] + public partial class Container + { + private int _value; + + public ref partial int RefValue { get; } + public ref readonly partial int ReadOnlyRefValue { get; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task ReadOnlyPropertyTest() + { + var source = @" + [Reactive] + public partial class TestClass + { + public partial string ReadOnlyProp { get; } + public readonly partial string ExplicitReadOnlyProp { get; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task MixedFeaturesTest() + { + var source = @" + [Reactive] + public partial class AdvancedClass + { + // Init with required + public required partial string Id { get; init; } + + // Ref return with static + public static ref partial int Counter { get; } + + // Init with virtual + public virtual partial string Name { get; init; } + + // Readonly with override + public override partial string ToString { get; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task RefPropertyTest() + { + var source = @" + [Reactive] + public partial class Container + { + // Basic ref property + public ref partial int RefValue { get; } + + // Ref readonly property + public ref readonly partial int ReadOnlyRefValue { get; } + + // Ref with modifiers + public static ref partial int StaticRefValue { get; } + + // Virtual ref property + public virtual ref partial int VirtualRefValue { get; } + + // Override ref property + public override ref partial int BaseRefValue { get; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task RefReadonlyPropertyTest() + { + var source = @" + [Reactive] + public partial class Container + { + // Basic ref readonly property + public ref readonly partial int ReadOnlyValue { get; } + + // Ref readonly with other modifiers + public static ref readonly partial int StaticReadOnlyValue { get; } + public virtual ref readonly partial int VirtualReadOnlyValue { get; } + + // Try with different types + public ref readonly partial string StringValue { get; } + public ref readonly partial System.DateTime DateValue { get; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task RefPropertyInvalidCombinationsTest() + { + var source = @" + [Reactive] + public partial class Container + { + // Ref readonly should not generate a setter + public ref readonly partial int ReadOnlyValue { get; set; } + + // Regular ref can have a setter + public ref partial int MutableValue { get; set; } + }"; + + return TestAndVerify(source); + } } diff --git a/ReactiveGenerator/ReactiveGenerator.cs b/ReactiveGenerator/ReactiveGenerator.cs index 0c2eec7..6535f8e 100644 --- a/ReactiveGenerator/ReactiveGenerator.cs +++ b/ReactiveGenerator/ReactiveGenerator.cs @@ -17,6 +17,27 @@ private record PropertyInfo( bool HasIgnoreAttribute, bool HasImplementation) { + public RefKind RefKind => GetRefKind(); + + private RefKind GetRefKind() + { + if (Property.DeclaringSyntaxReferences.Length > 0) + { + var syntax = Property.DeclaringSyntaxReferences[0].GetSyntax() as PropertyDeclarationSyntax; + if (syntax != null) + { + bool hasRef = syntax.Modifiers.Any(m => m.IsKind(SyntaxKind.RefKeyword)); + bool hasReadOnly = syntax.Modifiers.Any(m => m.IsKind(SyntaxKind.ReadOnlyKeyword)); + + if (hasRef) + { + return hasReadOnly ? RefKind.RefReadOnly : RefKind.Ref; + } + } + } + return RefKind.None; + } + public string GetPropertyModifiers() { var modifiers = new List(); @@ -681,32 +702,43 @@ private static void GenerateLegacyProperty( declarationModifiers.Add(modifiers); declarationModifiers.Add("partial"); - sb.AppendLine($"{indent}{string.Join(" ", declarationModifiers)} {propertyType} {propertyName}"); + // Add ref and readonly modifiers to declaration + var refPrefix = propInfo.RefKind switch + { + RefKind.Ref => "ref ", + RefKind.RefReadOnly => "ref readonly ", + _ => "" + }; + + sb.AppendLine($"{indent}{string.Join(" ", declarationModifiers)} {refPrefix}{propertyType} {propertyName}"); sb.AppendLine($"{indent}{{"); - if (isReactiveObject) - { - var getterModifier = getterAccessibility != propertyAccessibility ? $"{getterAccessibility} " : ""; - var setterModifier = setterAccessibility != propertyAccessibility ? $"{setterAccessibility} " : ""; + var getterModifier = getterAccessibility != propertyAccessibility ? $"{getterAccessibility} " : ""; - sb.AppendLine($"{indent} {getterModifier}get => {backingFieldName};"); - if (property.SetMethod != null) - { - sb.AppendLine( - $"{indent} {setterModifier}set => this.RaiseAndSetIfChanged(ref {backingFieldName}, value);"); - } + // Handle ref returns in getter + if (propInfo.RefKind != RefKind.None) + { + sb.AppendLine($"{indent} {getterModifier}get => ref {backingFieldName};"); } else { - var getterModifier = getterAccessibility != propertyAccessibility ? $"{getterAccessibility} " : ""; - var eventArgsFieldName = GetEventArgsFieldName(propertyName); - sb.AppendLine($"{indent} {getterModifier}get => {backingFieldName};"); + } - if (property.SetMethod != null) + // Only generate setter if not ref readonly + if (property.SetMethod != null && propInfo.RefKind != RefKind.RefReadOnly) + { + var setterModifier = setterAccessibility != propertyAccessibility ? $"{setterAccessibility} " : ""; + var setterType = property.SetMethod.IsInitOnly ? "init" : "set"; + + if (isReactiveObject) + { + sb.AppendLine($"{indent} {setterModifier}{setterType} => this.RaiseAndSetIfChanged(ref {backingFieldName}, value);"); + } + else { - var setterModifier = setterAccessibility != propertyAccessibility ? $"{setterAccessibility} " : ""; - sb.AppendLine($"{indent} {setterModifier}set"); + var eventArgsFieldName = GetEventArgsFieldName(propertyName); + sb.AppendLine($"{indent} {setterModifier}{setterType}"); sb.AppendLine($"{indent} {{"); sb.AppendLine($"{indent} if (!Equals({backingFieldName}, value))"); sb.AppendLine($"{indent} {{"); @@ -740,31 +772,43 @@ private static void GenerateFieldKeywordProperty( declarationModifiers.Add(modifiers); declarationModifiers.Add("partial"); - sb.AppendLine($"{indent}{string.Join(" ", declarationModifiers)} {propertyType} {propertyName}"); + // Add ref and readonly modifiers to declaration + var refPrefix = propInfo.RefKind switch + { + RefKind.Ref => "ref ", + RefKind.RefReadOnly => "ref readonly ", + _ => "" + }; + + sb.AppendLine($"{indent}{string.Join(" ", declarationModifiers)} {refPrefix}{propertyType} {propertyName}"); sb.AppendLine($"{indent}{{"); - if (isReactiveObject) - { - var getterModifier = getterAccessibility != propertyAccessibility ? $"{getterAccessibility} " : ""; - var setterModifier = setterAccessibility != propertyAccessibility ? $"{setterAccessibility} " : ""; + var getterModifier = getterAccessibility != propertyAccessibility ? $"{getterAccessibility} " : ""; - sb.AppendLine($"{indent} {getterModifier}get => field;"); - if (property.SetMethod != null) - { - sb.AppendLine($"{indent} {setterModifier}set => this.RaiseAndSetIfChanged(ref field, value);"); - } + // Handle ref returns in getter + if (propInfo.RefKind != RefKind.None) + { + sb.AppendLine($"{indent} {getterModifier}get => ref field;"); } else { - var getterModifier = getterAccessibility != propertyAccessibility ? $"{getterAccessibility} " : ""; - var eventArgsFieldName = GetEventArgsFieldName(propertyName); - sb.AppendLine($"{indent} {getterModifier}get => field;"); + } - if (property.SetMethod != null) + // Only generate setter if not ref readonly + if (property.SetMethod != null && propInfo.RefKind != RefKind.RefReadOnly) + { + var setterModifier = setterAccessibility != propertyAccessibility ? $"{setterAccessibility} " : ""; + var setterType = property.SetMethod.IsInitOnly ? "init" : "set"; + + if (isReactiveObject) + { + sb.AppendLine($"{indent} {setterModifier}{setterType} => this.RaiseAndSetIfChanged(ref field, value);"); + } + else { - var setterModifier = setterAccessibility != propertyAccessibility ? $"{setterAccessibility} " : ""; - sb.AppendLine($"{indent} {setterModifier}set"); + var eventArgsFieldName = GetEventArgsFieldName(propertyName); + sb.AppendLine($"{indent} {setterModifier}{setterType}"); sb.AppendLine($"{indent} {{"); sb.AppendLine($"{indent} if (!Equals(field, value))"); sb.AppendLine($"{indent} {{"); From 178e35cb384cc3444374ad11b75d577b8f47f3ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Sat, 4 Jan 2025 11:32:31 +0100 Subject: [PATCH 2/8] Update verified files --- ...ReactiveGeneratorTests.ClassWithInitOnlySetters.verified.txt | 2 +- ...iveGeneratorTests.GenericPropertyWithInitSetter.verified.txt | 2 +- ...eratorTests.NestedClassesWithInitOnlyProperties.verified.txt | 2 +- ...orTests.ReactiveObjectDerivedWithInitProperties.verified.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithInitOnlySetters.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithInitOnlySetters.verified.txt index fc636c8..e1cbde1 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithInitOnlySetters.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithInitOnlySetters.verified.txt @@ -69,7 +69,7 @@ public partial class TestClass public partial string InitOnlyProp { get => field; - set + init { if (!Equals(field, value)) { diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.GenericPropertyWithInitSetter.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.GenericPropertyWithInitSetter.verified.txt index 22697ca..df99538 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.GenericPropertyWithInitSetter.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.GenericPropertyWithInitSetter.verified.txt @@ -42,7 +42,7 @@ internal partial class GenericViewModel public partial T InitValue { get => field; - set + init { if (!Equals(field, value)) { diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithInitOnlyProperties.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithInitOnlyProperties.verified.txt index 42d8da9..7fd7c5f 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithInitOnlyProperties.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithInitOnlyProperties.verified.txt @@ -63,7 +63,7 @@ public partial class Container public partial string InitOnlyProp { get => field; - set + init { if (!Equals(field, value)) { diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithInitProperties.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithInitProperties.verified.txt index 8f7ecfb..47402ae 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithInitProperties.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ReactiveObjectDerivedWithInitProperties.verified.txt @@ -46,7 +46,7 @@ public partial class TestViewModel public partial string InitOnlyProp { get => field; - set => this.RaiseAndSetIfChanged(ref field, value); + init => this.RaiseAndSetIfChanged(ref field, value); } public partial string GetOnlyProp From dbbe2aaa3e01db4adbb235c4734ea1714552a680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Sat, 4 Jan 2025 11:32:59 +0100 Subject: [PATCH 3/8] Add init property demo --- ReactiveGeneratorDemo/MainWindow.axaml.cs | 2 +- ReactiveGeneratorDemo/ViewModels/Car.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ReactiveGeneratorDemo/MainWindow.axaml.cs b/ReactiveGeneratorDemo/MainWindow.axaml.cs index 9083455..afc271e 100644 --- a/ReactiveGeneratorDemo/MainWindow.axaml.cs +++ b/ReactiveGeneratorDemo/MainWindow.axaml.cs @@ -28,7 +28,7 @@ public MainWindow() var test = new Test { Person = person, - Car = new Car { Make = "Toyota" }, + Car = new Car { Make = "Toyota", UniqueId = "123"}, OaphViewModel = new OaphViewModel() }; diff --git a/ReactiveGeneratorDemo/ViewModels/Car.cs b/ReactiveGeneratorDemo/ViewModels/Car.cs index ff9307f..579e938 100644 --- a/ReactiveGeneratorDemo/ViewModels/Car.cs +++ b/ReactiveGeneratorDemo/ViewModels/Car.cs @@ -6,4 +6,7 @@ public partial class Car : ReactiveObject { [Reactive] public partial string? Make { get; set; } + + [Reactive] + public partial string? UniqueId { get; init; } } From d4d28fb7c04d089f87567be5caef90df05097b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Sat, 4 Jan 2025 11:49:21 +0100 Subject: [PATCH 4/8] Update ReactiveGeneratorTests.cs --- .../ReactiveGeneratorTests.cs | 100 +++++++++--------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs b/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs index 33665c5..0711b5a 100644 --- a/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs +++ b/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs @@ -1578,101 +1578,101 @@ public partial class Person } [Fact] - public Task RefReturnTest() + public Task ReadOnlyPropertyTest() { var source = @" [Reactive] - public partial class Container + public partial class TestClass { - private int _value; - - public ref partial int RefValue { get; } - public ref readonly partial int ReadOnlyRefValue { get; } + public partial string ReadOnlyProp { get; } + public readonly partial string ExplicitReadOnlyProp { get; } }"; return TestAndVerify(source); } [Fact] - public Task ReadOnlyPropertyTest() + public Task RefPropertyTest() { var source = @" [Reactive] - public partial class TestClass + public partial class Container { - public partial string ReadOnlyProp { get; } - public readonly partial string ExplicitReadOnlyProp { get; } + // Basic ref property + public partial ref int RefValue { get; } + + // Ref readonly property + public partial ref readonly int ReadOnlyRefValue { get; } + + // Ref with modifiers + public static partial ref int StaticRefValue { get; } + + // Virtual ref property + public virtual partial ref int VirtualRefValue { get; } + + // Override ref property + public override partial ref int BaseRefValue { get; } }"; return TestAndVerify(source); } [Fact] - public Task MixedFeaturesTest() + public Task RefReadonlyPropertyTest() { var source = @" [Reactive] - public partial class AdvancedClass + public partial class Container { - // Init with required - public required partial string Id { get; init; } - - // Ref return with static - public static ref partial int Counter { get; } - - // Init with virtual - public virtual partial string Name { get; init; } + // Basic ref readonly property + public partial ref readonly int ReadOnlyValue { get; } + + // Ref readonly with other modifiers + public static partial ref readonly int StaticReadOnlyValue { get; } + public virtual partial ref readonly int VirtualReadOnlyValue { get; } - // Readonly with override - public override partial string ToString { get; } + // Try with different types + public partial ref readonly string StringValue { get; } + public partial ref readonly System.DateTime DateValue { get; } }"; return TestAndVerify(source); } - + [Fact] - public Task RefPropertyTest() + public Task RefReturnTest() { var source = @" [Reactive] public partial class Container { - // Basic ref property - public ref partial int RefValue { get; } - - // Ref readonly property - public ref readonly partial int ReadOnlyRefValue { get; } - - // Ref with modifiers - public static ref partial int StaticRefValue { get; } - - // Virtual ref property - public virtual ref partial int VirtualRefValue { get; } + private int _value; - // Override ref property - public override ref partial int BaseRefValue { get; } + public partial ref int RefValue { get; } + public partial ref readonly int ReadOnlyRefValue { get; } }"; return TestAndVerify(source); } [Fact] - public Task RefReadonlyPropertyTest() + public Task MixedFeaturesTest() { var source = @" [Reactive] - public partial class Container + public partial class AdvancedClass { - // Basic ref readonly property - public ref readonly partial int ReadOnlyValue { get; } - - // Ref readonly with other modifiers - public static ref readonly partial int StaticReadOnlyValue { get; } - public virtual ref readonly partial int VirtualReadOnlyValue { get; } + // Init with required + public required partial string Id { get; init; } - // Try with different types - public ref readonly partial string StringValue { get; } - public ref readonly partial System.DateTime DateValue { get; } + // Ref return with static + public static partial ref int Counter { get; } + + // Init with virtual + public virtual partial string Name { get; init; } + + // Readonly with override + public override partial string ToString { get; } }"; return TestAndVerify(source); @@ -1686,10 +1686,10 @@ public Task RefPropertyInvalidCombinationsTest() public partial class Container { // Ref readonly should not generate a setter - public ref readonly partial int ReadOnlyValue { get; set; } + public partial ref readonly int ReadOnlyValue { get; set; } // Regular ref can have a setter - public ref partial int MutableValue { get; set; } + public partial ref int MutableValue { get; set; } }"; return TestAndVerify(source); From 25b97c2c48dc43809986d88fe473a8491dd9d5de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Sat, 4 Jan 2025 11:50:05 +0100 Subject: [PATCH 5/8] Create Advanced.cs --- ReactiveGeneratorDemo/ViewModels/Advanced.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 ReactiveGeneratorDemo/ViewModels/Advanced.cs diff --git a/ReactiveGeneratorDemo/ViewModels/Advanced.cs b/ReactiveGeneratorDemo/ViewModels/Advanced.cs new file mode 100644 index 0000000..75e5f06 --- /dev/null +++ b/ReactiveGeneratorDemo/ViewModels/Advanced.cs @@ -0,0 +1,10 @@ +namespace ReactiveGeneratorDemo.ViewModels; +/* TODO: +[Reactive] +public partial class Advanced +{ + public partial ref int RefValue { get; } + + public partial ref readonly int ReadOnlyRefValue { get; } +} +*/ From 1fcb2b84fdedd9c1db1cc2acffe6ec075a7fc985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Sat, 4 Jan 2025 11:51:09 +0100 Subject: [PATCH 6/8] Disable --- .../ReactiveGenerator/ReactiveGeneratorTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs b/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs index 0711b5a..8e978a3 100644 --- a/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs +++ b/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs @@ -1577,6 +1577,7 @@ public partial class Person return TestAndVerify(source); } + /* TODO: [Fact] public Task ReadOnlyPropertyTest() { @@ -1694,4 +1695,5 @@ public partial class Container return TestAndVerify(source); } + */ } From 21dbbee33f7c5cd43c8a9290c9c8e68a0c90b679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Sat, 4 Jan 2025 11:51:12 +0100 Subject: [PATCH 7/8] Create ReactiveGeneratorTests.InitAccessorTest.verified.txt --- ...neratorTests.InitAccessorTest.verified.txt | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.InitAccessorTest.verified.txt diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.InitAccessorTest.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.InitAccessorTest.verified.txt new file mode 100644 index 0000000..07f215f --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.InitAccessorTest.verified.txt @@ -0,0 +1,99 @@ +{ + Sources: [ + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +// +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + FileName: Person.INPC.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +public partial class Person : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) + { + PropertyChanged?.Invoke(this, args); + } +} + + }, + { + FileName: Person.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for Person. +/// +public partial class Person +{ + private static readonly PropertyChangedEventArgs _nameChangedEventArgs = new PropertyChangedEventArgs(nameof(Name)); + private static readonly PropertyChangedEventArgs _idChangedEventArgs = new PropertyChangedEventArgs(nameof(Id)); + + public partial string Name + { + get => field; + init + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_nameChangedEventArgs); + } + } + } + + public required partial string Id + { + get => field; + init + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_idChangedEventArgs); + } + } + } +} + + }, + { + FileName: ReactiveAttribute.g.cs, + Source: +// +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = true, AllowMultiple = false)] +sealed class ReactiveAttribute : Attribute +{ + public ReactiveAttribute() { } +} + } + ], + Diagnostics: null +} \ No newline at end of file From a1dfffcd815f1e1da78af4199a075e7ef4fce8f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Sat, 4 Jan 2025 12:27:07 +0100 Subject: [PATCH 8/8] Update ReactiveGenerator.cs --- ReactiveGenerator/ReactiveGenerator.cs | 53 +++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/ReactiveGenerator/ReactiveGenerator.cs b/ReactiveGenerator/ReactiveGenerator.cs index 6535f8e..e71fe19 100644 --- a/ReactiveGenerator/ReactiveGenerator.cs +++ b/ReactiveGenerator/ReactiveGenerator.cs @@ -26,9 +26,10 @@ private RefKind GetRefKind() var syntax = Property.DeclaringSyntaxReferences[0].GetSyntax() as PropertyDeclarationSyntax; if (syntax != null) { + // Check for both ref and readonly modifiers bool hasRef = syntax.Modifiers.Any(m => m.IsKind(SyntaxKind.RefKeyword)); bool hasReadOnly = syntax.Modifiers.Any(m => m.IsKind(SyntaxKind.ReadOnlyKeyword)); - + if (hasRef) { return hasReadOnly ? RefKind.RefReadOnly : RefKind.Ref; @@ -718,7 +719,32 @@ private static void GenerateLegacyProperty( // Handle ref returns in getter if (propInfo.RefKind != RefKind.None) { + // Ref properties can only return references to fields sb.AppendLine($"{indent} {getterModifier}get => ref {backingFieldName};"); + + // Don't generate a setter for ref readonly properties + if (property.SetMethod != null && propInfo.RefKind == RefKind.Ref) + { + var setterModifier = setterAccessibility != propertyAccessibility ? $"{setterAccessibility} " : ""; + var setterType = property.SetMethod.IsInitOnly ? "init" : "set"; + + if (isReactiveObject) + { + sb.AppendLine($"{indent} {setterModifier}{setterType} => this.RaiseAndSetIfChanged(ref {backingFieldName}, value);"); + } + else + { + var eventArgsFieldName = GetEventArgsFieldName(propertyName); + sb.AppendLine($"{indent} {setterModifier}{setterType}"); + sb.AppendLine($"{indent} {{"); + sb.AppendLine($"{indent} if (!Equals({backingFieldName}, value))"); + sb.AppendLine($"{indent} {{"); + sb.AppendLine($"{indent} {backingFieldName} = value;"); + sb.AppendLine($"{indent} OnPropertyChanged({eventArgsFieldName});"); + sb.AppendLine($"{indent} }}"); + sb.AppendLine($"{indent} }}"); + } + } } else { @@ -788,7 +814,32 @@ private static void GenerateFieldKeywordProperty( // Handle ref returns in getter if (propInfo.RefKind != RefKind.None) { + // Ref properties can only return references to fields sb.AppendLine($"{indent} {getterModifier}get => ref field;"); + + // Don't generate a setter for ref readonly properties + if (property.SetMethod != null && propInfo.RefKind == RefKind.Ref) + { + var setterModifier = setterAccessibility != propertyAccessibility ? $"{setterAccessibility} " : ""; + var setterType = property.SetMethod.IsInitOnly ? "init" : "set"; + + if (isReactiveObject) + { + sb.AppendLine($"{indent} {setterModifier}{setterType} => this.RaiseAndSetIfChanged(ref field, value);"); + } + else + { + var eventArgsFieldName = GetEventArgsFieldName(propertyName); + sb.AppendLine($"{indent} {setterModifier}{setterType}"); + sb.AppendLine($"{indent} {{"); + sb.AppendLine($"{indent} if (!Equals(field, value))"); + sb.AppendLine($"{indent} {{"); + sb.AppendLine($"{indent} field = value;"); + sb.AppendLine($"{indent} OnPropertyChanged({eventArgsFieldName});"); + sb.AppendLine($"{indent} }}"); + sb.AppendLine($"{indent} }}"); + } + } } else {