Skip to content

Commit ae3f261

Browse files
committed
Remove all polyfill emission from source generator
The generator should never emit polyfills into consumer projects. Instead it detects platform capabilities and generates appropriate code: - Remove CallerArgumentExpressionAttribute polyfill emission; the supportsCallerArgExpr flag now checks both C# 10+ language version AND that the attribute exists in the compilation, falling back to file/line dispatch when unavailable - Remove [ModuleInitializer] from generated registration code entirely - Replace [ModuleInitializer] in ReactiveUI benchmarks with explicit static constructor calls using an atomic bool guard pattern - Update snapshot tests
1 parent 253db0c commit ae3f261

67 files changed

Lines changed: 25 additions & 168 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/ReactiveUI.Binding.SourceGenerators/BindingGenerator.cs

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,43 +22,15 @@ public class BindingGenerator : IIncrementalGenerator
2222
/// <inheritdoc/>
2323
public void Initialize(IncrementalGeneratorInitializationContext context)
2424
{
25-
// Detect C# language version for CallerArgumentExpression support (C# 10+)
26-
var supportsCallerArgExpr = context.ParseOptionsProvider.Select(static (opts, _) =>
27-
opts is CSharpParseOptions csharpOpts
28-
&& csharpOpts.LanguageVersion >= LanguageVersion.CSharp10);
29-
30-
// Conditionally emit CallerArgumentExpression polyfill when C# 10+ but attribute is missing
31-
var needsPolyfill = supportsCallerArgExpr
25+
// Detect whether the consumer project supports CallerArgumentExpression (C# 10+ AND attribute available).
26+
// When supported, invocation generators use expression-text dispatch; otherwise file/line dispatch.
27+
var supportsCallerArgExpr = context.ParseOptionsProvider
3228
.Combine(context.CompilationProvider)
3329
.Select(static (data, _) =>
34-
data.Left && data.Right.GetTypeByMetadataName(
35-
Constants.CallerArgumentExpressionAttributeMetadataName) is null);
36-
37-
context.RegisterSourceOutput(needsPolyfill, static (ctx, needs) =>
38-
{
39-
if (needs)
40-
{
41-
ctx.AddSource(
42-
"CallerArgumentExpressionAttribute.g.cs",
43-
Constants.CallerArgumentExpressionAttributeSource);
44-
}
45-
});
46-
47-
// Conditionally emit ModuleInitializer polyfill for runtimes that don't include it (e.g. .NET Framework)
48-
var needsModuleInitPolyfill = context.CompilationProvider
49-
.Select(static (compilation, _) =>
50-
compilation.GetTypeByMetadataName(
51-
Constants.ModuleInitializerAttributeMetadataName) is null);
52-
53-
context.RegisterSourceOutput(needsModuleInitPolyfill, static (ctx, needs) =>
54-
{
55-
if (needs)
56-
{
57-
ctx.AddSource(
58-
"ModuleInitializerAttribute.g.cs",
59-
Constants.ModuleInitializerAttributeSource);
60-
}
61-
});
30+
data.Left is CSharpParseOptions csharpOpts
31+
&& csharpOpts.LanguageVersion >= LanguageVersion.CSharp10
32+
&& data.Right.GetTypeByMetadataName(
33+
Constants.CallerArgumentExpressionAttributeMetadataName) is not null);
6234

6335
// Pipeline A: Shared type detection
6436
// One pass: sets flags for IRO, INPC, WpfDP, WinUIDP, KVO, etc.

src/ReactiveUI.Binding.SourceGenerators/Constants.cs

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -45,43 +45,4 @@ internal static class Constants
4545
internal const string CallerFilePathAttributeName = "System.Runtime.CompilerServices.CallerFilePathAttribute";
4646
internal const string CallerLineNumberAttributeName = "System.Runtime.CompilerServices.CallerLineNumberAttribute";
4747
internal const string CallerArgumentExpressionAttributeMetadataName = "System.Runtime.CompilerServices.CallerArgumentExpressionAttribute";
48-
internal const string ModuleInitializerAttributeMetadataName = "System.Runtime.CompilerServices.ModuleInitializerAttribute";
49-
50-
/// <summary>
51-
/// Polyfill source for CallerArgumentExpressionAttribute when targeting runtimes that don't include it.
52-
/// Only emitted when C# 10+ is detected and the attribute is not already available.
53-
/// </summary>
54-
internal const string CallerArgumentExpressionAttributeSource = """
55-
// <auto-generated/>
56-
#pragma warning disable
57-
namespace System.Runtime.CompilerServices
58-
{
59-
[global::System.AttributeUsage(global::System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
60-
internal sealed class CallerArgumentExpressionAttribute : global::System.Attribute
61-
{
62-
public CallerArgumentExpressionAttribute(string parameterName)
63-
{
64-
ParameterName = parameterName;
65-
}
66-
67-
public string ParameterName { get; }
68-
}
69-
}
70-
""";
71-
72-
/// <summary>
73-
/// Polyfill source for ModuleInitializerAttribute when targeting runtimes that don't include it
74-
/// (e.g. .NET Framework 4.x). Only emitted when the attribute is not already available.
75-
/// </summary>
76-
internal const string ModuleInitializerAttributeSource = """
77-
// <auto-generated/>
78-
#pragma warning disable
79-
namespace System.Runtime.CompilerServices
80-
{
81-
[global::System.AttributeUsage(global::System.AttributeTargets.Method, Inherited = false)]
82-
internal sealed class ModuleInitializerAttribute : global::System.Attribute
83-
{
84-
}
85-
}
86-
""";
8748
}

src/ReactiveUI.Binding.SourceGenerators/Generators/RegistrationGenerator.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace ReactiveUI.Binding.SourceGenerators.Generators;
1313

1414
/// <summary>
1515
/// Consolidates all per-kind fallback generator results into a single RegisterSourceOutput.
16-
/// Generates all ICreatesObservableForProperty implementations and one [ModuleInitializer] class.
16+
/// Generates all ICreatesObservableForProperty implementations and a registration class.
1717
/// </summary>
1818
internal static class RegistrationGenerator
1919
{
@@ -58,7 +58,7 @@ internal static IncrementalValueProvider<ImmutableArray<ObservableTypeInfo>> Con
5858
}
5959

6060
/// <summary>
61-
/// Generates the consolidated registration output: all per-kind binder classes + [ModuleInitializer].
61+
/// Generates the consolidated registration output: all per-kind binder classes.
6262
/// </summary>
6363
/// <param name="context">The source production context.</param>
6464
/// <param name="allTypes">All detected observable type infos across all notification kinds.</param>
@@ -93,7 +93,6 @@ internal static class __GeneratedBinderRegistration
9393
/// <summary>
9494
/// Registers all generated binders with the Splat service locator.
9595
/// </summary>
96-
[global::System.Runtime.CompilerServices.ModuleInitializer]
9796
internal static void Initialize()
9897
{
9998
// Generated binder registrations will be added here in future phases.

src/benchmarks/ReactiveUI.Binding.Benchmarks.ReactiveUI/ModuleInitializer.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,31 @@
22
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for full license information.
44

5-
using System.Runtime.CompilerServices;
6-
75
using ReactiveUI.Builder;
86

97
using Splat;
108

119
namespace ReactiveUI.Binding.Benchmarks;
1210

1311
/// <summary>
14-
/// Module initializer that configures ReactiveUI before any benchmarks run.
12+
/// One-shot initializer that configures ReactiveUI before any benchmarks run.
13+
/// Call <see cref="EnsureInitialized"/> from each benchmark class's static constructor.
1514
/// </summary>
1615
internal static class ModuleInitializer
1716
{
17+
private static int _initialized;
18+
1819
/// <summary>
19-
/// Initializes ReactiveUI using the builder pattern.
20+
/// Ensures ReactiveUI is initialized exactly once, regardless of how many
21+
/// benchmark classes call this method.
2022
/// </summary>
21-
[ModuleInitializer]
22-
internal static void Initialize()
23+
internal static void EnsureInitialized()
2324
{
25+
if (Interlocked.Exchange(ref _initialized, 1) != 0)
26+
{
27+
return;
28+
}
29+
2430
ModeDetector.OverrideModeDetector(new BenchmarkModeDetector());
2531
RxAppBuilder.CreateReactiveUIBuilder()
2632
.WithCoreServices()

src/benchmarks/ReactiveUI.Binding.Benchmarks.ReactiveUI/Polyfills/ModuleInitializerAttribute.cs

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/benchmarks/ReactiveUI.Binding.Benchmarks.ReactiveUI/ReactiveUIBindingBenchmark.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public class ReactiveUIBindingBenchmark
2424
private BenchmarkViewModel _source = null!;
2525
private BenchmarkView _target = null!;
2626

27+
static ReactiveUIBindingBenchmark() => ModuleInitializer.EnsureInitialized();
28+
2729
/// <summary>
2830
/// Sets up fresh source and target objects before each benchmark iteration.
2931
/// </summary>

src/benchmarks/ReactiveUI.Binding.Benchmarks.ReactiveUI/ReactiveUIObservationBenchmark.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public class ReactiveUIObservationBenchmark
2525

2626
private BenchmarkViewModel _vm = null!;
2727

28+
static ReactiveUIObservationBenchmark() => ModuleInitializer.EnsureInitialized();
29+
2830
/// <summary>
2931
/// Sets up a fresh view model before each benchmark iteration.
3032
/// </summary>

src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BindGeneratorTests.MultipleBindings#GeneratedBinderRegistration.g.verified.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ internal static class __GeneratedBinderRegistration
1414
/// <summary>
1515
/// Registers all generated binders with the Splat service locator.
1616
/// </summary>
17-
[global::System.Runtime.CompilerServices.ModuleInitializer]
1817
internal static void Initialize()
1918
{
2019
// Generated binder registrations will be added here in future phases.

src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BindGeneratorTests.SingleProperty_StringToString#GeneratedBinderRegistration.g.verified.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ internal static class __GeneratedBinderRegistration
1414
/// <summary>
1515
/// Registers all generated binders with the Splat service locator.
1616
/// </summary>
17-
[global::System.Runtime.CompilerServices.ModuleInitializer]
1817
internal static void Initialize()
1918
{
2019
// Generated binder registrations will be added here in future phases.

src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BindOneWayGeneratorTests.MultipleBindings#GeneratedBinderRegistration.g.verified.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ internal static class __GeneratedBinderRegistration
1414
/// <summary>
1515
/// Registers all generated binders with the Splat service locator.
1616
/// </summary>
17-
[global::System.Runtime.CompilerServices.ModuleInitializer]
1817
internal static void Initialize()
1918
{
2019
// Generated binder registrations will be added here in future phases.

0 commit comments

Comments
 (0)