diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index de387fc627..93b6f3108d 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -49,11 +49,16 @@ jobs: - name: Display dotnet info run: dotnet --info + - name: Build Benchmarks + run: | + dotnet build ${{ env.PathToCommunityToolkitAnalyzersBenchmarksCsproj }} -c Release + dotnet build ${{ env.PathToCommunityToolkitSourceGeneratorsBenchmarksCsproj }} -c Release + - name: Run Analyzer Benchmarks - run: dotnet run --project ${{ env.PathToCommunityToolkitAnalyzersBenchmarksCsproj }} -c Release -- -a "${{ runner.temp }}" + run: dotnet run --project ${{ env.PathToCommunityToolkitAnalyzersBenchmarksCsproj }} -c Release --no-build -- -a "${{ runner.temp }}" - name: Run Source Generator Benchmarks - run: dotnet run --project ${{ env.PathToCommunityToolkitSourceGeneratorsBenchmarksCsproj }} -c Release -- -a "${{ runner.temp }}" + run: dotnet run --project ${{ env.PathToCommunityToolkitSourceGeneratorsBenchmarksCsproj }} -c Release --no-build -- -a "${{ runner.temp }}" - name: Publish Benchmarks uses: actions/upload-artifact@v7 diff --git a/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_CommonUsageTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_CommonUsageTests.cs index 9b73716179..e9fa05be08 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_CommonUsageTests.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_CommonUsageTests.cs @@ -595,37 +595,40 @@ public partial class {{defaultTestClassName}} : View namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingText; /// /// BindableProperty for the property. /// - public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultText); - public partial string Text { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingText ? field : (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } + public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultText); + public partial string Text { get => __isInitializingText ? field : (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingCustomDuration; /// /// BindableProperty for the property. /// - public static readonly global::Microsoft.Maui.Controls.BindableProperty CustomDurationProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("CustomDuration", typeof(System.TimeSpan), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultCustomDuration); - public partial System.TimeSpan CustomDuration { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingCustomDuration ? field : (System.TimeSpan)GetValue(CustomDurationProperty); set => SetValue(CustomDurationProperty, value); } - } + public static readonly global::Microsoft.Maui.Controls.BindableProperty CustomDurationProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("CustomDuration", typeof(System.TimeSpan), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultCustomDuration); + public partial System.TimeSpan CustomDuration { get => __isInitializingCustomDuration ? field : (System.TimeSpan)GetValue(CustomDurationProperty); set => SetValue(CustomDurationProperty, value); } - file static class __{{defaultTestClassName}}BindablePropertyInitHelpers - { - public static volatile bool IsInitializingText = false; - public static object CreateDefaultText(global::Microsoft.Maui.Controls.BindableObject bindable) - { - IsInitializingText = true; - var defaultValue = ((TestView)bindable).Text; - IsInitializingText = false; - return defaultValue; - } - - public static volatile bool IsInitializingCustomDuration = false; - public static object CreateDefaultCustomDuration(global::Microsoft.Maui.Controls.BindableObject bindable) + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class __BindablePropertyInitHelpers { - IsInitializingCustomDuration = true; - var defaultValue = ((TestView)bindable).CustomDuration; - IsInitializingCustomDuration = false; - return defaultValue; + public static object CreateDefaultText(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingText = true; + var defaultValue = (({{defaultTestClassName}})bindable).Text; + (({{defaultTestClassName}})bindable).__isInitializingText = false; + return defaultValue; + } + + public static object CreateDefaultCustomDuration(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingCustomDuration = true; + var defaultValue = (({{defaultTestClassName}})bindable).CustomDuration; + (({{defaultTestClassName}})bindable).__isInitializingCustomDuration = false; + return defaultValue; + } } } """; diff --git a/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_EdgeCaseTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_EdgeCaseTests.cs index 951ffe92a1..b5b5b7653a 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_EdgeCaseTests.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_EdgeCaseTests.cs @@ -91,22 +91,24 @@ public enum Status : byte namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingInvoiceStatus; /// /// BindableProperty for the property. /// - public static readonly global::Microsoft.Maui.Controls.BindableProperty InvoiceStatusProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("InvoiceStatus", typeof(TestNamespace.Status), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultInvoiceStatus); - public partial TestNamespace.Status InvoiceStatus { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingInvoiceStatus ? field : (TestNamespace.Status)GetValue(InvoiceStatusProperty); set => SetValue(InvoiceStatusProperty, value); } - } + public static readonly global::Microsoft.Maui.Controls.BindableProperty InvoiceStatusProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("InvoiceStatus", typeof(TestNamespace.Status), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultInvoiceStatus); + public partial TestNamespace.Status InvoiceStatus { get => __isInitializingInvoiceStatus ? field : (TestNamespace.Status)GetValue(InvoiceStatusProperty); set => SetValue(InvoiceStatusProperty, value); } - file static class __{{defaultTestClassName}}BindablePropertyInitHelpers - { - public static volatile bool IsInitializingInvoiceStatus = false; - public static object CreateDefaultInvoiceStatus(global::Microsoft.Maui.Controls.BindableObject bindable) + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class __BindablePropertyInitHelpers { - IsInitializingInvoiceStatus = true; - var defaultValue = ((TestView)bindable).InvoiceStatus; - IsInitializingInvoiceStatus = false; - return defaultValue; + public static object CreateDefaultInvoiceStatus(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingInvoiceStatus = true; + var defaultValue = (({{defaultTestClassName}})bindable).InvoiceStatus; + (({{defaultTestClassName}})bindable).__isInitializingInvoiceStatus = false; + return defaultValue; + } } } """; @@ -151,22 +153,24 @@ public enum Status : long namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingInvoiceStatus; /// /// BindableProperty for the property. /// - public static readonly global::Microsoft.Maui.Controls.BindableProperty InvoiceStatusProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("InvoiceStatus", typeof(TestNamespace.Status), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultInvoiceStatus); - public partial TestNamespace.Status InvoiceStatus { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingInvoiceStatus ? field : (TestNamespace.Status)GetValue(InvoiceStatusProperty); set => SetValue(InvoiceStatusProperty, value); } - } + public static readonly global::Microsoft.Maui.Controls.BindableProperty InvoiceStatusProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("InvoiceStatus", typeof(TestNamespace.Status), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultInvoiceStatus); + public partial TestNamespace.Status InvoiceStatus { get => __isInitializingInvoiceStatus ? field : (TestNamespace.Status)GetValue(InvoiceStatusProperty); set => SetValue(InvoiceStatusProperty, value); } - file static class __{{defaultTestClassName}}BindablePropertyInitHelpers - { - public static volatile bool IsInitializingInvoiceStatus = false; - public static object CreateDefaultInvoiceStatus(global::Microsoft.Maui.Controls.BindableObject bindable) + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class __BindablePropertyInitHelpers { - IsInitializingInvoiceStatus = true; - var defaultValue = ((TestView)bindable).InvoiceStatus; - IsInitializingInvoiceStatus = false; - return defaultValue; + public static object CreateDefaultInvoiceStatus(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingInvoiceStatus = true; + var defaultValue = (({{defaultTestClassName}})bindable).InvoiceStatus; + (({{defaultTestClassName}})bindable).__isInitializingInvoiceStatus = false; + return defaultValue; + } } } """; @@ -471,112 +475,120 @@ public partial class {{defaultTestClassName}} : View namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingIsEnabled; /// /// BindableProperty for the property. /// - public static readonly global::Microsoft.Maui.Controls.BindableProperty IsEnabledProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("IsEnabled", typeof(bool), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultIsEnabled); - public partial bool IsEnabled { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingIsEnabled ? field : (bool)GetValue(IsEnabledProperty); set => SetValue(IsEnabledProperty, value); } + public static readonly global::Microsoft.Maui.Controls.BindableProperty IsEnabledProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("IsEnabled", typeof(bool), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultIsEnabled); + public partial bool IsEnabled { get => __isInitializingIsEnabled ? field : (bool)GetValue(IsEnabledProperty); set => SetValue(IsEnabledProperty, value); } + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingPi; /// /// BindableProperty for the property. /// - public static readonly global::Microsoft.Maui.Controls.BindableProperty PiProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Pi", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultPi); - public partial double Pi { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingPi ? field : (double)GetValue(PiProperty); set => SetValue(PiProperty, value); } + public static readonly global::Microsoft.Maui.Controls.BindableProperty PiProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Pi", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultPi); + public partial double Pi { get => __isInitializingPi ? field : (double)GetValue(PiProperty); set => SetValue(PiProperty, value); } + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingLetter; /// /// BindableProperty for the property. /// - public static readonly global::Microsoft.Maui.Controls.BindableProperty LetterProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Letter", typeof(char), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultLetter); - public partial char Letter { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingLetter ? field : (char)GetValue(LetterProperty); set => SetValue(LetterProperty, value); } + public static readonly global::Microsoft.Maui.Controls.BindableProperty LetterProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Letter", typeof(char), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultLetter); + public partial char Letter { get => __isInitializingLetter ? field : (char)GetValue(LetterProperty); set => SetValue(LetterProperty, value); } + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingTimeSpent; /// /// BindableProperty for the property. /// - public static readonly global::Microsoft.Maui.Controls.BindableProperty TimeSpentProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("TimeSpent", typeof(System.TimeSpan), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultTimeSpent); - public partial System.TimeSpan TimeSpent { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingTimeSpent ? field : (System.TimeSpan)GetValue(TimeSpentProperty); set => SetValue(TimeSpentProperty, value); } + public static readonly global::Microsoft.Maui.Controls.BindableProperty TimeSpentProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("TimeSpent", typeof(System.TimeSpan), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultTimeSpent); + public partial System.TimeSpan TimeSpent { get => __isInitializingTimeSpent ? field : (System.TimeSpan)GetValue(TimeSpentProperty); set => SetValue(TimeSpentProperty, value); } + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingDoubleEpsilon; /// /// BindableProperty for the property. /// - public static readonly global::Microsoft.Maui.Controls.BindableProperty DoubleEpsilonProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("DoubleEpsilon", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultDoubleEpsilon); - public partial double DoubleEpsilon { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingDoubleEpsilon ? field : (double)GetValue(DoubleEpsilonProperty); set => SetValue(DoubleEpsilonProperty, value); } + public static readonly global::Microsoft.Maui.Controls.BindableProperty DoubleEpsilonProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("DoubleEpsilon", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultDoubleEpsilon); + public partial double DoubleEpsilon { get => __isInitializingDoubleEpsilon ? field : (double)GetValue(DoubleEpsilonProperty); set => SetValue(DoubleEpsilonProperty, value); } + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingSingleEpsilon; /// /// BindableProperty for the property. /// - public static readonly global::Microsoft.Maui.Controls.BindableProperty SingleEpsilonProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("SingleEpsilon", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultSingleEpsilon); - public partial double SingleEpsilon { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingSingleEpsilon ? field : (double)GetValue(SingleEpsilonProperty); set => SetValue(SingleEpsilonProperty, value); } + public static readonly global::Microsoft.Maui.Controls.BindableProperty SingleEpsilonProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("SingleEpsilon", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultSingleEpsilon); + public partial double SingleEpsilon { get => __isInitializingSingleEpsilon ? field : (double)GetValue(SingleEpsilonProperty); set => SetValue(SingleEpsilonProperty, value); } + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingCurrentTime; /// /// BindableProperty for the property. /// - public static readonly global::Microsoft.Maui.Controls.BindableProperty CurrentTimeProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("CurrentTime", typeof(System.DateTimeOffset), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultCurrentTime); - public partial System.DateTimeOffset CurrentTime { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingCurrentTime ? field : (System.DateTimeOffset)GetValue(CurrentTimeProperty); set => SetValue(CurrentTimeProperty, value); } - } - - file static class __{{defaultTestClassName}}BindablePropertyInitHelpers - { - public static volatile bool IsInitializingIsEnabled = false; - public static object CreateDefaultIsEnabled(global::Microsoft.Maui.Controls.BindableObject bindable) - { - IsInitializingIsEnabled = true; - var defaultValue = ((TestView)bindable).IsEnabled; - IsInitializingIsEnabled = false; - return defaultValue; - } - - public static volatile bool IsInitializingPi = false; - public static object CreateDefaultPi(global::Microsoft.Maui.Controls.BindableObject bindable) - { - IsInitializingPi = true; - var defaultValue = ((TestView)bindable).Pi; - IsInitializingPi = false; - return defaultValue; - } + public static readonly global::Microsoft.Maui.Controls.BindableProperty CurrentTimeProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("CurrentTime", typeof(System.DateTimeOffset), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultCurrentTime); + public partial System.DateTimeOffset CurrentTime { get => __isInitializingCurrentTime ? field : (System.DateTimeOffset)GetValue(CurrentTimeProperty); set => SetValue(CurrentTimeProperty, value); } - public static volatile bool IsInitializingLetter = false; - public static object CreateDefaultLetter(global::Microsoft.Maui.Controls.BindableObject bindable) + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class __BindablePropertyInitHelpers { - IsInitializingLetter = true; - var defaultValue = ((TestView)bindable).Letter; - IsInitializingLetter = false; - return defaultValue; - } - - public static volatile bool IsInitializingTimeSpent = false; - public static object CreateDefaultTimeSpent(global::Microsoft.Maui.Controls.BindableObject bindable) - { - IsInitializingTimeSpent = true; - var defaultValue = ((TestView)bindable).TimeSpent; - IsInitializingTimeSpent = false; - return defaultValue; - } - - public static volatile bool IsInitializingDoubleEpsilon = false; - public static object CreateDefaultDoubleEpsilon(global::Microsoft.Maui.Controls.BindableObject bindable) - { - IsInitializingDoubleEpsilon = true; - var defaultValue = ((TestView)bindable).DoubleEpsilon; - IsInitializingDoubleEpsilon = false; - return defaultValue; - } - - public static volatile bool IsInitializingSingleEpsilon = false; - public static object CreateDefaultSingleEpsilon(global::Microsoft.Maui.Controls.BindableObject bindable) - { - IsInitializingSingleEpsilon = true; - var defaultValue = ((TestView)bindable).SingleEpsilon; - IsInitializingSingleEpsilon = false; - return defaultValue; - } - - public static volatile bool IsInitializingCurrentTime = false; - public static object CreateDefaultCurrentTime(global::Microsoft.Maui.Controls.BindableObject bindable) - { - IsInitializingCurrentTime = true; - var defaultValue = ((TestView)bindable).CurrentTime; - IsInitializingCurrentTime = false; - return defaultValue; + public static object CreateDefaultIsEnabled(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingIsEnabled = true; + var defaultValue = (({{defaultTestClassName}})bindable).IsEnabled; + (({{defaultTestClassName}})bindable).__isInitializingIsEnabled = false; + return defaultValue; + } + + public static object CreateDefaultPi(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingPi = true; + var defaultValue = (({{defaultTestClassName}})bindable).Pi; + (({{defaultTestClassName}})bindable).__isInitializingPi = false; + return defaultValue; + } + + public static object CreateDefaultLetter(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingLetter = true; + var defaultValue = (({{defaultTestClassName}})bindable).Letter; + (({{defaultTestClassName}})bindable).__isInitializingLetter = false; + return defaultValue; + } + + public static object CreateDefaultTimeSpent(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingTimeSpent = true; + var defaultValue = (({{defaultTestClassName}})bindable).TimeSpent; + (({{defaultTestClassName}})bindable).__isInitializingTimeSpent = false; + return defaultValue; + } + + public static object CreateDefaultDoubleEpsilon(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingDoubleEpsilon = true; + var defaultValue = (({{defaultTestClassName}})bindable).DoubleEpsilon; + (({{defaultTestClassName}})bindable).__isInitializingDoubleEpsilon = false; + return defaultValue; + } + + public static object CreateDefaultSingleEpsilon(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingSingleEpsilon = true; + var defaultValue = (({{defaultTestClassName}})bindable).SingleEpsilon; + (({{defaultTestClassName}})bindable).__isInitializingSingleEpsilon = false; + return defaultValue; + } + + public static object CreateDefaultCurrentTime(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingCurrentTime = true; + var defaultValue = (({{defaultTestClassName}})bindable).CurrentTime; + (({{defaultTestClassName}})bindable).__isInitializingCurrentTime = false; + return defaultValue; + } } } """; @@ -668,53 +680,57 @@ public partial class {{defaultTestClassName}} : View namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingText; /// /// BindableProperty for the property. /// - internal static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultText); - internal partial string Text { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingText ? field : (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } + internal static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultText); + internal partial string Text { get => __isInitializingText ? field : (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingTime; /// /// BindableProperty for the property. /// - protected internal static readonly global::Microsoft.Maui.Controls.BindableProperty TimeProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Time", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultTime); - protected internal partial string Time { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingTime ? field : (string)GetValue(TimeProperty); set => SetValue(TimeProperty, value); } + protected internal static readonly global::Microsoft.Maui.Controls.BindableProperty TimeProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Time", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultTime); + protected internal partial string Time { get => __isInitializingTime ? field : (string)GetValue(TimeProperty); set => SetValue(TimeProperty, value); } - static readonly global::Microsoft.Maui.Controls.BindablePropertyKey customDurationPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateReadOnly("CustomDuration", typeof(System.TimeSpan), typeof(TestNamespace.TestView), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __TestViewBindablePropertyInitHelpers.CreateDefaultCustomDuration); + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingCustomDuration; + static readonly global::Microsoft.Maui.Controls.BindablePropertyKey customDurationPropertyKey = global::Microsoft.Maui.Controls.BindableProperty.CreateReadOnly("CustomDuration", typeof(System.TimeSpan), typeof(TestNamespace.TestView), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultCustomDuration); /// /// BindableProperty for the property. /// internal static readonly global::Microsoft.Maui.Controls.BindableProperty CustomDurationProperty = customDurationPropertyKey.BindableProperty; - internal partial System.TimeSpan CustomDuration { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingCustomDuration ? field : (System.TimeSpan)GetValue(CustomDurationProperty); private set => SetValue(customDurationPropertyKey, value); } - } + internal partial System.TimeSpan CustomDuration { get => __isInitializingCustomDuration ? field : (System.TimeSpan)GetValue(CustomDurationProperty); private set => SetValue(customDurationPropertyKey, value); } - file static class __{{defaultTestClassName}}BindablePropertyInitHelpers - { - public static volatile bool IsInitializingText = false; - public static object CreateDefaultText(global::Microsoft.Maui.Controls.BindableObject bindable) - { - IsInitializingText = true; - var defaultValue = ((TestView)bindable).Text; - IsInitializingText = false; - return defaultValue; - } - - public static volatile bool IsInitializingTime = false; - public static object CreateDefaultTime(global::Microsoft.Maui.Controls.BindableObject bindable) - { - IsInitializingTime = true; - var defaultValue = ((TestView)bindable).Time; - IsInitializingTime = false; - return defaultValue; - } - - public static volatile bool IsInitializingCustomDuration = false; - public static object CreateDefaultCustomDuration(global::Microsoft.Maui.Controls.BindableObject bindable) + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + static class __BindablePropertyInitHelpers { - IsInitializingCustomDuration = true; - var defaultValue = ((TestView)bindable).CustomDuration; - IsInitializingCustomDuration = false; - return defaultValue; + public static object CreateDefaultText(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingText = true; + var defaultValue = (({{defaultTestClassName}})bindable).Text; + (({{defaultTestClassName}})bindable).__isInitializingText = false; + return defaultValue; + } + + public static object CreateDefaultTime(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingTime = true; + var defaultValue = (({{defaultTestClassName}})bindable).Time; + (({{defaultTestClassName}})bindable).__isInitializingTime = false; + return defaultValue; + } + + public static object CreateDefaultCustomDuration(global::Microsoft.Maui.Controls.BindableObject bindable) + { + (({{defaultTestClassName}})bindable).__isInitializingCustomDuration = true; + var defaultValue = (({{defaultTestClassName}})bindable).CustomDuration; + (({{defaultTestClassName}})bindable).__isInitializingCustomDuration = false; + return defaultValue; + } } } """; diff --git a/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_IntegrationTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_IntegrationTests.cs index 5f47781e03..1be8ed3f52 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_IntegrationTests.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGenerator_IntegrationTests.cs @@ -159,21 +159,22 @@ public partial class {{defaultTestClassName}} : View where T : class namespace {{defaultTestNamespace}}; public partial class {{defaultTestClassName}} { + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + bool __isInitializingValue; /// /// BindableProperty for the property. /// - public static readonly global::Microsoft.Maui.Controls.BindableProperty ValueProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Value", typeof(T), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __{{defaultTestClassName}}BindablePropertyInitHelpers.CreateDefaultValue); - public partial T? Value { get => __{{defaultTestClassName}}BindablePropertyInitHelpers.IsInitializingValue ? field : (T? )GetValue(ValueProperty); set => SetValue(ValueProperty, value); } + public static readonly global::Microsoft.Maui.Controls.BindableProperty ValueProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Value", typeof(T), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, (Microsoft.Maui.Controls.BindingMode)0, null, null, null, null, __BindablePropertyInitHelpers.CreateDefaultValue); + public partial T? Value { get => __isInitializingValue ? field : (T? )GetValue(ValueProperty); set => SetValue(ValueProperty, value); } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - private static class __{{defaultTestClassName}}BindablePropertyInitHelpers + static class __BindablePropertyInitHelpers { - public static volatile bool IsInitializingValue = false; public static object CreateDefaultValue(global::Microsoft.Maui.Controls.BindableObject bindable) { - IsInitializingValue = true; + (({{defaultTestClassName}})bindable).__isInitializingValue = true; var defaultValue = (({{defaultTestClassName}})bindable).Value; - IsInitializingValue = false; + (({{defaultTestClassName}})bindable).__isInitializingValue = false; return defaultValue; } } diff --git a/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyModelTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyModelTests.cs index 84f7a6655f..f958c189fe 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyModelTests.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators.UnitTests/BindablePropertyModelTests.cs @@ -91,7 +91,7 @@ public void BindablePropertyModel_WithAllParameters_StoresCorrectValues() Assert.Equal(hasInitializer, model.HasInitializer); Assert.Equal("TestPropertyProperty", model.BindablePropertyName); Assert.Equal(defaultValueCreatorMethodName, model.EffectiveDefaultValueCreatorMethodName); - Assert.Equal("IsInitializingTestProperty", model.InitializingPropertyName); + Assert.Equal("__isInitializingTestProperty", model.InitializingPropertyName); Assert.Equal(propertyAccessibility, model.PropertyAccessibility); } diff --git a/src/CommunityToolkit.Maui.SourceGenerators/Generators/BindablePropertyAttributeSourceGenerator.cs b/src/CommunityToolkit.Maui.SourceGenerators/Generators/BindablePropertyAttributeSourceGenerator.cs index b7d163b3a2..e58b4e3217 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators/Generators/BindablePropertyAttributeSourceGenerator.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators/Generators/BindablePropertyAttributeSourceGenerator.cs @@ -142,8 +142,8 @@ static string GenerateSource(BindablePropertySemanticValues value) sb.Append(value.ClassInformation.DeclaredAccessibility).Append(" partial class ").Append(classNameWithGenerics).Append("\n{\n\n"); - // Prepare helper builder for file-static class members (static flags + default creators) - var fileStaticClassStringBuilder = new StringBuilder(256); + // Prepare helper builder for nested static class members (default value creators only) + var helperClassStringBuilder = new StringBuilder(256); var helperNames = new HashSet(); // Build fully-qualified declaring type name for helper method casts (include containing types) @@ -151,10 +151,19 @@ static string GenerateSource(BindablePropertySemanticValues value) ? classNameWithGenerics : string.Concat(value.ClassInformation.ContainingTypes, ".", classNameWithGenerics); - var bindablePropertyInitHelpersClassName = $"__{value.ClassInformation.ClassName}BindablePropertyInitHelpers"; + var bindablePropertyInitHelpersClassName = "__BindablePropertyInitHelpers"; foreach (var info in value.BindableProperties) { + if (info.ShouldUsePropertyInitializer) + { + // Emit the initializing flag as an instance field on the partial class + if (helperNames.Add(info.InitializingPropertyName)) + { + AppendInstanceInitializingField(sb, in info); + } + } + if (info.IsReadOnlyBindableProperty) { GenerateReadOnlyBindableProperty(sb, in info, bindablePropertyInitHelpersClassName); @@ -164,33 +173,26 @@ static string GenerateSource(BindablePropertySemanticValues value) GenerateBindableProperty(sb, in info, bindablePropertyInitHelpersClassName); } + GenerateProperty(sb, in info, bindablePropertyInitHelpersClassName); + if (info.ShouldUsePropertyInitializer) { - // Generate only references within the class; actual static field and creator method - // will be placed inside the file static helper class below. - if (helperNames.Add(info.InitializingPropertyName)) - { - AppendHelperInitializingField(fileStaticClassStringBuilder, in info); - } + // Collect the CreateDefault method for the nested helper class if (helperNames.Add(info.EffectiveDefaultValueCreatorMethodName)) { - AppendHelperDefaultValueMethod(fileStaticClassStringBuilder, in info, fullDeclaringType); + AppendHelperDefaultValueMethod(helperClassStringBuilder, in info, fullDeclaringType); } } - - GenerateProperty(sb, in info, bindablePropertyInitHelpersClassName); } - // If we generated any helper members and the declaring class is generic, - // emit the helper class nested inside the generated partial class so - // generic type parameters are in scope for casts used by the helper. - if (fileStaticClassStringBuilder.Length > 0 && !string.IsNullOrEmpty(value.ClassInformation.GenericTypeParameters)) + // Always emit the helper class nested inside the partial class + if (helperClassStringBuilder.Length > 0) { sb.Append("[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]"); sb.Append("\n"); - sb.Append("private static class ").Append(bindablePropertyInitHelpersClassName).Append("\n{"); + sb.Append("static class ").Append(bindablePropertyInitHelpersClassName).Append("\n{"); sb.Append("\n"); - sb.Append(fileStaticClassStringBuilder.ToString()); + sb.Append(helperClassStringBuilder.ToString()); sb.Append("}\n\n"); } @@ -206,15 +208,6 @@ static string GenerateSource(BindablePropertySemanticValues value) } } - // If we generated any helper members and the declaring class is not generic, - // emit a file static class with them. Generic types have their helpers emitted - // nested inside the class above to ensure type parameter scope. - if (fileStaticClassStringBuilder.Length > 0 && string.IsNullOrEmpty(value.ClassInformation.GenericTypeParameters)) - { - sb.Append("\n\nfile static class ").Append(bindablePropertyInitHelpersClassName).Append("\n{\n"); - sb.Append(fileStaticClassStringBuilder.ToString()); - sb.Append("}\n"); - } return sb.ToString(); } @@ -346,8 +339,8 @@ static void GenerateProperty(StringBuilder sb, in BindablePropertyModel info, in { if (info.ShouldUsePropertyInitializer) { - // Now reference the static flag on the file static helper class - sb.Append(bindablePropertyInitHelpersClassName).Append(".").Append(info.InitializingPropertyName); + // Reference the instance field directly on the partial class + sb.Append(info.InitializingPropertyName); } else { @@ -591,34 +584,39 @@ static string GetFormattedReturnType(ITypeSymbol typeSymbol) } /// - /// Appends the initializing flag into the file-static helper class. + /// Appends the initializing flag as an instance field on the generated partial class. + /// Using an instance field avoids cross-thread / cross-instance interference that + /// occurred when the flag was static. /// - /// Helper StringBuilder used to collect helper members. + /// StringBuilder for the partial class body. /// Property model. - static void AppendHelperInitializingField(StringBuilder fileStaticClassStringBuilder, in BindablePropertyModel info) + static void AppendInstanceInitializingField(StringBuilder sb, in BindablePropertyModel info) { - // Make the flag public static so it can be referenced from the generated partial class in the same file. - fileStaticClassStringBuilder.Append("public static volatile bool ") + sb.Append("[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n") + .Append("bool ") .Append(info.InitializingPropertyName) - .Append(" = false;\n"); + .Append(";\n"); } /// - /// Appends a default value creator method into the file-static helper class. - /// The method sets the static initializing flag, reads the property's initializer value by casting the bindable - /// to the declaring type, then clears the flag and returns the value. + /// Appends a default value creator method into the nested helper class. + /// The method sets the instance-level initializing flag on the bindable object, reads the property's initializer value + /// by casting the bindable to the declaring type, then clears the flag and returns the value. /// - /// Helper StringBuilder used to collect helper members. + /// Helper StringBuilder used to collect helper methods. /// Property model. /// Declaring type including containing types and generic parameters. - static void AppendHelperDefaultValueMethod(StringBuilder fileStaticClassStringBuilder, in BindablePropertyModel info, string fullDeclaringType) + static void AppendHelperDefaultValueMethod(StringBuilder helperClassStringBuilder, in BindablePropertyModel info, string fullDeclaringType) { var sanitizedPropertyName = IsDotnetKeyword(info.PropertyName) ? string.Concat("@", info.PropertyName) : info.PropertyName; - fileStaticClassStringBuilder.Append("public static object ") + helperClassStringBuilder.Append("public static object ") .Append(info.EffectiveDefaultValueCreatorMethodName) .Append("(global::Microsoft.Maui.Controls.BindableObject bindable)\n") .Append("{\n") + .Append("((") + .Append(fullDeclaringType) + .Append(")bindable).") .Append(info.InitializingPropertyName) .Append(" = true;\n") .Append("var defaultValue = ((") @@ -626,6 +624,9 @@ static void AppendHelperDefaultValueMethod(StringBuilder fileStaticClassStringBu .Append(")bindable).") .Append(sanitizedPropertyName) .Append(";\n") + .Append("((") + .Append(fullDeclaringType) + .Append(")bindable).") .Append(info.InitializingPropertyName) .Append(" = false;\n") .Append("return defaultValue;\n") diff --git a/src/CommunityToolkit.Maui.SourceGenerators/Models/BindablePropertyRecords.cs b/src/CommunityToolkit.Maui.SourceGenerators/Models/BindablePropertyRecords.cs index ff334352b2..3b3fd8e2ad 100644 --- a/src/CommunityToolkit.Maui.SourceGenerators/Models/BindablePropertyRecords.cs +++ b/src/CommunityToolkit.Maui.SourceGenerators/Models/BindablePropertyRecords.cs @@ -10,7 +10,7 @@ public record BindablePropertyModel(string PropertyName, ITypeSymbol ReturnType, public string BindablePropertyName => $"{PropertyName}Property"; public string BindablePropertyKeyName => $"{char.ToLower(PropertyName[0])}{PropertyName[1..]}PropertyKey"; public string EffectiveDefaultValueCreatorMethodName => ShouldUsePropertyInitializer ? $"CreateDefault{PropertyName}" : DefaultValueCreatorMethodName; - public string InitializingPropertyName => $"IsInitializing{PropertyName}"; + public string InitializingPropertyName => $"__isInitializing{PropertyName}"; } public record AttachedBindablePropertyModel(string PropertyName, ITypeSymbol ReturnType, ITypeSymbol DeclaringType, string DefaultValue, string DefaultBindingMode, string ValidateValueMethodName, string PropertyChangedMethodName, string PropertyChangingMethodName, string CoerceValueMethodName, string DefaultValueCreatorMethodName, string? GetterAccessibility, string? SetterAccessibility, string BindablePropertyAccessibility, bool IsDeclaringTypeNullable, string? BindablePropertyXmlDocumentation, string? GetterMethodXmlDocumentation, string? SetterMethodXmlDocumentation, bool IsReadOnlyBindableProperty) diff --git a/src/CommunityToolkit.Maui.UnitTests/Views/LazyView/LazyViewTests.cs b/src/CommunityToolkit.Maui.UnitTests/Views/LazyView/LazyViewTests.cs index fc38b93516..cfc10a72af 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Views/LazyView/LazyViewTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Views/LazyView/LazyViewTests.cs @@ -20,7 +20,10 @@ public async Task LoadViewAsync_CancellationTokenExpired() var lazyView = new LazyView