Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable Binding inteceptors source generator by default #22856

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 10 additions & 15 deletions src/Controls/src/BindingSourceGen/BindingSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private static Result<SetBindingInvocationDescription> GetBindingForGeneration(G
return Result<SetBindingInvocationDescription>.Failure(DiagnosticsFactory.UnableToResolvePath(invocation.GetLocation()));
}

var overloadDiagnostics = VerifyCorrectOverload(invocation, context, t);
var overloadDiagnostics = new EquatableArray<DiagnosticInfo>(VerifyCorrectOverload(invocation, context, t));
if (overloadDiagnostics.Length > 0)
{
return Result<SetBindingInvocationDescription>.Failure(overloadDiagnostics);
Expand Down Expand Up @@ -121,31 +121,26 @@ private static bool IsNullableContextEnabled(GeneratorSyntaxContext context)
return (nullableContext & NullableContext.Enabled) == NullableContext.Enabled;
}

private static EquatableArray<DiagnosticInfo> VerifyCorrectOverload(InvocationExpressionSyntax invocation, GeneratorSyntaxContext context, CancellationToken t)
private static DiagnosticInfo[] VerifyCorrectOverload(InvocationExpressionSyntax invocation, GeneratorSyntaxContext context, CancellationToken t)
{
var argumentList = invocation.ArgumentList.Arguments;

if (argumentList.Count < 2)
{
throw new ArgumentOutOfRangeException(nameof(invocation));
}

var secondArgument = argumentList[1].Expression;

if (secondArgument is IdentifierNameSyntax)
if (secondArgument is LambdaExpressionSyntax)
{
var type = context.SemanticModel.GetTypeInfo(secondArgument, cancellationToken: t).Type;
if (type != null && type.Name == "Func")
{
return new EquatableArray<DiagnosticInfo>([DiagnosticsFactory.GetterIsNotLambda(secondArgument.GetLocation())]);
}
else // String and Binding
{
return new EquatableArray<DiagnosticInfo>([DiagnosticsFactory.SuboptimalSetBindingOverload(secondArgument.GetLocation())]);
}
return [];
}

return [];
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())],
};
}

private static Result<LambdaExpressionSyntax> ExtractLambda(InvocationExpressionSyntax invocation)
Expand Down
3 changes: 3 additions & 0 deletions src/Controls/src/Build.Tasks/Controls.Build.Tasks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<ProjectReference Include="..\Core\Controls.Core.csproj" />
<ProjectReference Include="..\Xaml\Controls.Xaml.csproj" />
<ProjectReference Include="..\SourceGen\Controls.SourceGen.csproj" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\BindingSourceGen\Controls.BindingSourceGen.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup>
Expand All @@ -49,6 +50,8 @@
<None Include="$(PkgSystem_CodeDom)\lib\netstandard2.0\System.CodeDom.dll" Visible="false" Pack="true" PackagePath="buildTransitive\netstandard2.0" />
<None Include="$(ArtifactsBinDir)Controls.SourceGen\$(Configuration)\netstandard2.0\Microsoft.Maui.Controls.SourceGen.dll" Visible="false" Pack="true" PackagePath="buildTransitive\netstandard2.0" />
<None Include="$(ArtifactsBinDir)Controls.SourceGen\$(Configuration)\netstandard2.0\Microsoft.Maui.Controls.SourceGen.pdb" Visible="false" Pack="true" PackagePath="buildTransitive\netstandard2.0" />
<None Include="$(ArtifactsBinDir)Controls.BindingSourceGen\$(Configuration)\netstandard2.0\Microsoft.Maui.Controls.BindingSourceGen.dll" Visible="false" Pack="true" PackagePath="buildTransitive\netstandard2.0" />
<None Include="$(ArtifactsBinDir)Controls.BindingSourceGen\$(Configuration)\netstandard2.0\Microsoft.Maui.Controls.BindingSourceGen.pdb" Visible="false" Pack="true" PackagePath="buildTransitive\netstandard2.0" />
<None Remove="$(OutputPath)*.xml" />
<None Include="nuget\**" PackagePath="" Pack="true" Exclude="nuget\**\*.aotprofile.txt" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@
<Analyzer Include="$(MSBuildThisFileDirectory)Microsoft.Maui.Controls.SourceGen.dll" IsImplicitlyDefined="true" />
</ItemGroup>

<!-- Enable BindingSourceGen -->
<PropertyGroup>
<_MauiEnableBindingInterceptors Condition=" '$(_MauiEnableBindingInterceptors)' == '' and '$(DisableMauiAnalyzers)' != 'true' ">true</_MauiEnableBindingInterceptors>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to have a private MSBuild property to turn it off. 👍 Customers can opt out of the new source generator with _MauiEnableBindingInterceptors=false if something goes wrong with their build.

<InterceptorsPreviewNamespaces Condition=" '$(_MauiEnableBindingInterceptors)' == 'true' ">$(InterceptorsPreviewNamespaces);Microsoft.Maui.Controls.Generated</InterceptorsPreviewNamespaces>
</PropertyGroup>
<ItemGroup Condition=" '$(_MauiEnableBindingInterceptors)' == 'true' ">
<Analyzer Include="$(MSBuildThisFileDirectory)Microsoft.Maui.Controls.BindingSourceGen.dll" IsImplicitlyDefined="true" />
</ItemGroup>

<ItemGroup Condition="'$(AndroidEnableProfiledAot)' == 'true' and '$(MauiUseDefaultAotProfile)' != 'false'">
<AndroidAotProfile Include="$(MSBuildThisFileDirectory)maui.aotprofile" />
<AndroidAotProfile Include="$(MSBuildThisFileDirectory)maui-blazor.aotprofile" />
Expand Down Expand Up @@ -242,6 +251,10 @@
Condition="'$(MauiImplicitCastOperatorsUsageViaReflectionSupport)' != ''"
Value="$(MauiImplicitCastOperatorsUsageViaReflectionSupport)"
Trim="true" />
<RuntimeHostConfigurationOption Include="Microsoft.Maui.RuntimeFeature.Microsoft.Maui.RuntimeFeature.AreBindingInterceptorsEnabled"
Condition="'$(_MauiEnableBindingInterceptors)' != ''"
Value="$(_MauiEnableBindingInterceptors)"
Trim="true" />
</ItemGroup>
</Target>

Expand Down
5 changes: 5 additions & 0 deletions src/Controls/src/Core/BindableObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ public static void SetBinding<TSource, TProperty>(
object? fallbackValue = null,
object? targetNullValue = null)
{
if (!RuntimeFeature.AreBindingInterceptorsEnabled)
{
throw new InvalidOperationException($"Call to SetBinding<{typeof(TSource)}, {typeof(TProperty)}> could not be intercepted because the feature has been disabled. Consider removing the DisableMauiAnalyzers property from your project file or set the _MauiEnableBindingInterceptors property to true instead.");
}

throw new InvalidOperationException($"Call to SetBinding<{typeof(TSource)}, {typeof(TProperty)}> was not intercepted.");
}
#nullable disable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,40 @@ public void DoesNotReportWarningWhenUsingOverloadWithStringVariablePath()
Assert.Empty(result.SourceGeneratorDiagnostics);
}

[Fact]
public void DoesNotReportWarningWhenUsingOverloadWithNameofInPath()
{
var source = """
using Microsoft.Maui.Controls;
var label = new Label();
var slider = new Slider();

label.BindingContext = slider;
label.SetBinding(Label.ScaleProperty, nameof(Slider.Value));
""";

var result = SourceGenHelpers.Run(source);
Assert.Empty(result.SourceGeneratorDiagnostics);
}

[Fact]
public void DoesNotReportWarningWhenUsingOverloadWithMethodCallThatReturnsString()
{
var source = """
using Microsoft.Maui.Controls;
var label = new Label();
var slider = new Slider();

label.BindingContext = slider;
label.SetBinding(Label.ScaleProperty, GetPath());

static string GetPath() => "Value";
""";

var result = SourceGenHelpers.Run(source);
Assert.Empty(result.SourceGeneratorDiagnostics);
}

[Fact]
public void ReportsUnableToResolvePathWhenUsingMethodCall()
{
Expand Down
6 changes: 6 additions & 0 deletions src/Core/src/RuntimeFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal static class RuntimeFeature
private const bool IsShellSearchResultsRendererDisplayMemberNameSupportedByDefault = true;
private const bool IsQueryPropertyAttributeSupportedByDefault = true;
private const bool IsImplicitCastOperatorsUsageViaReflectionSupportedByDefault = true;
private const bool AreBindingInterceptorsEnabledByDefault = true;

#pragma warning disable IL4000 // Return value does not match FeatureGuardAttribute 'System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute'.
#if !NETSTANDARD
Expand Down Expand Up @@ -55,6 +56,11 @@ internal static bool IsShellSearchResultsRendererDisplayMemberNameSupported
AppContext.TryGetSwitch("Microsoft.Maui.RuntimeFeature.IsImplicitCastOperatorsUsageViaReflectionSupported", out bool isSupported)
? isSupported
: IsImplicitCastOperatorsUsageViaReflectionSupportedByDefault;

internal static bool AreBindingInterceptorsEnabled =>
AppContext.TryGetSwitch("Microsoft.Maui.RuntimeFeature.AreBindingInterceptorsEnabled", out bool areEnabled)
simonrozsival marked this conversation as resolved.
Show resolved Hide resolved
? areEnabled
: AreBindingInterceptorsEnabledByDefault;
simonrozsival marked this conversation as resolved.
Show resolved Hide resolved
#pragma warning restore IL4000
}
}
Loading