Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ObservableAsPropertyHelperGenerator type constrains #45

Merged
merged 1 commit into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -317,4 +317,68 @@ public partial class TestViewModel : ReactiveObject

return TestAndVerify(source);
}

[Fact]
public Task ObservableAsPropertyWithConstraints()
{
var source = @"
using ReactiveUI;

public partial class ViewModel<T, TKey> : 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<T, U, V> : ReactiveObject
where T : class?
where U : struct, IComparable<U>
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<T> : ReactiveObject
where T : class
{
public partial class Inner<U> : ReactiveObject
where U : T, new()
{
[ObservableAsProperty]
public partial U? Value { get; }
}
}";

return TestAndVerify(source);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
Sources: [
{
FileName: Container.ObservableAsProperty.g.cs,
Source:
// <auto-generated/>
#nullable enable

using System;
using ReactiveUI;

public partial class Container<T, U, V> where T : class? where U : struct, IComparable<U> where V : unmanaged, struct
{
private ObservableAsPropertyHelper<T?> _nullableRefHelper;

public partial T? NullableRef
{
get => _nullableRefHelper.Value;
}

private ObservableAsPropertyHelper<U> _valueTypeHelper;

public partial U ValueType
{
get => _valueTypeHelper.Value;
}

private ObservableAsPropertyHelper<V> _unmanagedValueHelper;

public partial V UnmanagedValue
{
get => _unmanagedValueHelper.Value;
}
}

},
{
FileName: ObservableAsPropertyAttribute.g.cs,
Source:
// <auto-generated/>
using System;

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
sealed class ObservableAsPropertyAttribute : Attribute
{
public ObservableAsPropertyAttribute() { }
}

}
],
Diagnostics: null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
Sources: [
{
FileName: ObservableAsPropertyAttribute.g.cs,
Source:
// <auto-generated/>
using System;

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
sealed class ObservableAsPropertyAttribute : Attribute
{
public ObservableAsPropertyAttribute() { }
}

},
{
FileName: ViewModel.ObservableAsProperty.g.cs,
Source:
// <auto-generated/>
#nullable enable

using System;
using ReactiveUI;

public partial class ViewModel<T, TKey> where T : class, IDisposable where TKey : notnull
{
private ObservableAsPropertyHelper<T?> _computedValueHelper;

public partial T? ComputedValue
{
get => _computedValueHelper.Value;
}

private ObservableAsPropertyHelper<TKey> _currentKeyHelper;

public partial TKey CurrentKey
{
get => _currentKeyHelper.Value;
}
}

}
],
Diagnostics: null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
Sources: [
{
FileName: ObservableAsPropertyAttribute.g.cs,
Source:
// <auto-generated/>
using System;

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
sealed class ObservableAsPropertyAttribute : Attribute
{
public ObservableAsPropertyAttribute() { }
}

},
{
FileName: Outer.Inner.ObservableAsProperty.g.cs,
Source:
// <auto-generated/>
#nullable enable

using System;
using ReactiveUI;

public partial class Outer<T> where T : class
{
public partial class Inner<U> where U : T, new()
{
private ObservableAsPropertyHelper<U?> _valueHelper;

public partial U? Value
{
get => _valueHelper.Value;
}
}
}

}
],
Diagnostics: null
}
40 changes: 9 additions & 31 deletions ReactiveGenerator/ObservableAsPropertyHelperGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 += " ";
}
Expand All @@ -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}");
Expand Down Expand Up @@ -208,35 +215,6 @@ private static string GenerateHelperProperties(
return sb.ToString();
}

private static string GenerateTypeConstraints(ImmutableArray<ITypeParameterSymbol> typeParameters)
{
var constraints = new List<string>();
foreach (var typeParam in typeParameters)
{
var paramConstraints = new List<string>();

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);
Expand Down
Loading