Skip to content

Commit d3250b6

Browse files
authored
Merge pull request #302 from CommunityToolkit/dev/fix-command-canexecute-nullability
Fix [RelayCommand] CanExecute when nullable arguments are used
2 parents 08bf899 + 01e78f5 commit d3250b6

File tree

2 files changed

+71
-16
lines changed

2 files changed

+71
-16
lines changed

CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.Execute.cs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ internal static class Execute
5555
out string? delegateType,
5656
out bool supportsCancellation,
5757
out ImmutableArray<string> commandTypeArguments,
58-
out ImmutableArray<string> delegateTypeArguments))
58+
out ImmutableArray<string> commandTypeArgumentsWithNullabilityAnnotations,
59+
out ImmutableArray<string> delegateTypeArgumentsWithNullabilityAnnotations))
5960
{
6061
goto Failure;
6162
}
@@ -115,8 +116,8 @@ internal static class Execute
115116
commandInterfaceType,
116117
commandClassType,
117118
delegateType,
118-
commandTypeArguments,
119-
delegateTypeArguments,
119+
commandTypeArgumentsWithNullabilityAnnotations,
120+
delegateTypeArgumentsWithNullabilityAnnotations,
120121
canExecuteMemberName,
121122
canExecuteExpressionType,
122123
allowConcurrentExecutions,
@@ -444,7 +445,8 @@ public static (string FieldName, string PropertyName) GetGeneratedFieldAndProper
444445
/// <param name="delegateType">The delegate type name for the wrapped method.</param>
445446
/// <param name="supportsCancellation">Indicates whether or not the resulting command supports cancellation.</param>
446447
/// <param name="commandTypeArguments">The type arguments for <paramref name="commandInterfaceType"/> and <paramref name="commandClassType"/>, if any.</param>
447-
/// <param name="delegateTypeArguments">The type arguments for <paramref name="delegateType"/>, if any.</param>
448+
/// <param name="commandTypeArgumentsWithNullabilityAnnotations">Same as <paramref name="commandTypeArguments"/>, but with nullability annotations.</param>
449+
/// <param name="delegateTypeArgumentsWithNullabilityAnnotations">The type arguments for <paramref name="delegateType"/>, if any, with nullability annotations.</param>
448450
/// <returns>Whether or not <paramref name="methodSymbol"/> was valid and the requested types have been set.</returns>
449451
private static bool TryMapCommandTypesFromMethod(
450452
IMethodSymbol methodSymbol,
@@ -454,7 +456,8 @@ private static bool TryMapCommandTypesFromMethod(
454456
[NotNullWhen(true)] out string? delegateType,
455457
out bool supportsCancellation,
456458
out ImmutableArray<string> commandTypeArguments,
457-
out ImmutableArray<string> delegateTypeArguments)
459+
out ImmutableArray<string> commandTypeArgumentsWithNullabilityAnnotations,
460+
out ImmutableArray<string> delegateTypeArgumentsWithNullabilityAnnotations)
458461
{
459462
// Map <void, void> to IRelayCommand, RelayCommand, Action
460463
if (methodSymbol.ReturnsVoid && methodSymbol.Parameters.Length == 0)
@@ -463,8 +466,9 @@ private static bool TryMapCommandTypesFromMethod(
463466
commandClassType = "global::CommunityToolkit.Mvvm.Input.RelayCommand";
464467
delegateType = "global::System.Action";
465468
supportsCancellation = false;
466-
commandTypeArguments = ImmutableArray<string>.Empty;
467-
delegateTypeArguments = ImmutableArray<string>.Empty;
469+
commandTypeArguments = ImmutableArray<string>.Empty;
470+
commandTypeArgumentsWithNullabilityAnnotations = ImmutableArray<string>.Empty;
471+
delegateTypeArgumentsWithNullabilityAnnotations = ImmutableArray<string>.Empty;
468472

469473
return true;
470474
}
@@ -478,8 +482,9 @@ private static bool TryMapCommandTypesFromMethod(
478482
commandClassType = "global::CommunityToolkit.Mvvm.Input.RelayCommand";
479483
delegateType = "global::System.Action";
480484
supportsCancellation = false;
481-
commandTypeArguments = ImmutableArray.Create(parameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations());
482-
delegateTypeArguments = ImmutableArray.Create(parameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations());
485+
commandTypeArguments = ImmutableArray.Create(parameter.Type.GetFullyQualifiedName());
486+
commandTypeArgumentsWithNullabilityAnnotations = ImmutableArray.Create(parameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations());
487+
delegateTypeArgumentsWithNullabilityAnnotations = ImmutableArray.Create(parameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations());
483488

484489
return true;
485490
}
@@ -496,7 +501,8 @@ private static bool TryMapCommandTypesFromMethod(
496501
delegateType = "global::System.Func";
497502
supportsCancellation = false;
498503
commandTypeArguments = ImmutableArray<string>.Empty;
499-
delegateTypeArguments = ImmutableArray.Create("global::System.Threading.Tasks.Task");
504+
commandTypeArgumentsWithNullabilityAnnotations = ImmutableArray<string>.Empty;
505+
delegateTypeArgumentsWithNullabilityAnnotations = ImmutableArray.Create("global::System.Threading.Tasks.Task");
500506

501507
return true;
502508
}
@@ -512,7 +518,8 @@ private static bool TryMapCommandTypesFromMethod(
512518
delegateType = "global::System.Func";
513519
supportsCancellation = true;
514520
commandTypeArguments = ImmutableArray<string>.Empty;
515-
delegateTypeArguments = ImmutableArray.Create("global::System.Threading.CancellationToken", "global::System.Threading.Tasks.Task");
521+
commandTypeArgumentsWithNullabilityAnnotations = ImmutableArray<string>.Empty;
522+
delegateTypeArgumentsWithNullabilityAnnotations = ImmutableArray.Create("global::System.Threading.CancellationToken", "global::System.Threading.Tasks.Task");
516523

517524
return true;
518525
}
@@ -522,8 +529,9 @@ private static bool TryMapCommandTypesFromMethod(
522529
commandClassType = "global::CommunityToolkit.Mvvm.Input.AsyncRelayCommand";
523530
delegateType = "global::System.Func";
524531
supportsCancellation = false;
525-
commandTypeArguments = ImmutableArray.Create(singleParameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations());
526-
delegateTypeArguments = ImmutableArray.Create(singleParameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations(), "global::System.Threading.Tasks.Task");
532+
commandTypeArguments = ImmutableArray.Create(singleParameter.Type.GetFullyQualifiedName());
533+
commandTypeArgumentsWithNullabilityAnnotations = ImmutableArray.Create(singleParameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations());
534+
delegateTypeArgumentsWithNullabilityAnnotations = ImmutableArray.Create(singleParameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations(), "global::System.Threading.Tasks.Task");
527535

528536
return true;
529537
}
@@ -538,8 +546,9 @@ private static bool TryMapCommandTypesFromMethod(
538546
commandClassType = "global::CommunityToolkit.Mvvm.Input.AsyncRelayCommand";
539547
delegateType = "global::System.Func";
540548
supportsCancellation = true;
541-
commandTypeArguments = ImmutableArray.Create(firstParameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations());
542-
delegateTypeArguments = ImmutableArray.Create(firstParameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations(), "global::System.Threading.CancellationToken", "global::System.Threading.Tasks.Task");
549+
commandTypeArguments = ImmutableArray.Create(firstParameter.Type.GetFullyQualifiedName());
550+
commandTypeArgumentsWithNullabilityAnnotations = ImmutableArray.Create(firstParameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations());
551+
delegateTypeArgumentsWithNullabilityAnnotations = ImmutableArray.Create(firstParameter.Type.GetFullyQualifiedNameWithNullabilityAnnotations(), "global::System.Threading.CancellationToken", "global::System.Threading.Tasks.Task");
543552

544553
return true;
545554
}
@@ -552,7 +561,8 @@ private static bool TryMapCommandTypesFromMethod(
552561
delegateType = null;
553562
supportsCancellation = false;
554563
commandTypeArguments = ImmutableArray<string>.Empty;
555-
delegateTypeArguments = ImmutableArray<string>.Empty;
564+
commandTypeArgumentsWithNullabilityAnnotations = ImmutableArray<string>.Empty;
565+
delegateTypeArgumentsWithNullabilityAnnotations = ImmutableArray<string>.Empty;
556566

557567
return false;
558568
}

tests/CommunityToolkit.Mvvm.UnitTests/Test_RelayCommandAttribute.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Reflection;
99
using System.Threading;
1010
using System.Threading.Tasks;
11+
using System.Windows.Input;
1112
using CommunityToolkit.Mvvm.ComponentModel;
1213
using CommunityToolkit.Mvvm.Input;
1314
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -554,6 +555,17 @@ static void AssertOptionsOfT<T>(IAsyncRelayCommand<T> command, AsyncRelayCommand
554555
AssertOptionsOfT(model.OfTAndAllowConcurrentExecutionsAndFlowExceptionsToTaskSchedulerCommand, AsyncRelayCommandOptions.AllowConcurrentExecutions | AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
555556
}
556557

558+
// See https://github.com/CommunityToolkit/dotnet/issues/294
559+
[TestMethod]
560+
public void Test_RelayCommandAttribute_CanExecuteWithNullabilityAnnotations()
561+
{
562+
ModelWithCommandWithNullableCanExecute model = new();
563+
564+
Assert.IsTrue(model.DoSomething1Command.CanExecute("Hello"));
565+
Assert.IsTrue(model.DoSomething2Command.CanExecute("Hello"));
566+
Assert.IsTrue(model.DoSomething3Command.CanExecute((0, "Hello")));
567+
}
568+
557569
#region Region
558570
public class Region
559571
{
@@ -994,4 +1006,37 @@ private Task OfTAndAllowConcurrentExecutionsAndFlowExceptionsToTaskScheduler(str
9941006
return Task.CompletedTask;
9951007
}
9961008
}
1009+
1010+
partial class ModelWithCommandWithNullableCanExecute
1011+
{
1012+
bool CanDoSomething1(string? parameter)
1013+
{
1014+
return !string.IsNullOrEmpty(parameter);
1015+
}
1016+
1017+
[RelayCommand(CanExecute = (nameof(CanDoSomething1)))]
1018+
private void DoSomething1(string? parameter)
1019+
{
1020+
}
1021+
1022+
bool CanDoSomething2(string parameter)
1023+
{
1024+
return !string.IsNullOrEmpty(parameter);
1025+
}
1026+
1027+
[RelayCommand(CanExecute = (nameof(CanDoSomething2)))]
1028+
private void DoSomething2(string? parameter)
1029+
{
1030+
}
1031+
1032+
bool CanDoSomething3((int A, string? B) parameter)
1033+
{
1034+
return !string.IsNullOrEmpty(parameter.B);
1035+
}
1036+
1037+
[RelayCommand(CanExecute = (nameof(CanDoSomething3)))]
1038+
private void DoSomething3((int A, string? B) parameter)
1039+
{
1040+
}
1041+
}
9971042
}

0 commit comments

Comments
 (0)