Skip to content

Commit

Permalink
Merge pull request #682 from CommunityToolkit/dev/fix-forwarded-enums
Browse files Browse the repository at this point in the history
Fix forwarding attributes with negative enum values
  • Loading branch information
Sergio0694 committed May 6, 2023
2 parents 4ad7fef + 40c82ce commit 7409b95
Show file tree
Hide file tree
Showing 2 changed files with 365 additions and 4 deletions.
Expand Up @@ -134,10 +134,9 @@ public sealed record Enum(string TypeName, object Value) : TypedConstantInfo
/// <inheritdoc/>
public override ExpressionSyntax GetSyntax()
{
return
CastExpression(
IdentifierName(TypeName),
LiteralExpression(SyntaxKind.NumericLiteralExpression, ParseToken(Value.ToString())));
// We let Roslyn parse the value expression, so that it can automatically handle both positive and negative values. This
// is needed because negative values have a different syntax tree (UnaryMinuxExpression holding the numeric expression).
return CastExpression(IdentifierName(TypeName), ParseExpression(Value.ToString()));
}
}

Expand Down
Expand Up @@ -1283,6 +1283,278 @@ partial class MyViewModel
VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test.g.cs", result));
}

// See https://github.com/CommunityToolkit/dotnet/issues/681
[TestMethod]
public void ObservablePropertyWithForwardedAttributesWithEnumWithNegativeValue_WorksCorrectly()
{
string source = """
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;

#nullable enable

namespace MyApp;

partial class MyViewModel : ObservableObject
{
[ObservableProperty]
[property: DefaultValue(NegativeEnum1.Problem)]
[property: DefaultValue(NegativeEnum2.Problem)]
[property: DefaultValue(NegativeEnum3.Problem)]
[property: DefaultValue(NegativeEnum4.Problem)]
private object? a;
}

public class DefaultValueAttribute : Attribute
{
public DefaultValueAttribute(object value)
{
}
}

public enum NegativeEnum1
{
Problem = -1073741824,
OK = 0
}

public enum NegativeEnum2 : long
{
Problem = long.MinValue,
OK = 0
}

public enum NegativeEnum3 : short
{
Problem = -1234,
OK = 0
}

public enum NegativeEnum4 : sbyte
{
Problem = -1,
OK = 0
}
""";

string result = """
// <auto-generated/>
#pragma warning disable
#nullable enable
namespace MyApp
{
/// <inheritdoc/>
partial class MyViewModel
{
/// <inheritdoc cref="a"/>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)-1073741824)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)-9223372036854775808)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)-1234)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)-1)]
public object? A
{
get => a;
set
{
if (!global::System.Collections.Generic.EqualityComparer<object?>.Default.Equals(a, value))
{
OnAChanging(value);
OnAChanging(default, value);
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A);
a = value;
OnAChanged(value);
OnAChanged(default, value);
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A);
}
}
}

/// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
/// <param name="value">The new property value being set.</param>
/// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
partial void OnAChanging(object? value);
/// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
/// <param name="oldValue">The previous property value that is being replaced.</param>
/// <param name="newValue">The new property value being set.</param>
/// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
partial void OnAChanging(object? oldValue, object? newValue);
/// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
/// <param name="value">The new property value that was set.</param>
/// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
partial void OnAChanged(object? value);
/// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
/// <param name="oldValue">The previous property value that was replaced.</param>
/// <param name="newValue">The new property value that was set.</param>
/// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
partial void OnAChanged(object? oldValue, object? newValue);
}
}
""";

VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
}

[TestMethod]
public void ObservablePropertyWithForwardedAttributesWithEnumWithCombinedValues_WorksCorrectly()
{
string source = """
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;

#nullable enable

namespace MyApp;

partial class MyViewModel : ObservableObject
{
[ObservableProperty]
[property: DefaultValue(NegativeEnum1.A)]
[property: DefaultValue(NegativeEnum1.B)]
[property: DefaultValue((NegativeEnum1)42)]
[property: DefaultValue((NegativeEnum1)OtherClass.MyNumber)]
[property: DefaultValue(OtherClass.MyEnumValue)]
[property: DefaultValue(NegativeEnum2.A)]
[property: DefaultValue(NegativeEnum2.B)]
[property: DefaultValue((NegativeEnum2)42)]
[property: DefaultValue((NegativeEnum2)OtherClass.MyNumber)]
[property: DefaultValue((NegativeEnum2)(int)OtherClass.MyEnumValue)]
[property: DefaultValue(NegativeEnum3.A)]
[property: DefaultValue(NegativeEnum3.B)]
[property: DefaultValue((NegativeEnum3)42)]
[property: DefaultValue((NegativeEnum3)OtherClass.MyNumber)]
[property: DefaultValue((NegativeEnum3)(int)OtherClass.MyEnumValue)]
[property: DefaultValue(NegativeEnum4.A)]
[property: DefaultValue(NegativeEnum4.B)]
[property: DefaultValue((NegativeEnum4)42)]
[property: DefaultValue((NegativeEnum4)unchecked((sbyte)OtherClass.MyNumber))]
[property: DefaultValue((NegativeEnum4)(int)OtherClass.MyEnumValue)]
private object? a;
}

public static class OtherClass
{
public const int MyNumber = 456;
public const NegativeEnum1 MyEnumValue = NegativeEnum1.C;
}

public class DefaultValueAttribute : Attribute
{
public DefaultValueAttribute(object value)
{
}
}

public enum NegativeEnum1
{
A = 0,
B = -1073741824,
C = 123
}

public enum NegativeEnum2 : long
{
A = 0,
B = long.MinValue
}

public enum NegativeEnum3 : short
{
A = 1,
B = -1234
}

public enum NegativeEnum4 : sbyte
{
A = 1,
B = -1
}
""";

string result = """
// <auto-generated/>
#pragma warning disable
#nullable enable
namespace MyApp
{
/// <inheritdoc/>
partial class MyViewModel
{
/// <inheritdoc cref="a"/>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)0)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)-1073741824)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)42)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)456)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)123)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)0)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)-9223372036854775808)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)42)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)456)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)123)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)1)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)-1234)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)42)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)456)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)123)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)1)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)-1)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)42)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)-56)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)123)]
public object? A
{
get => a;
set
{
if (!global::System.Collections.Generic.EqualityComparer<object?>.Default.Equals(a, value))
{
OnAChanging(value);
OnAChanging(default, value);
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A);
a = value;
OnAChanged(value);
OnAChanged(default, value);
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A);
}
}
}

/// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
/// <param name="value">The new property value being set.</param>
/// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
partial void OnAChanging(object? value);
/// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
/// <param name="oldValue">The previous property value that is being replaced.</param>
/// <param name="newValue">The new property value being set.</param>
/// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
partial void OnAChanging(object? oldValue, object? newValue);
/// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
/// <param name="value">The new property value that was set.</param>
/// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
partial void OnAChanged(object? value);
/// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
/// <param name="oldValue">The previous property value that was replaced.</param>
/// <param name="newValue">The new property value that was set.</param>
/// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
partial void OnAChanged(object? oldValue, object? newValue);
}
}
""";

VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
}

// See https://github.com/CommunityToolkit/dotnet/issues/632
[TestMethod]
public void RelayCommandMethodWithPartialDeclarations_TriggersCorrectly()
Expand Down Expand Up @@ -1450,6 +1722,96 @@ partial class MyViewModel
VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test1.g.cs", result1), ("MyApp.MyViewModel.Test2.g.cs", result2));
}

// See https://github.com/CommunityToolkit/dotnet/issues/681
[TestMethod]
public void RelayCommandMethodWithForwardedAttributesWithEnumValues_WorksCorrectly()
{
string source = """
using CommunityToolkit.Mvvm.Input;

#nullable enable

namespace MyApp;

partial class MyViewModel
{
[RelayCommand]
[field: DefaultValue(NegativeEnum1.Problem)]
[field: DefaultValue(NegativeEnum2.Problem)]
[field: DefaultValue(NegativeEnum3.Problem)]
[field: DefaultValue(NegativeEnum4.Problem)]
[property: DefaultValue(NegativeEnum1.Problem)]
[property: DefaultValue(NegativeEnum2.Problem)]
[property: DefaultValue(NegativeEnum3.Problem)]
[property: DefaultValue(NegativeEnum4.Problem)]
private void Test()
{
}
}

public class DefaultValueAttribute : Attribute
{
public DefaultValueAttribute(object value)
{
}
}

public enum NegativeEnum1
{
Problem = -1073741824,
OK = 0
}

public enum NegativeEnum2 : long
{
Problem = long.MinValue,
OK = 0
}

public enum NegativeEnum3 : short
{
Problem = -1234,
OK = 0
}

public enum NegativeEnum4 : sbyte
{
Problem = -1,
OK = 0
}
""";

string result = """
// <auto-generated/>
#pragma warning disable
#nullable enable
namespace MyApp
{
/// <inheritdoc/>
partial class MyViewModel
{
/// <summary>The backing field for <see cref="TestCommand"/>.</summary>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.2.0.0")]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)-1073741824)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)-9223372036854775808)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)-1234)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)-1)]
private global::CommunityToolkit.Mvvm.Input.RelayCommand? testCommand;
/// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand"/> instance wrapping <see cref="Test"/>.</summary>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.2.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)-1073741824)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)-9223372036854775808)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)-1234)]
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)-1)]
public global::CommunityToolkit.Mvvm.Input.IRelayCommand TestCommand => testCommand ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test));
}
}
""";

VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test.g.cs", result));
}

[TestMethod]
public void ObservablePropertyWithinGenericAndNestedTypes()
{
Expand Down

0 comments on commit 7409b95

Please sign in to comment.