diff --git a/src/Controls/src/BindingSourceGen/BindingCodeWriter.cs b/src/Controls/src/BindingSourceGen/BindingCodeWriter.cs index d2c18f8334d7..360a4e14743e 100644 --- a/src/Controls/src/BindingSourceGen/BindingCodeWriter.cs +++ b/src/Controls/src/BindingSourceGen/BindingCodeWriter.cs @@ -45,7 +45,7 @@ namespace Microsoft.Maui.Controls.Generated using System.CodeDom.Compiler; {{GeneratedCodeAttribute}} - internal static partial class GeneratedBindableObjectExtensions + internal static partial class GeneratedBindingInterceptors { private static bool ShouldUseSetter(BindingMode mode, BindableProperty bindableProperty) => mode == BindingMode.OneWayToSource @@ -53,6 +53,11 @@ private static bool ShouldUseSetter(BindingMode mode, BindableProperty bindableP || (mode == BindingMode.Default && (bindableProperty.DefaultBindingMode == BindingMode.OneWayToSource || bindableProperty.DefaultBindingMode == BindingMode.TwoWay)); + + private static bool ShouldUseSetter(BindingMode mode) + => mode == BindingMode.OneWayToSource + || mode == BindingMode.TwoWay + || mode == BindingMode.Default; } } """; @@ -75,15 +80,14 @@ namespace Microsoft.Maui.Controls.Generated using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; - internal static partial class GeneratedBindableObjectExtensions + internal static partial class GeneratedBindingInterceptors { {{bindingMethodBody}} } } """; - - public static string GenerateBinding(SetBindingInvocationDescription binding, uint id) + public static string GenerateBinding(BindingInvocationDescription binding, uint id) { if (!binding.NullableContextEnabled) { @@ -95,7 +99,7 @@ public static string GenerateBinding(SetBindingInvocationDescription binding, ui return GenerateBindingCode(bindingMethod); } - private static string GenerateBindingMethod(SetBindingInvocationDescription binding, uint id) + private static string GenerateBindingMethod(BindingInvocationDescription binding, uint id) { using var builder = new BindingInterceptorCodeBuilder(indent: 2); builder.AppendSetBindingInterceptor(id: id, binding: binding); @@ -119,11 +123,11 @@ public BindingInterceptorCodeBuilder(int indent = 0) _indentedTextWriter = new IndentedTextWriter(_stringWriter, "\t") { Indent = indent }; } - public void AppendSetBindingInterceptor(uint id, SetBindingInvocationDescription binding) + public void AppendSetBindingInterceptor(uint id, BindingInvocationDescription binding) { AppendLine(GeneratedCodeAttribute); AppendInterceptorAttribute(binding.Location); - Append($"public static void SetBinding{id}"); + AppendMethodName(binding, id); if (binding.SourceType.IsGenericParameter && binding.PropertyType.IsGenericParameter) { Append($"<{binding.SourceType}, {binding.PropertyType}>"); @@ -138,20 +142,12 @@ public void AppendSetBindingInterceptor(uint id, SetBindingInvocationDescription } AppendLine('('); + AppendFunctionArguments(binding); + AppendLines($$""" - this BindableObject bindableObject, - BindableProperty bindableProperty, - Func<{{binding.SourceType}}, {{binding.PropertyType}}> getter, - BindingMode mode = BindingMode.Default, - IValueConverter? converter = null, - object? converterParameter = null, - string? stringFormat = null, - object? source = null, - object? fallbackValue = null, - object? targetNullValue = null) { Action<{{binding.SourceType}}, {{binding.PropertyType}}>? setter = null; - if (ShouldUseSetter(mode, bindableProperty)) + if ({{GetShouldUseSetterCall(binding.MethodType)}}) { """); @@ -173,8 +169,7 @@ public void AppendSetBindingInterceptor(uint id, SetBindingInvocationDescription } else { - // TODO is this too strict? I believe today when the Binding can't write to the property, it just silently ignores the value - AppendLine("throw new InvalidOperationException(\"Cannot set value on the source object.\");"); // TODO improve exception wording + AppendLine("throw new InvalidOperationException(\"Cannot set value on the source object.\");"); } Unindent(); @@ -209,18 +204,67 @@ public void AppendSetBindingInterceptor(uint id, SetBindingInvocationDescription FallbackValue = fallbackValue, TargetNullValue = targetNullValue }; - - bindableObject.SetBinding(bindableProperty, binding); + + {{GetEpilog(binding.MethodType)}} } """); } + private void AppendFunctionArguments(BindingInvocationDescription binding) + { + if (binding.MethodType == InterceptedMethodType.SetBinding) + { + AppendLines($$""" + this BindableObject bindableObject, + BindableProperty bindableProperty, + """); + + } + AppendLines($$""" + Func<{{binding.SourceType}}, {{binding.PropertyType}}> getter, + BindingMode mode = BindingMode.Default, + IValueConverter? converter = null, + object? converterParameter = null, + string? stringFormat = null, + object? source = null, + object? fallbackValue = null, + object? targetNullValue = null) + """); + } + + private static string GetEpilog(InterceptedMethodType interceptedMethodType) => + interceptedMethodType switch + { + InterceptedMethodType.SetBinding => "bindableObject.SetBinding(bindableProperty, binding);", + InterceptedMethodType.Create => "return binding;", + _ => throw new ArgumentOutOfRangeException(nameof(interceptedMethodType)) + }; + + private static string GetShouldUseSetterCall(InterceptedMethodType interceptedMethodType) => + interceptedMethodType switch + { + InterceptedMethodType.SetBinding => "ShouldUseSetter(mode, bindableProperty)", + InterceptedMethodType.Create => "ShouldUseSetter(mode)", + _ => throw new ArgumentOutOfRangeException(nameof(interceptedMethodType)) + }; + + + private void AppendMethodName(BindingInvocationDescription binding, uint id) + { + Append(binding.MethodType switch + { + InterceptedMethodType.SetBinding => $"public static void SetBinding{id}", + InterceptedMethodType.Create => $"public static TypedBinding<{binding.SourceType}, {binding.PropertyType}> Create{id}", + _ => throw new ArgumentOutOfRangeException(nameof(binding.MethodType)) + }); + } + private void AppendInterceptorAttribute(InterceptorLocation location) { AppendLine($"[InterceptsLocationAttribute(@\"{location.FilePath}\", {location.Line}, {location.Column})]"); } - private void AppendSetterAction(SetBindingInvocationDescription binding, string sourceVariableName = "source", string valueVariableName = "value") + private void AppendSetterAction(BindingInvocationDescription binding, string sourceVariableName = "source", string valueVariableName = "value") { var assignedValueExpression = valueVariableName; @@ -283,7 +327,7 @@ private void AppendSetterAction(SetBindingInvocationDescription binding, string } } - private void AppendHandlersArray(SetBindingInvocationDescription binding) + private void AppendHandlersArray(BindingInvocationDescription binding) { AppendLine($"new Tuple, string>[]"); AppendLine('{'); @@ -296,7 +340,6 @@ private void AppendHandlersArray(SetBindingInvocationDescription binding) { var previousExpression = nextExpression; nextExpression = AccessExpressionBuilder.ExtendExpression(previousExpression, MaybeWrapInConditionalAccess(part, forceConditonalAccessToNextPart)); - var isNullableReferenceType = part is MemberAccess memberAccess && !memberAccess.IsValueType; forceConditonalAccessToNextPart = part is Cast; // Some parts don't have a property name, so we can't generate a handler for them (for example casts) diff --git a/src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs b/src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs index 360bddc43794..64f72a42b4c8 100644 --- a/src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs +++ b/src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs @@ -10,7 +10,7 @@ public class BindingSourceGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { var bindingsWithDiagnostics = context.SyntaxProvider.CreateSyntaxProvider( - predicate: static (node, _) => IsSetBindingMethod(node), + predicate: static (node, _) => IsSetBindingMethod(node) || IsCreateMethod(node), transform: static (ctx, t) => GetBindingForGeneration(ctx, t) ) .WithTrackingName(TrackingNames.BindingsWithDiagnostics); @@ -30,12 +30,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.RegisterPostInitializationOutput(spc => { - spc.AddSource("GeneratedBindableObjectExtensionsCommon.g.cs", BindingCodeWriter.GenerateCommonCode()); + spc.AddSource("GeneratedBindingInterceptorsCommon.g.cs", BindingCodeWriter.GenerateCommonCode()); }); context.RegisterImplementationSourceOutput(bindings, (spc, binding) => { - var fileName = $"{binding.Location.FilePath}-GeneratedBindableObjectExtensions-{binding.Location.Line}-{binding.Location.Column}.g.cs"; + var fileName = $"{binding.Location.FilePath}-GeneratedBindingInterceptors-{binding.Location.Line}-{binding.Location.Column}.g.cs"; var sanitizedFileName = fileName.Replace('/', '-').Replace('\\', '-').Replace(':', '-'); var code = BindingCodeWriter.GenerateBinding(binding, (uint)Math.Abs(binding.Location.GetHashCode())); spc.AddSource(sanitizedFileName, code); @@ -52,65 +52,82 @@ private static bool IsSetBindingMethod(SyntaxNode node) && invocation.ArgumentList.Arguments[1].Expression is not ObjectCreationExpressionSyntax; } - private static Result GetBindingForGeneration(GeneratorSyntaxContext context, CancellationToken t) + private static bool IsCreateMethod(SyntaxNode node) + { + return node is InvocationExpressionSyntax invocation + && invocation.Expression is MemberAccessExpressionSyntax method + && method.Name.Identifier.Text == "Create" + && invocation.ArgumentList.Arguments.Count >= 1 + && invocation.ArgumentList.Arguments[0].Expression is not LiteralExpressionSyntax + && invocation.ArgumentList.Arguments[0].Expression is not ObjectCreationExpressionSyntax; + } + + private static Result GetBindingForGeneration(GeneratorSyntaxContext context, CancellationToken t) { - var diagnostics = new List(); var enabledNullable = IsNullableContextEnabled(context); var invocation = (InvocationExpressionSyntax)context.Node; var method = (MemberAccessExpressionSyntax)invocation.Expression; + var interceptedMethodType = method.Name.Identifier.Text switch + { + "SetBinding" => InterceptedMethodType.SetBinding, + "Create" => InterceptedMethodType.Create, + _ => throw new NotSupportedException() + }; + var sourceCodeLocation = SourceCodeLocation.CreateFrom(method.Name.GetLocation()); if (sourceCodeLocation == null) { - return Result.Failure(DiagnosticsFactory.UnableToResolvePath(invocation.GetLocation())); + return Result.Failure(DiagnosticsFactory.UnableToResolvePath(invocation.GetLocation())); } - var overloadDiagnostics = new EquatableArray(VerifyCorrectOverload(invocation, context, t)); + var overloadDiagnostics = VerifyCorrectOverload(invocation, interceptedMethodType, context, t); if (overloadDiagnostics.Length > 0) { - return Result.Failure(overloadDiagnostics); + return Result.Failure(new EquatableArray(overloadDiagnostics)); } - var lambdaResult = ExtractLambda(invocation); + var lambdaResult = ExtractLambda(invocation, interceptedMethodType); if (lambdaResult.HasDiagnostics) { - return Result.Failure(lambdaResult.Diagnostics); + return Result.Failure(lambdaResult.Diagnostics); } var lambdaBodyResult = ExtractLambdaBody(lambdaResult.Value); if (lambdaBodyResult.HasDiagnostics) { - return Result.Failure(lambdaBodyResult.Diagnostics); + return Result.Failure(lambdaBodyResult.Diagnostics); } var lambdaSymbolResult = GetLambdaSymbol(lambdaResult.Value, context.SemanticModel); if (lambdaSymbolResult.HasDiagnostics) { - return Result.Failure(lambdaSymbolResult.Diagnostics); + return Result.Failure(lambdaSymbolResult.Diagnostics); } var lambdaTypeInfo = context.SemanticModel.GetTypeInfo(lambdaBodyResult.Value, t); if (lambdaTypeInfo.Type == null) { - return Result.Failure(DiagnosticsFactory.UnableToResolvePath(lambdaBodyResult.Value.GetLocation())); + return Result.Failure(DiagnosticsFactory.UnableToResolvePath(lambdaBodyResult.Value.GetLocation())); } var pathParser = new PathParser(context, enabledNullable); var pathParseResult = pathParser.ParsePath(lambdaBodyResult.Value); if (pathParseResult.HasDiagnostics) { - return Result.Failure(pathParseResult.Diagnostics); + return Result.Failure(pathParseResult.Diagnostics); } - var binding = new SetBindingInvocationDescription( + var binding = new BindingInvocationDescription( Location: sourceCodeLocation.ToInterceptorLocation(), SourceType: BindingGenerationUtilities.CreateTypeDescription(lambdaSymbolResult.Value.Parameters[0].Type, enabledNullable), PropertyType: BindingGenerationUtilities.CreateTypeDescription(lambdaTypeInfo.Type, enabledNullable), Path: new EquatableArray([.. pathParseResult.Value]), SetterOptions: DeriveSetterOptions(lambdaBodyResult.Value, context.SemanticModel, enabledNullable), - NullableContextEnabled: enabledNullable); - return Result.Success(binding); + NullableContextEnabled: enabledNullable, + MethodType: interceptedMethodType); + return Result.Success(binding); } private static bool IsNullableContextEnabled(GeneratorSyntaxContext context) @@ -119,39 +136,108 @@ private static bool IsNullableContextEnabled(GeneratorSyntaxContext context) return (nullableContext & NullableContext.Enabled) == NullableContext.Enabled; } - private static DiagnosticInfo[] VerifyCorrectOverload(InvocationExpressionSyntax invocation, GeneratorSyntaxContext context, CancellationToken t) - { + private static DiagnosticInfo[] VerifyCorrectOverload(InvocationExpressionSyntax invocation, InterceptedMethodType methodType, GeneratorSyntaxContext context, CancellationToken t) => + methodType switch + { + InterceptedMethodType.SetBinding => VerifyCorrectOverloadSetBinding(invocation, context, t), + InterceptedMethodType.Create => VerifyCorrectOverloadBindingCreate(invocation, context, t), + _ => throw new NotSupportedException() + }; + + + private static DiagnosticInfo[] VerifyCorrectOverloadBindingCreate(InvocationExpressionSyntax invocation, GeneratorSyntaxContext context, CancellationToken t){ var argumentList = invocation.ArgumentList.Arguments; - if (argumentList.Count < 2) + + var symbol = context.SemanticModel.GetSymbolInfo(invocation.Expression).Symbol; + if (symbol?.ContainingType?.Name != "Binding" || symbol?.ContainingType?.ContainingNamespace.ToDisplayString() is not "Microsoft.Maui.Controls") + { + return [DiagnosticsFactory.SuboptimalSetBindingOverload(invocation.GetLocation())]; + } + + if (argumentList.Count == 0) { throw new ArgumentOutOfRangeException(nameof(invocation)); } - var secondArgument = argumentList[1].Expression; - if (secondArgument is LambdaExpressionSyntax) + var firstArgument = argumentList[0].Expression; + if (firstArgument is IdentifierNameSyntax) { - return []; + var type = context.SemanticModel.GetTypeInfo(firstArgument, cancellationToken: t).Type; + if (type != null && type.Name == "Func") + { + return [DiagnosticsFactory.GetterIsNotLambda(firstArgument.GetLocation())]; + } + else // String and Binding + { + return [DiagnosticsFactory.SuboptimalSetBindingOverload(firstArgument.GetLocation())]; + } } - var secondArgumentType = context.SemanticModel.GetTypeInfo(secondArgument, cancellationToken: t).Type; - return secondArgumentType switch + return []; + } + + private static DiagnosticInfo[] VerifyCorrectOverloadSetBinding(InvocationExpressionSyntax invocation, GeneratorSyntaxContext context, CancellationToken t) + { + var symbol = context.SemanticModel.GetSymbolInfo(invocation.Expression).Symbol; + if (symbol is not null) { - { Name: "Func", ContainingNamespace.Name: "System" } => [DiagnosticsFactory.GetterIsNotLambda(secondArgument.GetLocation())], - _ => [DiagnosticsFactory.SuboptimalSetBindingOverload(secondArgument.GetLocation())], - }; + if (symbol is not IMethodSymbol methodSymbol + || methodSymbol.Kind != SymbolKind.Method + || methodSymbol.Name != "SetBinding" + || !methodSymbol.IsGenericMethod + || methodSymbol.TypeParameters.Length != 2 + || methodSymbol.Parameters.Length != 9 + || methodSymbol.ContainingType?.Name != "BindableObjectExtensions" + || methodSymbol.ContainingType?.ContainingNamespace.ToDisplayString() is not "Microsoft.Maui.Controls") + { + // ignore this method invocation + return [DiagnosticsFactory.SuboptimalSetBindingOverload(invocation.GetLocation())]; + } + } + else + { + // It is not possible to resolve the method symbol when the bindable object (the first argument or the object that the extension method + // is called on) is referenced by a field that will be generated via XamlG based on the x:Name attributes. In that case, this source generator + // cannot see the outputs of the other source generator and we have incomplete information about the method invocation and we can only work with + // the syntax tree and not the semantic model. + + var argumentsList = invocation.ArgumentList.Arguments; + if (argumentsList.Count < 2) + { + return [DiagnosticsFactory.SuboptimalSetBindingOverload(invocation.GetLocation())]; + } + + var secondArgument = argumentsList[1].Expression; + if (secondArgument is not LambdaExpressionSyntax) + { + var secondArgumentType = context.SemanticModel.GetTypeInfo(secondArgument, cancellationToken: t).Type; + return secondArgumentType switch + { + { Name: "Func", ContainingNamespace.Name: "System" } => [DiagnosticsFactory.GetterIsNotLambda(secondArgument.GetLocation())], + _ => [DiagnosticsFactory.SuboptimalSetBindingOverload(secondArgument.GetLocation())], + }; + } + } + + return []; } - private static Result ExtractLambda(InvocationExpressionSyntax invocation) + private static Result ExtractLambda(InvocationExpressionSyntax invocation, InterceptedMethodType methodType) { var argumentList = invocation.ArgumentList.Arguments; - var lambda = argumentList[1].Expression; + var lambda = methodType switch + { + InterceptedMethodType.SetBinding => argumentList[1].Expression, + InterceptedMethodType.Create => argumentList[0].Expression, + _ => throw new NotSupportedException() + }; if (lambda is not LambdaExpressionSyntax lambdaExpression) { return Result.Failure(DiagnosticsFactory.GetterIsNotLambda(lambda.GetLocation())); } - return Result.Success(lambdaExpression); + return Result.Success(lambdaExpression); } private static Result ExtractLambdaBody(LambdaExpressionSyntax lambdaExpression) @@ -159,8 +245,8 @@ private static Result ExtractLambdaBody(LambdaExpressionSyntax if (lambdaExpression.Body is not ExpressionSyntax lambdaBody) { return Result.Failure(DiagnosticsFactory.GetterLambdaBodyIsNotExpression(lambdaExpression.Body.GetLocation())); - } + return Result.Success(lambdaBody); } diff --git a/src/Controls/src/BindingSourceGen/BindingTransformer.cs b/src/Controls/src/BindingSourceGen/BindingTransformer.cs index 4056b52e068f..7e5aa71f9705 100644 --- a/src/Controls/src/BindingSourceGen/BindingTransformer.cs +++ b/src/Controls/src/BindingSourceGen/BindingTransformer.cs @@ -3,23 +3,23 @@ namespace Microsoft.Maui.Controls.BindingSourceGen; public interface IBindingInvocationTransformer { - SetBindingInvocationDescription Transform(SetBindingInvocationDescription setBindingInvocationDescription); + BindingInvocationDescription Transform(BindingInvocationDescription BindingInvocationDescription); } public class ReferenceTypesConditionalAccessTransformer : IBindingInvocationTransformer { - public SetBindingInvocationDescription Transform(SetBindingInvocationDescription setBindingInvocationDescription) + public BindingInvocationDescription Transform(BindingInvocationDescription BindingInvocationDescription) { - var path = TransformPath(setBindingInvocationDescription); - return setBindingInvocationDescription with { Path = path }; + var path = TransformPath(BindingInvocationDescription); + return BindingInvocationDescription with { Path = path }; } - private static EquatableArray TransformPath(SetBindingInvocationDescription setBindingInvocationDescription) + private static EquatableArray TransformPath(BindingInvocationDescription BindingInvocationDescription) { var newPath = new List(); - foreach (var pathPart in setBindingInvocationDescription.Path) + foreach (var pathPart in BindingInvocationDescription.Path) { - var sourceIsReferenceType = newPath.Count == 0 && !setBindingInvocationDescription.SourceType.IsValueType; + var sourceIsReferenceType = newPath.Count == 0 && !BindingInvocationDescription.SourceType.IsValueType; var previousPartIsReferenceType = newPath.Count > 0 && PreviousPartIsReferenceType(newPath.Last()); if (pathPart is not MemberAccess && pathPart is not IndexAccess) diff --git a/src/Controls/src/BindingSourceGen/GeneratorDataModels.cs b/src/Controls/src/BindingSourceGen/GeneratorDataModels.cs index 1b70c45f1117..a09fc257d32f 100644 --- a/src/Controls/src/BindingSourceGen/GeneratorDataModels.cs +++ b/src/Controls/src/BindingSourceGen/GeneratorDataModels.cs @@ -3,18 +3,25 @@ namespace Microsoft.Maui.Controls.BindingSourceGen; +public enum InterceptedMethodType +{ + SetBinding, + Create +} + public class TrackingNames { public const string BindingsWithDiagnostics = nameof(BindingsWithDiagnostics); public const string Bindings = nameof(Bindings); } -public sealed record SetBindingInvocationDescription( +public sealed record BindingInvocationDescription( InterceptorLocation Location, TypeDescription SourceType, TypeDescription PropertyType, EquatableArray Path, SetterOptions SetterOptions, - bool NullableContextEnabled); + bool NullableContextEnabled, + InterceptedMethodType MethodType); public sealed record SourceCodeLocation(string FilePath, TextSpan TextSpan, LinePositionSpan LineSpan) { diff --git a/src/Controls/src/Core/BindableObjectExtensions.cs b/src/Controls/src/Core/BindableObjectExtensions.cs index 833c02517ab8..668bde3aa9b5 100644 --- a/src/Controls/src/Core/BindableObjectExtensions.cs +++ b/src/Controls/src/Core/BindableObjectExtensions.cs @@ -126,7 +126,6 @@ public static void SetBinding(this BindableObject self, BindableProperty targetP /// The value to use instead of the default value for the property, if no specified value exists. /// The value to supply for a bound property when the target of the binding is . /// - [EditorBrowsable(EditorBrowsableState.Never)] // TODO: remove the attribute once the source generator is enabled by default public static void SetBinding( this BindableObject self, BindableProperty targetProperty, diff --git a/src/Controls/src/Core/Binding.Create.cs b/src/Controls/src/Core/Binding.Create.cs new file mode 100644 index 000000000000..f712f1b449fa --- /dev/null +++ b/src/Controls/src/Core/Binding.Create.cs @@ -0,0 +1,37 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Maui.Controls +{ + partial class Binding + { + /// + /// This factory method was added to simplify creating TypedBindingBase instances from lambda getters. + /// + /// The source type. + /// The property type. + /// An getter method used to retrieve the source property. + /// The binding mode. This property is optional. Default is . + /// The converter. This parameter is optional. Default is . + /// An user-defined parameter to pass to the converter. This parameter is optional. Default is . + /// A String format. This parameter is optional. Default is . + /// An object used as the source for this binding. This parameter is optional. Default is . + /// The value to use instead of the default value for the property, if no specified value exists. + /// The value to supply for a bound property when the target of the binding is . + /// + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCodeMessage", + Justification = "The Binding.Create() method does not create an instance of Binding but an instance of TypedBinding.")] + public static BindingBase Create( + Func getter, + BindingMode mode = BindingMode.Default, + IValueConverter? converter = null, + object? converterParameter = null, + string? stringFormat = null, + object? source = null, + object? fallbackValue = null, + object? targetNullValue = null) + { + throw new InvalidOperationException($"Call to Binding.Create<{typeof(TSource)}, {typeof(TProperty)}>() was not intercepted."); + } + } +} diff --git a/src/Controls/src/Core/Binding.cs b/src/Controls/src/Core/Binding.cs index d3c0e1c2e833..b9e434889632 100644 --- a/src/Controls/src/Core/Binding.cs +++ b/src/Controls/src/Core/Binding.cs @@ -8,7 +8,7 @@ namespace Microsoft.Maui.Controls { /// [RequiresUnreferencedCode(TrimmerConstants.StringPathBindingWarning, Url = TrimmerConstants.ExpressionBasedBindingsDocsUrl)] - public sealed class Binding : BindingBase + public sealed partial class Binding : BindingBase { public const string SelfPath = "."; IValueConverter _converter; diff --git a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt index 4c8105349500..6ca2b95572bb 100644 --- a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -250,3 +250,4 @@ Microsoft.Maui.Controls.Xaml.RequireServiceAttribute ~Microsoft.Maui.Controls.ResourceDictionary.SetAndCreateSource(System.Uri value) -> void *REMOVED*~Microsoft.Maui.Controls.ResourceDictionary.SetAndLoadSource(System.Uri value, string resourcePath, System.Reflection.Assembly assembly, System.Xml.IXmlLineInfo lineInfo) -> void static Microsoft.Maui.Controls.BindableObjectExtensions.SetBinding(this Microsoft.Maui.Controls.BindableObject! self, Microsoft.Maui.Controls.BindableProperty! targetProperty, System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> void +static Microsoft.Maui.Controls.Binding.Create(System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> Microsoft.Maui.Controls.BindingBase! diff --git a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt index d050d5ef499d..044e4e82b5c5 100644 --- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -274,3 +274,4 @@ Microsoft.Maui.Controls.Xaml.RequireServiceAttribute ~Microsoft.Maui.Controls.ResourceDictionary.SetAndCreateSource(System.Uri value) -> void *REMOVED*~Microsoft.Maui.Controls.ResourceDictionary.SetAndLoadSource(System.Uri value, string resourcePath, System.Reflection.Assembly assembly, System.Xml.IXmlLineInfo lineInfo) -> void static Microsoft.Maui.Controls.BindableObjectExtensions.SetBinding(this Microsoft.Maui.Controls.BindableObject! self, Microsoft.Maui.Controls.BindableProperty! targetProperty, System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> void +static Microsoft.Maui.Controls.Binding.Create(System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> Microsoft.Maui.Controls.BindingBase! diff --git a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index 03bd016d49ea..d1771227750c 100644 --- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -274,3 +274,4 @@ Microsoft.Maui.Controls.Xaml.RequireServiceAttribute ~Microsoft.Maui.Controls.ResourceDictionary.SetAndCreateSource(System.Uri value) -> void *REMOVED*~Microsoft.Maui.Controls.ResourceDictionary.SetAndLoadSource(System.Uri value, string resourcePath, System.Reflection.Assembly assembly, System.Xml.IXmlLineInfo lineInfo) -> void static Microsoft.Maui.Controls.BindableObjectExtensions.SetBinding(this Microsoft.Maui.Controls.BindableObject! self, Microsoft.Maui.Controls.BindableProperty! targetProperty, System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> void +static Microsoft.Maui.Controls.Binding.Create(System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> Microsoft.Maui.Controls.BindingBase! diff --git a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt index 52330296498c..e32d6e8c2dc7 100644 --- a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt @@ -222,3 +222,4 @@ Microsoft.Maui.Controls.Xaml.RequireServiceAttribute ~Microsoft.Maui.Controls.ResourceDictionary.SetAndCreateSource(System.Uri value) -> void *REMOVED*~Microsoft.Maui.Controls.ResourceDictionary.SetAndLoadSource(System.Uri value, string resourcePath, System.Reflection.Assembly assembly, System.Xml.IXmlLineInfo lineInfo) -> void static Microsoft.Maui.Controls.BindableObjectExtensions.SetBinding(this Microsoft.Maui.Controls.BindableObject! self, Microsoft.Maui.Controls.BindableProperty! targetProperty, System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> void +static Microsoft.Maui.Controls.Binding.Create(System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> Microsoft.Maui.Controls.BindingBase! diff --git a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt index 134d2cfce583..2270d964161a 100644 --- a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -262,3 +262,4 @@ Microsoft.Maui.Controls.Xaml.RequireServiceAttribute ~Microsoft.Maui.Controls.ResourceDictionary.SetAndCreateSource(System.Uri value) -> void *REMOVED*~Microsoft.Maui.Controls.ResourceDictionary.SetAndLoadSource(System.Uri value, string resourcePath, System.Reflection.Assembly assembly, System.Xml.IXmlLineInfo lineInfo) -> void static Microsoft.Maui.Controls.BindableObjectExtensions.SetBinding(this Microsoft.Maui.Controls.BindableObject! self, Microsoft.Maui.Controls.BindableProperty! targetProperty, System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> void +static Microsoft.Maui.Controls.Binding.Create(System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> Microsoft.Maui.Controls.BindingBase! diff --git a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt index 334899e1ffa1..8420cbc99b5d 100644 --- a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt @@ -219,3 +219,4 @@ Microsoft.Maui.Controls.Xaml.RequireServiceAttribute ~Microsoft.Maui.Controls.ResourceDictionary.SetAndCreateSource(System.Uri value) -> void *REMOVED*~Microsoft.Maui.Controls.ResourceDictionary.SetAndLoadSource(System.Uri value, string resourcePath, System.Reflection.Assembly assembly, System.Xml.IXmlLineInfo lineInfo) -> void static Microsoft.Maui.Controls.BindableObjectExtensions.SetBinding(this Microsoft.Maui.Controls.BindableObject! self, Microsoft.Maui.Controls.BindableProperty! targetProperty, System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> void +static Microsoft.Maui.Controls.Binding.Create(System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> Microsoft.Maui.Controls.BindingBase! diff --git a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt index 212b98d66c3e..585a0675a609 100644 --- a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt @@ -219,3 +219,4 @@ Microsoft.Maui.Controls.ContentPage.HideSoftInputOnTapped.set -> void ~Microsoft.Maui.Controls.ResourceDictionary.SetAndCreateSource(System.Uri value) -> void *REMOVED*~Microsoft.Maui.Controls.ResourceDictionary.SetAndLoadSource(System.Uri value, string resourcePath, System.Reflection.Assembly assembly, System.Xml.IXmlLineInfo lineInfo) -> void static Microsoft.Maui.Controls.BindableObjectExtensions.SetBinding(this Microsoft.Maui.Controls.BindableObject! self, Microsoft.Maui.Controls.BindableProperty! targetProperty, System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> void +static Microsoft.Maui.Controls.Binding.Create(System.Func! getter, Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, Microsoft.Maui.Controls.IValueConverter? converter = null, object? converterParameter = null, string? stringFormat = null, object? source = null, object? fallbackValue = null, object? targetNullValue = null) -> Microsoft.Maui.Controls.BindingBase! diff --git a/src/Controls/tests/BindingSourceGen.UnitTests/AssertExtensions.cs b/src/Controls/tests/BindingSourceGen.UnitTests/AssertExtensions.cs index 98be295c91fa..c541f41e0d87 100644 --- a/src/Controls/tests/BindingSourceGen.UnitTests/AssertExtensions.cs +++ b/src/Controls/tests/BindingSourceGen.UnitTests/AssertExtensions.cs @@ -20,7 +20,7 @@ internal static void CodeIsEqual(string expectedCode, string actualCode) Assert.Equal(expectedLines.Count(), actualLines.Count()); } - internal static void BindingsAreEqual(SetBindingInvocationDescription expectedBinding, CodeGeneratorResult codeGeneratorResult) + internal static void BindingsAreEqual(BindingInvocationDescription expectedBinding, CodeGeneratorResult codeGeneratorResult) { AssertNoDiagnostics(codeGeneratorResult); Assert.NotNull(codeGeneratorResult.Binding); diff --git a/src/Controls/tests/BindingSourceGen.UnitTests/BindingCodeWriterTests.cs b/src/Controls/tests/BindingSourceGen.UnitTests/BindingCodeWriterTests.cs index 2940cc163727..79f77236c4aa 100644 --- a/src/Controls/tests/BindingSourceGen.UnitTests/BindingCodeWriterTests.cs +++ b/src/Controls/tests/BindingSourceGen.UnitTests/BindingCodeWriterTests.cs @@ -49,7 +49,7 @@ namespace Microsoft.Maui.Controls.Generated using System.CodeDom.Compiler; {{BindingCodeWriter.GeneratedCodeAttribute}} - internal static partial class GeneratedBindableObjectExtensions + internal static partial class GeneratedBindingInterceptors { private static bool ShouldUseSetter(BindingMode mode, BindableProperty bindableProperty) => mode == BindingMode.OneWayToSource @@ -57,6 +57,11 @@ private static bool ShouldUseSetter(BindingMode mode, BindableProperty bindableP || (mode == BindingMode.Default && (bindableProperty.DefaultBindingMode == BindingMode.OneWayToSource || bindableProperty.DefaultBindingMode == BindingMode.TwoWay)); + + private static bool ShouldUseSetter(BindingMode mode) + => mode == BindingMode.OneWayToSource + || mode == BindingMode.TwoWay + || mode == BindingMode.Default; } } """, @@ -66,7 +71,7 @@ private static bool ShouldUseSetter(BindingMode mode, BindableProperty bindableP [Fact] public void BuildsWholeBinding() { - var code = BindingCodeWriter.GenerateBinding(new SetBindingInvocationDescription( + var code = BindingCodeWriter.GenerateBinding(new BindingInvocationDescription( Location: new InterceptorLocation(FilePath: @"Path\To\Program.cs", Line: 20, Column: 30), SourceType: new TypeDescription("global::MyNamespace.MySourceClass", IsValueType: false, IsNullable: false, IsGenericParameter: false), PropertyType: new TypeDescription("global::MyNamespace.MyPropertyClass", IsValueType: false, IsNullable: false, IsGenericParameter: false), @@ -76,7 +81,8 @@ public void BuildsWholeBinding() new ConditionalAccess(new MemberAccess("C")), ]), SetterOptions: new(IsWritable: true, AcceptsNullValue: false), - NullableContextEnabled: true), id: 1); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding), id: 1); AssertExtensions.CodeIsEqual( $$""" @@ -97,7 +103,7 @@ namespace Microsoft.Maui.Controls.Generated using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; - internal static partial class GeneratedBindableObjectExtensions + internal static partial class GeneratedBindingInterceptors { {{BindingCodeWriter.GeneratedCodeAttribute}} @@ -157,7 +163,7 @@ public static void SetBinding1( public void CorrectlyFormatsSimpleBinding() { var codeBuilder = new BindingCodeWriter.BindingInterceptorCodeBuilder(); - codeBuilder.AppendSetBindingInterceptor(id: 1, new SetBindingInvocationDescription( + codeBuilder.AppendSetBindingInterceptor(id: 1, new BindingInvocationDescription( Location: new InterceptorLocation(FilePath: @"Path\To\Program.cs", Line: 20, Column: 30), SourceType: new TypeDescription("global::MyNamespace.MySourceClass", IsValueType: false, IsNullable: false, IsGenericParameter: false), PropertyType: new TypeDescription("global::MyNamespace.MyPropertyClass", IsValueType: false, IsNullable: false, IsGenericParameter: false), @@ -167,7 +173,8 @@ public void CorrectlyFormatsSimpleBinding() new ConditionalAccess(new MemberAccess("C")), ]), SetterOptions: new(IsWritable: true, AcceptsNullValue: false), - NullableContextEnabled: true)); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding)); var code = codeBuilder.ToString(); AssertExtensions.CodeIsEqual( @@ -228,7 +235,7 @@ public static void SetBinding1( public void CorrectlyFormatsBindingWithoutAnyNullablesInPath() { var codeBuilder = new BindingCodeWriter.BindingInterceptorCodeBuilder(); - codeBuilder.AppendSetBindingInterceptor(id: 1, new SetBindingInvocationDescription( + codeBuilder.AppendSetBindingInterceptor(id: 1, new BindingInvocationDescription( Location: new InterceptorLocation(FilePath: @"Path\To\Program.cs", Line: 20, Column: 30), SourceType: new TypeDescription("global::MyNamespace.MySourceClass", IsValueType: false, IsNullable: false, IsGenericParameter: false), PropertyType: new TypeDescription("global::MyNamespace.MyPropertyClass", IsValueType: false, IsNullable: false, IsGenericParameter: false), @@ -238,7 +245,8 @@ public void CorrectlyFormatsBindingWithoutAnyNullablesInPath() new MemberAccess("C"), ]), SetterOptions: new(IsWritable: true, AcceptsNullValue: false), - NullableContextEnabled: true)); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding)); var code = codeBuilder.ToString(); AssertExtensions.CodeIsEqual( @@ -295,7 +303,7 @@ public static void SetBinding1( public void CorrectlyFormatsBindingWithoutSetter() { var codeBuilder = new BindingCodeWriter.BindingInterceptorCodeBuilder(); - codeBuilder.AppendSetBindingInterceptor(id: 1, new SetBindingInvocationDescription( + codeBuilder.AppendSetBindingInterceptor(id: 1, new BindingInvocationDescription( Location: new InterceptorLocation(FilePath: @"Path\To\Program.cs", Line: 20, Column: 30), SourceType: new TypeDescription("global::MyNamespace.MySourceClass", IsNullable: false, IsGenericParameter: false, IsValueType: false), PropertyType: new TypeDescription("global::MyNamespace.MyPropertyClass", IsNullable: false, IsGenericParameter: false, IsValueType: false), @@ -305,7 +313,8 @@ public void CorrectlyFormatsBindingWithoutSetter() new MemberAccess("C"), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true)); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding)); var code = codeBuilder.ToString(); AssertExtensions.CodeIsEqual( @@ -359,7 +368,7 @@ public static void SetBinding1( public void CorrectlyFormatsBindingWithIndexers() { var codeBuilder = new BindingCodeWriter.BindingInterceptorCodeBuilder(); - codeBuilder.AppendSetBindingInterceptor(id: 1, new SetBindingInvocationDescription( + codeBuilder.AppendSetBindingInterceptor(id: 1, new BindingInvocationDescription( Location: new InterceptorLocation(FilePath: @"Path\To\Program.cs", Line: 20, Column: 30), SourceType: new TypeDescription("global::MyNamespace.MySourceClass", IsNullable: false, IsGenericParameter: false), PropertyType: new TypeDescription("global::MyNamespace.MyPropertyClass", IsNullable: true, IsGenericParameter: false), @@ -369,7 +378,8 @@ public void CorrectlyFormatsBindingWithIndexers() new IndexAccess("Item", 0), ]), SetterOptions: new(IsWritable: true, AcceptsNullValue: false), - NullableContextEnabled: true)); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding)); var code = codeBuilder.ToString(); AssertExtensions.CodeIsEqual( @@ -434,7 +444,7 @@ public static void SetBinding1( public void CorrectlyFormatsBindingWithCasts() { var codeBuilder = new BindingCodeWriter.BindingInterceptorCodeBuilder(); - codeBuilder.AppendSetBindingInterceptor(id: 1, new SetBindingInvocationDescription( + codeBuilder.AppendSetBindingInterceptor(id: 1, new BindingInvocationDescription( Location: new InterceptorLocation(FilePath: @"Path\To\Program.cs", Line: 20, Column: 30), SourceType: new TypeDescription("global::MyNamespace.MySourceClass", IsNullable: false, IsGenericParameter: false), PropertyType: new TypeDescription("global::MyNamespace.MyPropertyClass", IsNullable: false, IsGenericParameter: false), @@ -448,7 +458,8 @@ public void CorrectlyFormatsBindingWithCasts() new ConditionalAccess(new MemberAccess("D")), ]), SetterOptions: new(IsWritable: true, AcceptsNullValue: false), - NullableContextEnabled: true)); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding)); var code = codeBuilder.ToString(); diff --git a/src/Controls/tests/BindingSourceGen.UnitTests/BindingRepresentationGenTests.cs b/src/Controls/tests/BindingSourceGen.UnitTests/BindingRepresentationGenTests.cs index c3690095330c..f5326ba4e37a 100644 --- a/src/Controls/tests/BindingSourceGen.UnitTests/BindingRepresentationGenTests.cs +++ b/src/Controls/tests/BindingSourceGen.UnitTests/BindingRepresentationGenTests.cs @@ -17,7 +17,7 @@ public void GenerateSimpleBinding() """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("string"), new TypeDescription("int", IsValueType: true), @@ -25,7 +25,8 @@ public void GenerateSimpleBinding() new MemberAccess("Length", IsValueType: true), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -40,7 +41,7 @@ public void GenerateBindingWithNestedProperties() """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Microsoft.Maui.Controls.Button"), new TypeDescription("int", IsValueType: true, IsNullable: true), @@ -49,7 +50,8 @@ public void GenerateBindingWithNestedProperties() new ConditionalAccess(new MemberAccess("Length", IsValueType: true)), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -69,7 +71,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsValueType: true, IsNullable: true), @@ -79,7 +81,8 @@ class Foo new ConditionalAccess(new MemberAccess("Length", IsValueType: true)), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); @@ -95,7 +98,7 @@ public void GenerateBindingWithNullableReferenceSourceWhenNullableEnabled() """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Microsoft.Maui.Controls.Button", IsNullable: true), new TypeDescription("int", IsValueType: true, IsNullable: true), @@ -104,7 +107,8 @@ public void GenerateBindingWithNullableReferenceSourceWhenNullableEnabled() new ConditionalAccess(new MemberAccess("Length", IsValueType: true)), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -124,7 +128,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsValueType: true, IsNullable: true), @@ -132,7 +136,8 @@ class Foo new MemberAccess("Value", IsValueType: true), ]), SetterOptions: new(IsWritable: true, AcceptsNullValue: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -147,7 +152,7 @@ public void GenerateBindingWithNullableSourceReferenceAndNullableReferenceElemen """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Microsoft.Maui.Controls.Button", IsNullable: true), new TypeDescription("int", IsValueType: true, IsNullable: true), @@ -156,7 +161,8 @@ public void GenerateBindingWithNullableSourceReferenceAndNullableReferenceElemen new ConditionalAccess(new MemberAccess("Length", IsValueType: true)), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -176,7 +182,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("string", IsNullable: true), @@ -184,7 +190,8 @@ class Foo new MemberAccess("Value"), ]), SetterOptions: new(IsWritable: true, AcceptsNullValue: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -205,7 +212,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 4, 7), new TypeDescription("global::Foo", IsNullable: true), new TypeDescription("int", IsValueType: true, IsNullable: true), @@ -214,7 +221,8 @@ class Foo new ConditionalAccess(new MemberAccess("Length", IsValueType: true)), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: false); + NullableContextEnabled: false, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -240,7 +248,7 @@ class Bar """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 4, 7), new TypeDescription("global::Foo", IsNullable: true), new TypeDescription("int", IsValueType: true), @@ -249,7 +257,8 @@ class Bar new MemberAccess("Length", IsValueType: true), ]), SetterOptions: new(IsWritable: true), - NullableContextEnabled: false); + NullableContextEnabled: false, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -275,7 +284,7 @@ class Bar """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 4, 7), new TypeDescription("global::Foo", IsNullable: true), new TypeDescription("int", IsNullable: true, IsValueType: true), @@ -284,7 +293,8 @@ class Bar new MemberAccess("Length", IsValueType: true), ]), SetterOptions: new(IsWritable: true, AcceptsNullValue: true), - NullableContextEnabled: false); + NullableContextEnabled: false, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -315,7 +325,7 @@ class CustomLength """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 4, 7), new TypeDescription("global::Foo", IsNullable: true), new TypeDescription("global::CustomLength", IsNullable: true), @@ -324,7 +334,8 @@ class CustomLength new MemberAccess("Length"), ]), SetterOptions: new(IsWritable: true, AcceptsNullValue: true), - NullableContextEnabled: false); + NullableContextEnabled: false, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -344,7 +355,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsValueType: true), @@ -354,7 +365,8 @@ class Foo new MemberAccess("Length", IsValueType: true), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -374,7 +386,7 @@ class Foo } """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 4, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsValueType: true), @@ -384,7 +396,8 @@ class Foo new MemberAccess("Length", IsValueType: true), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -407,7 +420,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 6, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsValueType: true), @@ -416,7 +429,8 @@ class Foo new MemberAccess("Length", IsValueType: true), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -436,7 +450,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsValueType: true, IsNullable: true), @@ -445,7 +459,8 @@ class Foo new ConditionalAccess(new MemberAccess("Length", IsValueType: true)), // TODO: Improve naming so this looks right ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -470,7 +485,7 @@ class Bar """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsValueType: true, IsNullable: true), @@ -480,7 +495,8 @@ class Bar new MemberAccess("Length", IsValueType: true), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -518,7 +534,7 @@ public class MyPropertyClass """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 6, 7), new TypeDescription("global::MyNamespace.MySourceClass"), new TypeDescription("global::MyNamespace.MyPropertyClass", IsNullable: true), @@ -528,7 +544,8 @@ public class MyPropertyClass new IndexAccess("Item", 0), ]), SetterOptions: new(IsWritable: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -551,7 +568,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 6, 7), new TypeDescription("global::Foo"), new TypeDescription("char", IsValueType: true), @@ -560,7 +577,8 @@ class Foo new IndexAccess("Chars", 0, IsValueType: true), ]), SetterOptions: new(IsWritable: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -581,7 +599,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 4, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsValueType: true), @@ -590,7 +608,8 @@ class Foo new MemberAccess("Length", IsValueType: true), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -610,7 +629,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("string", IsNullable: true), @@ -619,7 +638,8 @@ class Foo new Cast(new TypeDescription("string")), ]), SetterOptions: new(IsWritable: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -639,7 +659,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("string"), @@ -648,7 +668,8 @@ class Foo new Cast(new TypeDescription("string")), ]), SetterOptions: new(IsWritable: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -673,7 +694,7 @@ class C """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsValueType: true, IsNullable: true), @@ -683,7 +704,8 @@ class C new ConditionalAccess(new MemberAccess("X", IsValueType: true)), ]), SetterOptions: new(IsWritable: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -708,7 +730,7 @@ class C """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsValueType: true), @@ -718,7 +740,8 @@ class C new MemberAccess("X", IsValueType: true), ]), SetterOptions: new(IsWritable: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -746,7 +769,7 @@ class C """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsNullable: true, IsValueType: true), @@ -756,7 +779,8 @@ class C new ConditionalAccess(new MemberAccess("X", IsValueType: true)), ]), SetterOptions: new(IsWritable: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -778,7 +802,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsNullable: true, IsValueType: true), @@ -787,7 +811,8 @@ class Foo new Cast(new TypeDescription("int", IsNullable: true, IsValueType: true)), ]), SetterOptions: new(IsWritable: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); @@ -808,7 +833,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsValueType: true), @@ -817,7 +842,8 @@ class Foo new Cast(new TypeDescription("int", IsValueType: true)), ]), SetterOptions: new(IsWritable: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); @@ -845,7 +871,7 @@ struct C """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("int", IsNullable: true, IsValueType: true), @@ -855,7 +881,8 @@ struct C new ConditionalAccess(new MemberAccess("X", IsValueType: true)), ]), SetterOptions: new(IsWritable: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -875,7 +902,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("char", IsValueType: true), @@ -884,7 +911,8 @@ class Foo new IndexAccess("Chars", 0, IsValueType: true), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -904,7 +932,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("char", IsValueType: true), @@ -913,7 +941,8 @@ class Foo new IndexAccess("Item", 0, IsValueType: true), ]), SetterOptions: new(IsWritable: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -933,7 +962,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("string"), @@ -941,7 +970,8 @@ class Foo new IndexAccess("Item", "key"), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -961,7 +991,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo"), new TypeDescription("string"), @@ -969,7 +999,8 @@ class Foo new IndexAccess("Item", "key"), ]), SetterOptions: new(IsWritable: true), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } @@ -989,7 +1020,7 @@ class Foo """; var codeGeneratorResult = SourceGenHelpers.Run(source); - var expectedBinding = new SetBindingInvocationDescription( + var expectedBinding = new BindingInvocationDescription( new InterceptorLocation(@"Path\To\Program.cs", 3, 7), new TypeDescription("global::Foo", IsNullable: true), new TypeDescription("int", IsValueType: true, IsNullable: true), @@ -997,7 +1028,8 @@ class Foo new ConditionalAccess(new IndexAccess("Item", 0, IsValueType: true)), ]), SetterOptions: new(IsWritable: false), - NullableContextEnabled: true); + NullableContextEnabled: true, + MethodType: InterceptedMethodType.SetBinding); AssertExtensions.BindingsAreEqual(expectedBinding, codeGeneratorResult); } diff --git a/src/Controls/tests/BindingSourceGen.UnitTests/BindingTransformerTests.cs b/src/Controls/tests/BindingSourceGen.UnitTests/BindingTransformerTests.cs index 7d38981a56a1..f3423b343bb5 100644 --- a/src/Controls/tests/BindingSourceGen.UnitTests/BindingTransformerTests.cs +++ b/src/Controls/tests/BindingSourceGen.UnitTests/BindingTransformerTests.cs @@ -8,13 +8,14 @@ public class BindingTransformerTests [Fact] public void WrapMemberAccessInConditionalAccessWhenSourceTypeIsReferenceType() { - var binding = new SetBindingInvocationDescription( + var binding = new BindingInvocationDescription( Location: new InterceptorLocation(@"Path\To\Program.cs", 3, 7), SourceType: new TypeDescription("MyType", IsValueType: false), PropertyType: new TypeDescription("MyType2"), Path: new EquatableArray([new MemberAccess("A")]), SetterOptions: new SetterOptions(IsWritable: true), - NullableContextEnabled: false); + NullableContextEnabled: false, + MethodType: InterceptedMethodType.SetBinding); var transformer = new ReferenceTypesConditionalAccessTransformer(); var transformedBinding = transformer.Transform(binding); @@ -26,7 +27,7 @@ public void WrapMemberAccessInConditionalAccessWhenSourceTypeIsReferenceType() [Fact] public void WrapMemberAccessInConditionalAccessWhePreviousPartTypeIsReferenceType() { - var binding = new SetBindingInvocationDescription( + var binding = new BindingInvocationDescription( Location: new InterceptorLocation(@"Path\To\Program.cs", 3, 7), SourceType: new TypeDescription("MyType", IsValueType: true), PropertyType: new TypeDescription("MyType2"), @@ -36,7 +37,8 @@ public void WrapMemberAccessInConditionalAccessWhePreviousPartTypeIsReferenceTyp new MemberAccess("B"), ]), SetterOptions: new SetterOptions(IsWritable: true), - NullableContextEnabled: false); + NullableContextEnabled: false, + MethodType: InterceptedMethodType.SetBinding); var transformer = new ReferenceTypesConditionalAccessTransformer(); var transformedBinding = transformer.Transform(binding); @@ -52,7 +54,7 @@ public void WrapMemberAccessInConditionalAccessWhePreviousPartTypeIsReferenceTyp [Fact] public void DoNotWrapMemberAccessInConditionalAccessWhePreviousPartTypeIsValueType() { - var binding = new SetBindingInvocationDescription( + var binding = new BindingInvocationDescription( Location: new InterceptorLocation(@"Path\To\Program.cs", 3, 7), SourceType: new TypeDescription("MyType", IsValueType: false), PropertyType: new TypeDescription("MyType2"), @@ -62,7 +64,8 @@ public void DoNotWrapMemberAccessInConditionalAccessWhePreviousPartTypeIsValueTy new MemberAccess("B"), ]), SetterOptions: new SetterOptions(IsWritable: true), - NullableContextEnabled: false); + NullableContextEnabled: false, + MethodType: InterceptedMethodType.SetBinding); var transformer = new ReferenceTypesConditionalAccessTransformer(); var transformedBinding = transformer.Transform(binding); @@ -78,7 +81,7 @@ public void DoNotWrapMemberAccessInConditionalAccessWhePreviousPartTypeIsValueTy [Fact] public void WrapAccessInConditionalAccessWhenAllPartsAreReferenceTypes() { - var binding = new SetBindingInvocationDescription( + var binding = new BindingInvocationDescription( Location: new InterceptorLocation(@"Path\To\Program.cs", 3, 7), SourceType: new TypeDescription("MyType"), PropertyType: new TypeDescription("MyType2"), @@ -89,7 +92,8 @@ public void WrapAccessInConditionalAccessWhenAllPartsAreReferenceTypes() new MemberAccess("B"), ]), SetterOptions: new SetterOptions(IsWritable: true), - NullableContextEnabled: false); + NullableContextEnabled: false, + MethodType: InterceptedMethodType.SetBinding); var transformer = new ReferenceTypesConditionalAccessTransformer(); var transformedBinding = transformer.Transform(binding); @@ -106,7 +110,7 @@ public void WrapAccessInConditionalAccessWhenAllPartsAreReferenceTypes() [Fact] public void DoNotWrapAccessInConditionalAccessWhenNoPartsAreReferenceTypes() { - var binding = new SetBindingInvocationDescription( + var binding = new BindingInvocationDescription( Location: new InterceptorLocation(@"Path\To\Program.cs", 3, 7), SourceType: new TypeDescription("MyType", IsValueType: true), PropertyType: new TypeDescription("MyType2"), @@ -117,7 +121,8 @@ public void DoNotWrapAccessInConditionalAccessWhenNoPartsAreReferenceTypes() new MemberAccess("B", IsValueType: true), ]), SetterOptions: new SetterOptions(IsWritable: true), - NullableContextEnabled: false); + NullableContextEnabled: false, + MethodType: InterceptedMethodType.SetBinding); var transformer = new ReferenceTypesConditionalAccessTransformer(); var transformedBinding = transformer.Transform(binding); diff --git a/src/Controls/tests/BindingSourceGen.UnitTests/IncrementalGenerationTests.cs b/src/Controls/tests/BindingSourceGen.UnitTests/IncrementalGenerationTests.cs index cdcc3ede11bd..5196d48d5be2 100644 --- a/src/Controls/tests/BindingSourceGen.UnitTests/IncrementalGenerationTests.cs +++ b/src/Controls/tests/BindingSourceGen.UnitTests/IncrementalGenerationTests.cs @@ -190,9 +190,10 @@ private static Dictionary RunGeneratorOnM // Pairs . Note that a single run can be associated with multiple bindings. // In such cases generate pair for each binding. var bindingRunPairs = runs - .Select(run => (GetSetBindingInvocationDescription(run), run)) + .Select(run => (GetBindingInvocationDescription(run), run)) .SelectMany(bindingsRunPair => bindingsRunPair.Item1.Select(binding => (binding, bindingsRunPair.run))); + // Sometimes the binding has more than one run of the same step associated with it. // In such cases keep the one with Modified reason for safety. return bindingRunPairs @@ -206,7 +207,7 @@ private static Dictionary RunGeneratorOnM .ToArray()); } - private static SetBindingInvocationDescription[] GetSetBindingInvocationDescription(IncrementalGeneratorRunStep step) + private static BindingInvocationDescription[] GetBindingInvocationDescription(IncrementalGeneratorRunStep step) { var bindingCandidate = step switch { @@ -219,9 +220,9 @@ private static SetBindingInvocationDescription[] GetSetBindingInvocationDescript return bindingCandidate switch { - SetBindingInvocationDescription => [(SetBindingInvocationDescription)bindingCandidate], - Result => [((Result)bindingCandidate).Value], - ImmutableArray => ((ImmutableArray)bindingCandidate).ToArray(), + BindingInvocationDescription => [(BindingInvocationDescription)bindingCandidate], + Result => [((Result)bindingCandidate).Value], + ImmutableArray => ((ImmutableArray)bindingCandidate).ToArray(), _ => [] }; } diff --git a/src/Controls/tests/BindingSourceGen.UnitTests/IntegrationTests.cs b/src/Controls/tests/BindingSourceGen.UnitTests/IntegrationTests.cs index a811b537586a..a02c893936a3 100644 --- a/src/Controls/tests/BindingSourceGen.UnitTests/IntegrationTests.cs +++ b/src/Controls/tests/BindingSourceGen.UnitTests/IntegrationTests.cs @@ -14,7 +14,9 @@ public void GenerateSimpleBinding() """; var result = SourceGenHelpers.Run(source); - var id = Math.Abs(result.Binding!.Location.GetHashCode()); + Assert.NotNull(result.Binding); + + var id = Math.Abs(result.Binding.Location.GetHashCode()); AssertExtensions.AssertNoDiagnostics(result); AssertExtensions.CodeIsEqual( @@ -36,7 +38,7 @@ namespace Microsoft.Maui.Controls.Generated using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; - internal static partial class GeneratedBindableObjectExtensions + internal static partial class GeneratedBindingInterceptors { {{BindingCodeWriter.GeneratedCodeAttribute}} @@ -80,7 +82,144 @@ internal static partial class GeneratedBindableObjectExtensions } } """, - result.GeneratedFiles["Path-To-Program.cs-GeneratedBindableObjectExtensions-3-7.g.cs"]); + result.GeneratedFiles["Path-To-Program.cs-GeneratedBindingInterceptors-3-7.g.cs"]); + } + + [Fact] + public void GenerateSimpleBindingCreate() + { + var source = """ + using Microsoft.Maui.Controls; + var bindingBase = Binding.Create(static (string s) => s.Length); + """; + + var result = SourceGenHelpers.Run(source); + var id = Math.Abs(result.Binding!.Location.GetHashCode()); + + AssertExtensions.AssertNoDiagnostics(result); + AssertExtensions.CodeIsEqual( + $$""" + //------------------------------------------------------------------------------ + // + // This code was generated by a .NET MAUI source generator. + // + // Changes to this file may cause incorrect behavior and will be lost if + // the code is regenerated. + // + //------------------------------------------------------------------------------ + #nullable enable + + namespace Microsoft.Maui.Controls.Generated + { + using System; + using System.CodeDom.Compiler; + using System.Runtime.CompilerServices; + using Microsoft.Maui.Controls.Internals; + + internal static partial class GeneratedBindingInterceptors + { + + {{BindingCodeWriter.GeneratedCodeAttribute}} + [InterceptsLocationAttribute(@"Path\To\Program.cs", 2, 27)] + public static TypedBinding Create{{id}}( + Func getter, + BindingMode mode = BindingMode.Default, + IValueConverter? converter = null, + object? converterParameter = null, + string? stringFormat = null, + object? source = null, + object? fallbackValue = null, + object? targetNullValue = null) + { + Action? setter = null; + if (ShouldUseSetter(mode)) + { + throw new InvalidOperationException("Cannot set value on the source object."); + } + + var binding = new TypedBinding( + getter: source => (getter(source), true), + setter, + handlers: new Tuple, string>[] + { + new(static source => source, "Length"), + }) + { + Mode = mode, + Converter = converter, + ConverterParameter = converterParameter, + StringFormat = stringFormat, + Source = source, + FallbackValue = fallbackValue, + TargetNullValue = targetNullValue + }; + return binding; + } + } + } + """, + result.GeneratedFiles["Path-To-Program.cs-GeneratedBindingInterceptors-2-27.g.cs"]); + } + + [Fact] + public void IgnoresOtherOtherCreateMethod() + { + var source = """ + using System; + using Microsoft.Maui.Controls.Internals; + using MyNamespace; + var bindingBase = MyBinding.Create(static (string s) => s.Length); + + namespace MyNamespace + { + public static class MyBinding + { + public static TypedBinding Create(Func getter) + { + throw new NotImplementedException(); + } + } + } + """; + + var result = SourceGenHelpers.Run(source); + + AssertExtensions.AssertNoDiagnostics(result); + Assert.Null(result.Binding); + } + + [Fact] + public void IgnoresOtherBindingCreateMethod() + { + var source = """ + using System; + using MyNamespace; + var bindingBase = Binding.Create(static (string s) => s.Length); + + namespace MyNamespace + { + public class Binding + { + public static Microsoft.Maui.Controls.BindingBase Create( + Func getter, + Microsoft.Maui.Controls.BindingMode mode = Microsoft.Maui.Controls.BindingMode.Default, + Microsoft.Maui.Controls.IValueConverter? converter = null, + object? converterParameter = null, + string? stringFormat = null, + object? source = null, + object? fallbackValue = null, + object? targetNullValue = null) + { + throw new InvalidOperationException($"Call to Binding.Create<{typeof(TSource)}, {typeof(TProperty)}>() was not intercepted."); + } + } + } + """; + + var result = SourceGenHelpers.Run(source); + + AssertExtensions.AssertNoDiagnostics(result); + Assert.Null(result.Binding); } [Fact] @@ -130,7 +269,7 @@ namespace Microsoft.Maui.Controls.Generated using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; - internal static partial class GeneratedBindableObjectExtensions + internal static partial class GeneratedBindingInterceptors { {{BindingCodeWriter.GeneratedCodeAttribute}} @@ -182,7 +321,7 @@ internal static partial class GeneratedBindableObjectExtensions } } """, - result.GeneratedFiles["Path-To-Program.cs-GeneratedBindableObjectExtensions-6-7.g.cs"]); + result.GeneratedFiles["Path-To-Program.cs-GeneratedBindingInterceptors-6-7.g.cs"]); } [Fact] @@ -233,7 +372,7 @@ namespace Microsoft.Maui.Controls.Generated using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; - internal static partial class GeneratedBindableObjectExtensions + internal static partial class GeneratedBindingInterceptors { {{BindingCodeWriter.GeneratedCodeAttribute}} @@ -287,7 +426,7 @@ internal static partial class GeneratedBindableObjectExtensions } } """, - result.GeneratedFiles["Path-To-Program.cs-GeneratedBindableObjectExtensions-7-7.g.cs"]); + result.GeneratedFiles["Path-To-Program.cs-GeneratedBindingInterceptors-7-7.g.cs"]); } public static IEnumerable GenerateSimpleBindingWhenNullableDisabledAndPropertyNullableData => @@ -441,7 +580,7 @@ namespace Microsoft.Maui.Controls.Generated using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; - internal static partial class GeneratedBindableObjectExtensions + internal static partial class GeneratedBindingInterceptors { {{BindingCodeWriter.GeneratedCodeAttribute}} @@ -493,7 +632,7 @@ internal static partial class GeneratedBindableObjectExtensions } } """, - result.GeneratedFiles["Path-To-Program.cs-GeneratedBindableObjectExtensions-7-7.g.cs"]); + result.GeneratedFiles["Path-To-Program.cs-GeneratedBindingInterceptors-7-7.g.cs"]); } [Fact] @@ -558,7 +697,7 @@ namespace Microsoft.Maui.Controls.Generated using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; - internal static partial class GeneratedBindableObjectExtensions + internal static partial class GeneratedBindingInterceptors { {{BindingCodeWriter.GeneratedCodeAttribute}} @@ -611,7 +750,7 @@ internal static partial class GeneratedBindableObjectExtensions } } """, - result.GeneratedFiles["Path-To-Program.cs-GeneratedBindableObjectExtensions-7-7.g.cs"]); + result.GeneratedFiles["Path-To-Program.cs-GeneratedBindingInterceptors-7-7.g.cs"]); } [Theory] @@ -676,7 +815,7 @@ namespace Microsoft.Maui.Controls.Generated using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; - internal static partial class GeneratedBindableObjectExtensions + internal static partial class GeneratedBindingInterceptors { {{BindingCodeWriter.GeneratedCodeAttribute}} @@ -736,7 +875,7 @@ internal static partial class GeneratedBindableObjectExtensions } } """, - result.GeneratedFiles["Path-To-Program.cs-GeneratedBindableObjectExtensions-4-7.g.cs"]); + result.GeneratedFiles["Path-To-Program.cs-GeneratedBindingInterceptors-4-7.g.cs"]); } [Fact] @@ -799,7 +938,7 @@ namespace Microsoft.Maui.Controls.Generated using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; - internal static partial class GeneratedBindableObjectExtensions + internal static partial class GeneratedBindingInterceptors { {{BindingCodeWriter.GeneratedCodeAttribute}} @@ -855,7 +994,7 @@ internal static partial class GeneratedBindableObjectExtensions } } """, - result.GeneratedFiles["Path-To-Program.cs-GeneratedBindableObjectExtensions-4-7.g.cs"]); + result.GeneratedFiles["Path-To-Program.cs-GeneratedBindingInterceptors-4-7.g.cs"]); } [Fact] @@ -909,7 +1048,7 @@ namespace Microsoft.Maui.Controls.Generated using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; - internal static partial class GeneratedBindableObjectExtensions + internal static partial class GeneratedBindingInterceptors { {{BindingCodeWriter.GeneratedCodeAttribute}} [InterceptsLocationAttribute(@"Path\To\Program.cs", 3, 7)] @@ -957,7 +1096,7 @@ internal static partial class GeneratedBindableObjectExtensions } } """, - result.GeneratedFiles["Path-To-Program.cs-GeneratedBindableObjectExtensions-3-7.g.cs"]); + result.GeneratedFiles["Path-To-Program.cs-GeneratedBindingInterceptors-3-7.g.cs"]); } [Fact] @@ -1015,7 +1154,7 @@ namespace Microsoft.Maui.Controls.Generated using System.Runtime.CompilerServices; using Microsoft.Maui.Controls.Internals; - internal static partial class GeneratedBindableObjectExtensions + internal static partial class GeneratedBindingInterceptors { {{BindingCodeWriter.GeneratedCodeAttribute}} @@ -1073,6 +1212,6 @@ internal static partial class GeneratedBindableObjectExtensions } } """, - result.GeneratedFiles["Path-To-Program.cs-GeneratedBindableObjectExtensions-6-7.g.cs"]); + result.GeneratedFiles["Path-To-Program.cs-GeneratedBindingInterceptors-6-7.g.cs"]); } } diff --git a/src/Controls/tests/BindingSourceGen.UnitTests/SourceGenHelpers.cs b/src/Controls/tests/BindingSourceGen.UnitTests/SourceGenHelpers.cs index 0742580e85e5..9c97e1e7ffaf 100644 --- a/src/Controls/tests/BindingSourceGen.UnitTests/SourceGenHelpers.cs +++ b/src/Controls/tests/BindingSourceGen.UnitTests/SourceGenHelpers.cs @@ -11,7 +11,7 @@ internal record CodeGeneratorResult( ImmutableArray SourceCompilationDiagnostics, ImmutableArray SourceGeneratorDiagnostics, ImmutableArray GeneratedCodeCompilationDiagnostics, - SetBindingInvocationDescription? Binding); + BindingInvocationDescription? Binding); internal static class SourceGenHelpers { @@ -43,7 +43,7 @@ internal static CodeGeneratorResult Run(string source) var trackedSteps = result.TrackedSteps; var resultBinding = trackedSteps.TryGetValue("Bindings", out ImmutableArray value) - ? (SetBindingInvocationDescription)value[0].Outputs[0].Value + ? (BindingInvocationDescription)value[0].Outputs[0].Value : null; return new CodeGeneratorResult(