Skip to content
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 @@ -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
Original file line number Diff line number Diff line change
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