From f0078e36b1ecb33a1b86b21a05f9083420f0e3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Wed, 15 Jan 2025 21:05:29 +0100 Subject: [PATCH 1/7] Update TypeHelper.cs --- ReactiveGenerator/TypeHelper.cs | 63 +++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/ReactiveGenerator/TypeHelper.cs b/ReactiveGenerator/TypeHelper.cs index 7d4ec98..7e43ff7 100644 --- a/ReactiveGenerator/TypeHelper.cs +++ b/ReactiveGenerator/TypeHelper.cs @@ -254,4 +254,67 @@ void ProcessNamespaceTypes(INamespaceSymbol ns) ProcessNamespaceTypes(compilation.GlobalNamespace); return result; } + + public static string GetTypeConstraints(ITypeParameterSymbol typeParam) + { + var constraints = new List(); + + // Handle null constraints first + if (typeParam.HasNotNullConstraint) + { + constraints.Add("notnull"); + } + else if (typeParam.HasUnmanagedTypeConstraint) + { + constraints.Add("unmanaged"); + } + + // Handle reference/value type constraints + if (typeParam.HasReferenceTypeConstraint) + { + // Handle nullable reference type constraint + if (typeParam.ReferenceTypeConstraintNullableAnnotation == NullableAnnotation.Annotated) + { + constraints.Add("class?"); + } + else + { + constraints.Add("class"); + } + } + else if (typeParam.HasValueTypeConstraint) + { + constraints.Add("struct"); + } + + // Add type constraints + foreach (var constraintType in typeParam.ConstraintTypes) + { + constraints.Add(constraintType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + } + + // Add constructor constraint last + if (typeParam.HasConstructorConstraint) + { + constraints.Add("new()"); + } + + return string.Join(", ", constraints); + } + + public static string GenerateTypeConstraints(INamedTypeSymbol classSymbol) + { + var constraints = new List(); + + foreach (var typeParam in classSymbol.TypeParameters) + { + var typeConstraints = GetTypeConstraints(typeParam); + if (!string.IsNullOrEmpty(typeConstraints)) + { + constraints.Add($"where {typeParam.Name} : {typeConstraints}"); + } + } + + return constraints.Count > 0 ? " " + string.Join(" ", constraints) : ""; + } } From 0e627a04baa395f8e3fff54e741a79d15a96ebcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Wed, 15 Jan 2025 21:05:35 +0100 Subject: [PATCH 2/7] Update ReactiveGenerator.cs --- ReactiveGenerator/ReactiveGenerator.cs | 76 +++++++------------------- 1 file changed, 20 insertions(+), 56 deletions(-) diff --git a/ReactiveGenerator/ReactiveGenerator.cs b/ReactiveGenerator/ReactiveGenerator.cs index e71fe19..2607aaf 100644 --- a/ReactiveGenerator/ReactiveGenerator.cs +++ b/ReactiveGenerator/ReactiveGenerator.cs @@ -355,7 +355,6 @@ IEnumerable GetContainingTypesChain(INamedTypeSymbol symbol) types.Insert(0, current); current = current.ContainingType; } - return types; } @@ -370,30 +369,7 @@ IEnumerable GetContainingTypesChain(INamedTypeSymbol symbol) if (classSymbol.TypeParameters.Length > 0) { typeParameters = "<" + string.Join(", ", classSymbol.TypeParameters.Select(tp => tp.Name)) + ">"; - - var constraints = new List(); - foreach (var typeParam in classSymbol.TypeParameters) - { - var paramConstraints = new List(); - - if (typeParam.HasReferenceTypeConstraint) - paramConstraints.Add("class"); - if (typeParam.HasValueTypeConstraint) - paramConstraints.Add("struct"); - if (typeParam.HasConstructorConstraint) - paramConstraints.Add("new()"); - - var typeConstraint = string.Join(", ", typeParam.ConstraintTypes.Select(t => - t.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); - if (!string.IsNullOrEmpty(typeConstraint)) - paramConstraints.Add(typeConstraint); - - if (paramConstraints.Count > 0) - constraints.Add($"where {typeParam.Name} : {string.Join(", ", paramConstraints)}"); - } - - if (constraints.Count > 0) - typeConstraints = " " + string.Join(" ", constraints); + typeConstraints = TypeHelper.GenerateTypeConstraints(classSymbol); } var sb = new StringBuilder(); @@ -414,21 +390,31 @@ IEnumerable GetContainingTypesChain(INamedTypeSymbol symbol) foreach (var containingType in containingTypes) { var containingTypeAccessibility = containingType.DeclaredAccessibility.ToString().ToLowerInvariant(); - sb.AppendLine($"{indent}{containingTypeAccessibility} partial class {containingType.Name}"); + + // Handle containing type's generic parameters if any + var containingTypeParams = ""; + var containingTypeConstraints = ""; + if (containingType.TypeParameters.Length > 0) + { + containingTypeParams = "<" + string.Join(", ", containingType.TypeParameters.Select(tp => tp.Name)) + ">"; + containingTypeConstraints = TypeHelper.GenerateTypeConstraints(containingType); + } + + sb.AppendLine($"{indent}{containingTypeAccessibility} partial class {containingType.Name}{containingTypeParams}{containingTypeConstraints}"); sb.AppendLine($"{indent}{{"); indent += " "; } var accessibility = classSymbol.DeclaredAccessibility.ToString().ToLowerInvariant(); - sb.AppendLine( - $"{indent}{accessibility} partial class {classSymbol.Name}{typeParameters} : INotifyPropertyChanged{typeConstraints}"); + sb.AppendLine($"{indent}{accessibility} partial class {classSymbol.Name}{typeParameters} : INotifyPropertyChanged{typeConstraints}"); sb.AppendLine($"{indent}{{"); - + + // Add PropertyChanged event sb.AppendLine($"{indent} public event PropertyChangedEventHandler? PropertyChanged;"); sb.AppendLine(); - sb.AppendLine( - $"{indent} protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)"); + // Add OnPropertyChanged methods + sb.AppendLine($"{indent} protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)"); sb.AppendLine($"{indent} {{"); sb.AppendLine($"{indent} PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));"); sb.AppendLine($"{indent} }}"); @@ -438,8 +424,10 @@ IEnumerable GetContainingTypesChain(INamedTypeSymbol symbol) sb.AppendLine($"{indent} {{"); sb.AppendLine($"{indent} PropertyChanged?.Invoke(this, args);"); sb.AppendLine($"{indent} }}"); + sb.AppendLine($"{indent}}}"); + // Close any containing type declarations for (int i = 0; i < containingTypes.Count; i++) { indent = indent.Substring(0, indent.Length - 4); @@ -494,31 +482,7 @@ IEnumerable GetContainingTypesChain(INamedTypeSymbol symbol) if (classSymbol.TypeParameters.Length > 0) { typeParameters = "<" + string.Join(", ", classSymbol.TypeParameters.Select(tp => tp.Name)) + ">"; - - // Add constraints for each type parameter - var constraints = new List(); - foreach (var typeParam in classSymbol.TypeParameters) - { - var paramConstraints = new List(); - - if (typeParam.HasReferenceTypeConstraint) - paramConstraints.Add("class"); - if (typeParam.HasValueTypeConstraint) - paramConstraints.Add("struct"); - if (typeParam.HasConstructorConstraint) - paramConstraints.Add("new()"); - - var typeConstraint = string.Join(", ", typeParam.ConstraintTypes.Select(t => - t.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); - if (!string.IsNullOrEmpty(typeConstraint)) - paramConstraints.Add(typeConstraint); - - if (paramConstraints.Count > 0) - constraints.Add($"where {typeParam.Name} : {string.Join(", ", paramConstraints)}"); - } - - if (constraints.Count > 0) - typeConstraints = " " + string.Join(" ", constraints); + typeConstraints = TypeHelper.GenerateTypeConstraints(classSymbol); } var sb = new StringBuilder(); From 8cc269a4efb90fa3fe7f7ff877ef94cbe5ea06a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Wed, 15 Jan 2025 21:05:39 +0100 Subject: [PATCH 3/7] Update ReactiveGeneratorTests.cs --- .../ReactiveGeneratorTests.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs b/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs index 8e978a3..1553fd1 100644 --- a/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs +++ b/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs @@ -1577,6 +1577,68 @@ public partial class Person return TestAndVerify(source); } + [Fact] + public Task ClassWithNotNullConstraint() + { + var source = @" + [Reactive] + public partial class Cache + where TKey : notnull + where TValue : class + { + public partial TValue? Value { get; set; } + public partial TKey Key { get; set; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task ClassWithMultipleComplexConstraints() + { + var source = @" + [Reactive] + public partial class AdvancedCache + where T : class, IDisposable + where TKey : notnull + where TValue : struct, IComparable + { + public partial T? Instance { get; set; } + public partial TKey Key { get; set; } + public partial TValue Value { get; set; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task ClassWithNullableReferenceConstraint() + { + var source = @" + [Reactive] + public partial class Container + where T : class? + { + public partial T? Value { get; set; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task ClassWithUnmanagedConstraint() + { + var source = @" + [Reactive] + public partial class UnmanagedContainer + where T : unmanaged + { + public partial T Value { get; set; } + }"; + + return TestAndVerify(source); + } + /* TODO: [Fact] public Task ReadOnlyPropertyTest() From 4faff4e3e07e32cded51c7157398ad51829fe257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Wed, 15 Jan 2025 21:05:43 +0100 Subject: [PATCH 4/7] Update ReactiveGeneratorTests.ClassWithGenericConstraintsAndNullableProperties.verified.txt --- ...ssWithGenericConstraintsAndNullableProperties.verified.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithGenericConstraintsAndNullableProperties.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithGenericConstraintsAndNullableProperties.verified.txt index 2669c5e..887ee90 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithGenericConstraintsAndNullableProperties.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithGenericConstraintsAndNullableProperties.verified.txt @@ -33,7 +33,7 @@ sealed class ReactiveAttribute : Attribute using System.ComponentModel; using System.Runtime.CompilerServices; -public partial class TestClass : INotifyPropertyChanged where T : class, IDisposable where U : struct, IComparable +public partial class TestClass : INotifyPropertyChanged where T : class?, IDisposable where U : struct, IComparable { public event PropertyChangedEventHandler? PropertyChanged; @@ -61,7 +61,7 @@ using System.Runtime.CompilerServices; /// /// A partial class implementation for TestClass{T, U}. /// -public partial class TestClass where T : class, IDisposable where U : struct, IComparable +public partial class TestClass where T : class?, IDisposable where U : struct, IComparable { private static readonly PropertyChangedEventArgs _nullableRefChangedEventArgs = new PropertyChangedEventArgs(nameof(NullableRef)); private static readonly PropertyChangedEventArgs _nullableStructChangedEventArgs = new PropertyChangedEventArgs(nameof(NullableStruct)); From 92d8649b2bb5f35ce765a9da77b3fa2e75937840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Wed, 15 Jan 2025 21:05:59 +0100 Subject: [PATCH 5/7] Update ReactiveGeneratorTests.NestedClassesWithGenericConstraints.verified.txt --- ...eratorTests.NestedClassesWithGenericConstraints.verified.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithGenericConstraints.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithGenericConstraints.verified.txt index c4360f8..d83f7d0 100644 --- a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithGenericConstraints.verified.txt +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.NestedClassesWithGenericConstraints.verified.txt @@ -9,7 +9,7 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -public partial class Container +public partial class Container where T : class { public partial class Nested : INotifyPropertyChanged where U : struct { From b5946729ef878f02bfa21d35a32f6e6da07a94e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Wed, 15 Jan 2025 21:06:59 +0100 Subject: [PATCH 6/7] Add verified --- ...ithMultipleComplexConstraints.verified.txt | 113 ++++++++++++++++++ ...ts.ClassWithNotNullConstraint.verified.txt | 99 +++++++++++++++ ...thNullableReferenceConstraint.verified.txt | 85 +++++++++++++ ....ClassWithUnmanagedConstraint.verified.txt | 85 +++++++++++++ 4 files changed, 382 insertions(+) create mode 100644 ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithMultipleComplexConstraints.verified.txt create mode 100644 ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithNotNullConstraint.verified.txt create mode 100644 ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithNullableReferenceConstraint.verified.txt create mode 100644 ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithUnmanagedConstraint.verified.txt diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithMultipleComplexConstraints.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithMultipleComplexConstraints.verified.txt new file mode 100644 index 0000000..b93b9c3 --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithMultipleComplexConstraints.verified.txt @@ -0,0 +1,113 @@ +{ + Sources: [ + { + FileName: AdvancedCache.INPC.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +public partial class AdvancedCache : INotifyPropertyChanged where T : class, IDisposable where TKey : notnull where TValue : struct, IComparable +{ + 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: AdvancedCache.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for AdvancedCache{T, TKey, TValue}. +/// +public partial class AdvancedCache where T : class, IDisposable where TKey : notnull where TValue : struct, IComparable +{ + private static readonly PropertyChangedEventArgs _instanceChangedEventArgs = new PropertyChangedEventArgs(nameof(Instance)); + private static readonly PropertyChangedEventArgs _keyChangedEventArgs = new PropertyChangedEventArgs(nameof(Key)); + private static readonly PropertyChangedEventArgs _valueChangedEventArgs = new PropertyChangedEventArgs(nameof(Value)); + + public partial T? Instance + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_instanceChangedEventArgs); + } + } + } + + public partial TKey Key + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_keyChangedEventArgs); + } + } + } + + public partial TValue Value + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_valueChangedEventArgs); + } + } + } +} + + }, + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +// +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + 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 diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithNotNullConstraint.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithNotNullConstraint.verified.txt new file mode 100644 index 0000000..f57ab43 --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithNotNullConstraint.verified.txt @@ -0,0 +1,99 @@ +{ + Sources: [ + { + FileName: Cache.INPC.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +public partial class Cache : INotifyPropertyChanged where TKey : notnull where TValue : class +{ + 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: Cache.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for Cache{TKey, TValue}. +/// +public partial class Cache where TKey : notnull where TValue : class +{ + private static readonly PropertyChangedEventArgs _valueChangedEventArgs = new PropertyChangedEventArgs(nameof(Value)); + private static readonly PropertyChangedEventArgs _keyChangedEventArgs = new PropertyChangedEventArgs(nameof(Key)); + + public partial TValue? Value + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_valueChangedEventArgs); + } + } + } + + public partial TKey Key + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_keyChangedEventArgs); + } + } + } +} + + }, + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +// +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + 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 diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithNullableReferenceConstraint.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithNullableReferenceConstraint.verified.txt new file mode 100644 index 0000000..d675b5d --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithNullableReferenceConstraint.verified.txt @@ -0,0 +1,85 @@ +{ + Sources: [ + { + FileName: Container.INPC.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +public partial class Container : INotifyPropertyChanged where T : class? +{ + 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: Container.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for Container{T}. +/// +public partial class Container where T : class? +{ + private static readonly PropertyChangedEventArgs _valueChangedEventArgs = new PropertyChangedEventArgs(nameof(Value)); + + public partial T? Value + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_valueChangedEventArgs); + } + } + } +} + + }, + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +// +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + 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 diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithUnmanagedConstraint.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithUnmanagedConstraint.verified.txt new file mode 100644 index 0000000..c431933 --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.ClassWithUnmanagedConstraint.verified.txt @@ -0,0 +1,85 @@ +{ + Sources: [ + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +// +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + FileName: ReactiveAttribute.g.cs, + Source: +// +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = true, AllowMultiple = false)] +sealed class ReactiveAttribute : Attribute +{ + public ReactiveAttribute() { } +} + }, + { + FileName: UnmanagedContainer.INPC.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +public partial class UnmanagedContainer : INotifyPropertyChanged where T : unmanaged, struct +{ + 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: UnmanagedContainer.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for UnmanagedContainer{T}. +/// +public partial class UnmanagedContainer where T : unmanaged, struct +{ + private static readonly PropertyChangedEventArgs _valueChangedEventArgs = new PropertyChangedEventArgs(nameof(Value)); + + public partial T Value + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_valueChangedEventArgs); + } + } + } +} + + } + ], + Diagnostics: null +} \ No newline at end of file From 66a97ee29acbbe7c73ee1549f39a24d17ba705eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Wed, 15 Jan 2025 21:10:50 +0100 Subject: [PATCH 7/7] Add AllPossibleConstraints test --- .../ReactiveGeneratorTests.cs | 30 +++- ...rTests.AllPossibleConstraints.verified.txt | 169 ++++++++++++++++++ 2 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.AllPossibleConstraints.verified.txt diff --git a/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs b/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs index 1553fd1..314457e 100644 --- a/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs +++ b/ReactiveGenerator.Tests/ReactiveGenerator/ReactiveGeneratorTests.cs @@ -1638,7 +1638,35 @@ public partial class UnmanagedContainer return TestAndVerify(source); } - + + [Fact] + public Task AllPossibleConstraints() + { + var source = @" + public interface ITestInterface { } + public class BaseClass { } + + [Reactive] + public partial class ConstraintsTest + where T1 : class, ITestInterface, new() // Reference type + interface + constructor + where T2 : struct, IComparable // Value type + interface with self + where T3 : notnull // Non-null constraint + where T4 : unmanaged // Unmanaged constraint + where T5 : BaseClass // Base class constraint + where T6 : T1 // Another type parameter constraint + where T7 : class? // Nullable reference type constraint + { + public partial T1? Property1 { get; set; } + public partial T2 Property2 { get; set; } + public partial T3 Property3 { get; set; } + public partial T4 Property4 { get; set; } + public partial T5? Property5 { get; set; } + public partial T6? Property6 { get; set; } + public partial T7? Property7 { get; set; } + }"; + + return TestAndVerify(source); + } /* TODO: [Fact] public Task ReadOnlyPropertyTest() diff --git a/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.AllPossibleConstraints.verified.txt b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.AllPossibleConstraints.verified.txt new file mode 100644 index 0000000..e1c39ae --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ReactiveGeneratorTests.AllPossibleConstraints.verified.txt @@ -0,0 +1,169 @@ +{ + Sources: [ + { + FileName: ConstraintsTest.INPC.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +public partial class ConstraintsTest : INotifyPropertyChanged where T1 : class, global::ITestInterface, new() where T2 : struct, IComparable where T3 : notnull where T4 : unmanaged, struct where T5 : global::BaseClass where T6 : T1 where T7 : class? +{ + 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: ConstraintsTest.ReactiveProperties.g.cs, + Source: +// +#nullable enable + +using System.ComponentModel; +using System.Runtime.CompilerServices; + +/// +/// A partial class implementation for ConstraintsTest{T1, T2, T3, T4, T5, T6, T7}. +/// +public partial class ConstraintsTest where T1 : class, global::ITestInterface, new() where T2 : struct, IComparable where T3 : notnull where T4 : unmanaged, struct where T5 : global::BaseClass where T6 : T1 where T7 : class? +{ + private static readonly PropertyChangedEventArgs _property1ChangedEventArgs = new PropertyChangedEventArgs(nameof(Property1)); + private static readonly PropertyChangedEventArgs _property2ChangedEventArgs = new PropertyChangedEventArgs(nameof(Property2)); + private static readonly PropertyChangedEventArgs _property3ChangedEventArgs = new PropertyChangedEventArgs(nameof(Property3)); + private static readonly PropertyChangedEventArgs _property4ChangedEventArgs = new PropertyChangedEventArgs(nameof(Property4)); + private static readonly PropertyChangedEventArgs _property5ChangedEventArgs = new PropertyChangedEventArgs(nameof(Property5)); + private static readonly PropertyChangedEventArgs _property6ChangedEventArgs = new PropertyChangedEventArgs(nameof(Property6)); + private static readonly PropertyChangedEventArgs _property7ChangedEventArgs = new PropertyChangedEventArgs(nameof(Property7)); + + public partial T1? Property1 + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_property1ChangedEventArgs); + } + } + } + + public partial T2 Property2 + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_property2ChangedEventArgs); + } + } + } + + public partial T3 Property3 + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_property3ChangedEventArgs); + } + } + } + + public partial T4 Property4 + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_property4ChangedEventArgs); + } + } + } + + public partial T5? Property5 + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_property5ChangedEventArgs); + } + } + } + + public partial T6? Property6 + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_property6ChangedEventArgs); + } + } + } + + public partial T7? Property7 + { + get => field; + set + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(_property7ChangedEventArgs); + } + } + } +} + + }, + { + FileName: IgnoreReactiveAttribute.g.cs, + Source: +// +using System; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +sealed class IgnoreReactiveAttribute : Attribute +{ + public IgnoreReactiveAttribute() { } +} + }, + { + 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