diff --git a/ReactiveGenerator.Tests/ObservableAsPropertyHelperGenerator/ObservableAsPropertyHelperGeneratorTests.cs b/ReactiveGenerator.Tests/ObservableAsPropertyHelperGenerator/ObservableAsPropertyHelperGeneratorTests.cs index dad4548..efeb138 100644 --- a/ReactiveGenerator.Tests/ObservableAsPropertyHelperGenerator/ObservableAsPropertyHelperGeneratorTests.cs +++ b/ReactiveGenerator.Tests/ObservableAsPropertyHelperGenerator/ObservableAsPropertyHelperGeneratorTests.cs @@ -317,4 +317,68 @@ public partial class TestViewModel : ReactiveObject return TestAndVerify(source); } + + [Fact] + public Task ObservableAsPropertyWithConstraints() + { + var source = @" + using ReactiveUI; + + public partial class ViewModel : ReactiveObject + where T : class, IDisposable + where TKey : notnull + { + [ObservableAsProperty] + public partial T? ComputedValue { get; } + + [ObservableAsProperty] + public partial TKey CurrentKey { get; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task ObservableAsPropertyWithComplexConstraints() + { + var source = @" + using ReactiveUI; + + public partial class Container : ReactiveObject + where T : class? + where U : struct, IComparable + where V : unmanaged + { + [ObservableAsProperty] + public partial T? NullableRef { get; } + + [ObservableAsProperty] + public partial U ValueType { get; } + + [ObservableAsProperty] + public partial V UnmanagedValue { get; } + }"; + + return TestAndVerify(source); + } + + [Fact] + public Task ObservableAsPropertyWithNestedGenerics() + { + var source = @" + using ReactiveUI; + + public partial class Outer : ReactiveObject + where T : class + { + public partial class Inner : ReactiveObject + where U : T, new() + { + [ObservableAsProperty] + public partial U? Value { get; } + } + }"; + + return TestAndVerify(source); + } } diff --git a/ReactiveGenerator.Tests/Snapshots/ObservableAsPropertyHelperGeneratorTests.ObservableAsPropertyWithComplexConstraints.verified.txt b/ReactiveGenerator.Tests/Snapshots/ObservableAsPropertyHelperGeneratorTests.ObservableAsPropertyWithComplexConstraints.verified.txt new file mode 100644 index 0000000..4a374f9 --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ObservableAsPropertyHelperGeneratorTests.ObservableAsPropertyWithComplexConstraints.verified.txt @@ -0,0 +1,52 @@ +{ + Sources: [ + { + FileName: Container.ObservableAsProperty.g.cs, + Source: +// +#nullable enable + +using System; +using ReactiveUI; + +public partial class Container where T : class? where U : struct, IComparable where V : unmanaged, struct +{ + private ObservableAsPropertyHelper _nullableRefHelper; + + public partial T? NullableRef + { + get => _nullableRefHelper.Value; + } + + private ObservableAsPropertyHelper _valueTypeHelper; + + public partial U ValueType + { + get => _valueTypeHelper.Value; + } + + private ObservableAsPropertyHelper _unmanagedValueHelper; + + public partial V UnmanagedValue + { + get => _unmanagedValueHelper.Value; + } +} + + }, + { + FileName: ObservableAsPropertyAttribute.g.cs, + Source: +// +using System; + +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +sealed class ObservableAsPropertyAttribute : Attribute +{ + public ObservableAsPropertyAttribute() { } +} + + } + ], + Diagnostics: null +} \ No newline at end of file diff --git a/ReactiveGenerator.Tests/Snapshots/ObservableAsPropertyHelperGeneratorTests.ObservableAsPropertyWithConstraints.verified.txt b/ReactiveGenerator.Tests/Snapshots/ObservableAsPropertyHelperGeneratorTests.ObservableAsPropertyWithConstraints.verified.txt new file mode 100644 index 0000000..be6efdb --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ObservableAsPropertyHelperGeneratorTests.ObservableAsPropertyWithConstraints.verified.txt @@ -0,0 +1,45 @@ +{ + Sources: [ + { + FileName: ObservableAsPropertyAttribute.g.cs, + Source: +// +using System; + +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +sealed class ObservableAsPropertyAttribute : Attribute +{ + public ObservableAsPropertyAttribute() { } +} + + }, + { + FileName: ViewModel.ObservableAsProperty.g.cs, + Source: +// +#nullable enable + +using System; +using ReactiveUI; + +public partial class ViewModel where T : class, IDisposable where TKey : notnull +{ + private ObservableAsPropertyHelper _computedValueHelper; + + public partial T? ComputedValue + { + get => _computedValueHelper.Value; + } + + private ObservableAsPropertyHelper _currentKeyHelper; + + public partial TKey CurrentKey + { + get => _currentKeyHelper.Value; + } +} + + } + ], + Diagnostics: null +} \ No newline at end of file diff --git a/ReactiveGenerator.Tests/Snapshots/ObservableAsPropertyHelperGeneratorTests.ObservableAsPropertyWithNestedGenerics.verified.txt b/ReactiveGenerator.Tests/Snapshots/ObservableAsPropertyHelperGeneratorTests.ObservableAsPropertyWithNestedGenerics.verified.txt new file mode 100644 index 0000000..dc974d4 --- /dev/null +++ b/ReactiveGenerator.Tests/Snapshots/ObservableAsPropertyHelperGeneratorTests.ObservableAsPropertyWithNestedGenerics.verified.txt @@ -0,0 +1,41 @@ +{ + Sources: [ + { + FileName: ObservableAsPropertyAttribute.g.cs, + Source: +// +using System; + +[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +sealed class ObservableAsPropertyAttribute : Attribute +{ + public ObservableAsPropertyAttribute() { } +} + + }, + { + FileName: Outer.Inner.ObservableAsProperty.g.cs, + Source: +// +#nullable enable + +using System; +using ReactiveUI; + +public partial class Outer where T : class +{ + public partial class Inner where U : T, new() + { + private ObservableAsPropertyHelper _valueHelper; + + public partial U? Value + { + get => _valueHelper.Value; + } + } +} + + } + ], + Diagnostics: null +} \ No newline at end of file diff --git a/ReactiveGenerator/ObservableAsPropertyHelperGenerator.cs b/ReactiveGenerator/ObservableAsPropertyHelperGenerator.cs index 7e4ddd4..5760898 100644 --- a/ReactiveGenerator/ObservableAsPropertyHelperGenerator.cs +++ b/ReactiveGenerator/ObservableAsPropertyHelperGenerator.cs @@ -163,7 +163,14 @@ private static string GenerateHelperProperties( foreach (var containingType in containingTypes) { var containingTypeAccessibility = containingType.DeclaredAccessibility.ToString().ToLowerInvariant(); - sb.AppendLine($"{indent}{containingTypeAccessibility} partial class {containingType.Name}"); + 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 += " "; } @@ -175,7 +182,7 @@ private static string GenerateHelperProperties( if (classSymbol.TypeParameters.Length > 0) { typeParameters = "<" + string.Join(", ", classSymbol.TypeParameters.Select(tp => tp.Name)) + ">"; - typeConstraints = GenerateTypeConstraints(classSymbol.TypeParameters); + typeConstraints = TypeHelper.GenerateTypeConstraints(classSymbol); } sb.AppendLine($"{indent}{accessibility} partial class {classSymbol.Name}{typeParameters}{typeConstraints}"); @@ -208,35 +215,6 @@ private static string GenerateHelperProperties( return sb.ToString(); } - private static string GenerateTypeConstraints(ImmutableArray typeParameters) - { - var constraints = new List(); - foreach (var typeParam in typeParameters) - { - var paramConstraints = new List(); - - if (typeParam.HasReferenceTypeConstraint) - paramConstraints.Add("class"); - else if (typeParam.HasValueTypeConstraint) - paramConstraints.Add("struct"); - - foreach (var constraintType in typeParam.ConstraintTypes) - { - paramConstraints.Add(constraintType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - } - - if (typeParam.HasConstructorConstraint) - paramConstraints.Add("new()"); - - if (paramConstraints.Count > 0) - { - constraints.Add($"where {typeParam.Name} : {string.Join(", ", paramConstraints)}"); - } - } - - return constraints.Count > 0 ? " " + string.Join(" ", constraints) : ""; - } - private static void GenerateObservableAsPropertyHelper(StringBuilder sb, IPropertySymbol property, string indent) { var nullablePropertyType = GetPropertyTypeWithNullability(property);