Skip to content

Commit 7409b95

Browse files
authored
Merge pull request #682 from CommunityToolkit/dev/fix-forwarded-enums
Fix forwarding attributes with negative enum values
2 parents 4ad7fef + 40c82ce commit 7409b95

File tree

2 files changed

+365
-4
lines changed

2 files changed

+365
-4
lines changed

src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,9 @@ public sealed record Enum(string TypeName, object Value) : TypedConstantInfo
134134
/// <inheritdoc/>
135135
public override ExpressionSyntax GetSyntax()
136136
{
137-
return
138-
CastExpression(
139-
IdentifierName(TypeName),
140-
LiteralExpression(SyntaxKind.NumericLiteralExpression, ParseToken(Value.ToString())));
137+
// We let Roslyn parse the value expression, so that it can automatically handle both positive and negative values. This
138+
// is needed because negative values have a different syntax tree (UnaryMinuxExpression holding the numeric expression).
139+
return CastExpression(IdentifierName(TypeName), ParseExpression(Value.ToString()));
141140
}
142141
}
143142

tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs

Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,278 @@ partial class MyViewModel
12831283
VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test.g.cs", result));
12841284
}
12851285

1286+
// See https://github.com/CommunityToolkit/dotnet/issues/681
1287+
[TestMethod]
1288+
public void ObservablePropertyWithForwardedAttributesWithEnumWithNegativeValue_WorksCorrectly()
1289+
{
1290+
string source = """
1291+
using System.ComponentModel;
1292+
using CommunityToolkit.Mvvm.ComponentModel;
1293+
1294+
#nullable enable
1295+
1296+
namespace MyApp;
1297+
1298+
partial class MyViewModel : ObservableObject
1299+
{
1300+
[ObservableProperty]
1301+
[property: DefaultValue(NegativeEnum1.Problem)]
1302+
[property: DefaultValue(NegativeEnum2.Problem)]
1303+
[property: DefaultValue(NegativeEnum3.Problem)]
1304+
[property: DefaultValue(NegativeEnum4.Problem)]
1305+
private object? a;
1306+
}
1307+
1308+
public class DefaultValueAttribute : Attribute
1309+
{
1310+
public DefaultValueAttribute(object value)
1311+
{
1312+
}
1313+
}
1314+
1315+
public enum NegativeEnum1
1316+
{
1317+
Problem = -1073741824,
1318+
OK = 0
1319+
}
1320+
1321+
public enum NegativeEnum2 : long
1322+
{
1323+
Problem = long.MinValue,
1324+
OK = 0
1325+
}
1326+
1327+
public enum NegativeEnum3 : short
1328+
{
1329+
Problem = -1234,
1330+
OK = 0
1331+
}
1332+
1333+
public enum NegativeEnum4 : sbyte
1334+
{
1335+
Problem = -1,
1336+
OK = 0
1337+
}
1338+
""";
1339+
1340+
string result = """
1341+
// <auto-generated/>
1342+
#pragma warning disable
1343+
#nullable enable
1344+
namespace MyApp
1345+
{
1346+
/// <inheritdoc/>
1347+
partial class MyViewModel
1348+
{
1349+
/// <inheritdoc cref="a"/>
1350+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
1351+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
1352+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)-1073741824)]
1353+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)-9223372036854775808)]
1354+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)-1234)]
1355+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)-1)]
1356+
public object? A
1357+
{
1358+
get => a;
1359+
set
1360+
{
1361+
if (!global::System.Collections.Generic.EqualityComparer<object?>.Default.Equals(a, value))
1362+
{
1363+
OnAChanging(value);
1364+
OnAChanging(default, value);
1365+
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A);
1366+
a = value;
1367+
OnAChanged(value);
1368+
OnAChanged(default, value);
1369+
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A);
1370+
}
1371+
}
1372+
}
1373+
1374+
/// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
1375+
/// <param name="value">The new property value being set.</param>
1376+
/// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
1377+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
1378+
partial void OnAChanging(object? value);
1379+
/// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
1380+
/// <param name="oldValue">The previous property value that is being replaced.</param>
1381+
/// <param name="newValue">The new property value being set.</param>
1382+
/// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
1383+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
1384+
partial void OnAChanging(object? oldValue, object? newValue);
1385+
/// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
1386+
/// <param name="value">The new property value that was set.</param>
1387+
/// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
1388+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
1389+
partial void OnAChanged(object? value);
1390+
/// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
1391+
/// <param name="oldValue">The previous property value that was replaced.</param>
1392+
/// <param name="newValue">The new property value that was set.</param>
1393+
/// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
1394+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
1395+
partial void OnAChanged(object? oldValue, object? newValue);
1396+
}
1397+
}
1398+
""";
1399+
1400+
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
1401+
}
1402+
1403+
[TestMethod]
1404+
public void ObservablePropertyWithForwardedAttributesWithEnumWithCombinedValues_WorksCorrectly()
1405+
{
1406+
string source = """
1407+
using System.ComponentModel;
1408+
using CommunityToolkit.Mvvm.ComponentModel;
1409+
1410+
#nullable enable
1411+
1412+
namespace MyApp;
1413+
1414+
partial class MyViewModel : ObservableObject
1415+
{
1416+
[ObservableProperty]
1417+
[property: DefaultValue(NegativeEnum1.A)]
1418+
[property: DefaultValue(NegativeEnum1.B)]
1419+
[property: DefaultValue((NegativeEnum1)42)]
1420+
[property: DefaultValue((NegativeEnum1)OtherClass.MyNumber)]
1421+
[property: DefaultValue(OtherClass.MyEnumValue)]
1422+
[property: DefaultValue(NegativeEnum2.A)]
1423+
[property: DefaultValue(NegativeEnum2.B)]
1424+
[property: DefaultValue((NegativeEnum2)42)]
1425+
[property: DefaultValue((NegativeEnum2)OtherClass.MyNumber)]
1426+
[property: DefaultValue((NegativeEnum2)(int)OtherClass.MyEnumValue)]
1427+
[property: DefaultValue(NegativeEnum3.A)]
1428+
[property: DefaultValue(NegativeEnum3.B)]
1429+
[property: DefaultValue((NegativeEnum3)42)]
1430+
[property: DefaultValue((NegativeEnum3)OtherClass.MyNumber)]
1431+
[property: DefaultValue((NegativeEnum3)(int)OtherClass.MyEnumValue)]
1432+
[property: DefaultValue(NegativeEnum4.A)]
1433+
[property: DefaultValue(NegativeEnum4.B)]
1434+
[property: DefaultValue((NegativeEnum4)42)]
1435+
[property: DefaultValue((NegativeEnum4)unchecked((sbyte)OtherClass.MyNumber))]
1436+
[property: DefaultValue((NegativeEnum4)(int)OtherClass.MyEnumValue)]
1437+
private object? a;
1438+
}
1439+
1440+
public static class OtherClass
1441+
{
1442+
public const int MyNumber = 456;
1443+
public const NegativeEnum1 MyEnumValue = NegativeEnum1.C;
1444+
}
1445+
1446+
public class DefaultValueAttribute : Attribute
1447+
{
1448+
public DefaultValueAttribute(object value)
1449+
{
1450+
}
1451+
}
1452+
1453+
public enum NegativeEnum1
1454+
{
1455+
A = 0,
1456+
B = -1073741824,
1457+
C = 123
1458+
}
1459+
1460+
public enum NegativeEnum2 : long
1461+
{
1462+
A = 0,
1463+
B = long.MinValue
1464+
}
1465+
1466+
public enum NegativeEnum3 : short
1467+
{
1468+
A = 1,
1469+
B = -1234
1470+
}
1471+
1472+
public enum NegativeEnum4 : sbyte
1473+
{
1474+
A = 1,
1475+
B = -1
1476+
}
1477+
""";
1478+
1479+
string result = """
1480+
// <auto-generated/>
1481+
#pragma warning disable
1482+
#nullable enable
1483+
namespace MyApp
1484+
{
1485+
/// <inheritdoc/>
1486+
partial class MyViewModel
1487+
{
1488+
/// <inheritdoc cref="a"/>
1489+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
1490+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
1491+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)0)]
1492+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)-1073741824)]
1493+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)42)]
1494+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)456)]
1495+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)123)]
1496+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)0)]
1497+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)-9223372036854775808)]
1498+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)42)]
1499+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)456)]
1500+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)123)]
1501+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)1)]
1502+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)-1234)]
1503+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)42)]
1504+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)456)]
1505+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)123)]
1506+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)1)]
1507+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)-1)]
1508+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)42)]
1509+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)-56)]
1510+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)123)]
1511+
public object? A
1512+
{
1513+
get => a;
1514+
set
1515+
{
1516+
if (!global::System.Collections.Generic.EqualityComparer<object?>.Default.Equals(a, value))
1517+
{
1518+
OnAChanging(value);
1519+
OnAChanging(default, value);
1520+
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.A);
1521+
a = value;
1522+
OnAChanged(value);
1523+
OnAChanged(default, value);
1524+
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.A);
1525+
}
1526+
}
1527+
}
1528+
1529+
/// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
1530+
/// <param name="value">The new property value being set.</param>
1531+
/// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
1532+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
1533+
partial void OnAChanging(object? value);
1534+
/// <summary>Executes the logic for when <see cref="A"/> is changing.</summary>
1535+
/// <param name="oldValue">The previous property value that is being replaced.</param>
1536+
/// <param name="newValue">The new property value being set.</param>
1537+
/// <remarks>This method is invoked right before the value of <see cref="A"/> is changed.</remarks>
1538+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
1539+
partial void OnAChanging(object? oldValue, object? newValue);
1540+
/// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
1541+
/// <param name="value">The new property value that was set.</param>
1542+
/// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
1543+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
1544+
partial void OnAChanged(object? value);
1545+
/// <summary>Executes the logic for when <see cref="A"/> just changed.</summary>
1546+
/// <param name="oldValue">The previous property value that was replaced.</param>
1547+
/// <param name="newValue">The new property value that was set.</param>
1548+
/// <remarks>This method is invoked right after the value of <see cref="A"/> is changed.</remarks>
1549+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
1550+
partial void OnAChanged(object? oldValue, object? newValue);
1551+
}
1552+
}
1553+
""";
1554+
1555+
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
1556+
}
1557+
12861558
// See https://github.com/CommunityToolkit/dotnet/issues/632
12871559
[TestMethod]
12881560
public void RelayCommandMethodWithPartialDeclarations_TriggersCorrectly()
@@ -1450,6 +1722,96 @@ partial class MyViewModel
14501722
VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test1.g.cs", result1), ("MyApp.MyViewModel.Test2.g.cs", result2));
14511723
}
14521724

1725+
// See https://github.com/CommunityToolkit/dotnet/issues/681
1726+
[TestMethod]
1727+
public void RelayCommandMethodWithForwardedAttributesWithEnumValues_WorksCorrectly()
1728+
{
1729+
string source = """
1730+
using CommunityToolkit.Mvvm.Input;
1731+
1732+
#nullable enable
1733+
1734+
namespace MyApp;
1735+
1736+
partial class MyViewModel
1737+
{
1738+
[RelayCommand]
1739+
[field: DefaultValue(NegativeEnum1.Problem)]
1740+
[field: DefaultValue(NegativeEnum2.Problem)]
1741+
[field: DefaultValue(NegativeEnum3.Problem)]
1742+
[field: DefaultValue(NegativeEnum4.Problem)]
1743+
[property: DefaultValue(NegativeEnum1.Problem)]
1744+
[property: DefaultValue(NegativeEnum2.Problem)]
1745+
[property: DefaultValue(NegativeEnum3.Problem)]
1746+
[property: DefaultValue(NegativeEnum4.Problem)]
1747+
private void Test()
1748+
{
1749+
}
1750+
}
1751+
1752+
public class DefaultValueAttribute : Attribute
1753+
{
1754+
public DefaultValueAttribute(object value)
1755+
{
1756+
}
1757+
}
1758+
1759+
public enum NegativeEnum1
1760+
{
1761+
Problem = -1073741824,
1762+
OK = 0
1763+
}
1764+
1765+
public enum NegativeEnum2 : long
1766+
{
1767+
Problem = long.MinValue,
1768+
OK = 0
1769+
}
1770+
1771+
public enum NegativeEnum3 : short
1772+
{
1773+
Problem = -1234,
1774+
OK = 0
1775+
}
1776+
1777+
public enum NegativeEnum4 : sbyte
1778+
{
1779+
Problem = -1,
1780+
OK = 0
1781+
}
1782+
""";
1783+
1784+
string result = """
1785+
// <auto-generated/>
1786+
#pragma warning disable
1787+
#nullable enable
1788+
namespace MyApp
1789+
{
1790+
/// <inheritdoc/>
1791+
partial class MyViewModel
1792+
{
1793+
/// <summary>The backing field for <see cref="TestCommand"/>.</summary>
1794+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.2.0.0")]
1795+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)-1073741824)]
1796+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)-9223372036854775808)]
1797+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)-1234)]
1798+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)-1)]
1799+
private global::CommunityToolkit.Mvvm.Input.RelayCommand? testCommand;
1800+
/// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand"/> instance wrapping <see cref="Test"/>.</summary>
1801+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.2.0.0")]
1802+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
1803+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum1)-1073741824)]
1804+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum2)-9223372036854775808)]
1805+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum3)-1234)]
1806+
[global::MyApp.DefaultValueAttribute((global::MyApp.NegativeEnum4)-1)]
1807+
public global::CommunityToolkit.Mvvm.Input.IRelayCommand TestCommand => testCommand ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test));
1808+
}
1809+
}
1810+
""";
1811+
1812+
VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test.g.cs", result));
1813+
}
1814+
14531815
[TestMethod]
14541816
public void ObservablePropertyWithinGenericAndNestedTypes()
14551817
{

0 commit comments

Comments
 (0)