Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
42 changes: 42 additions & 0 deletions docs/NetEscapades.EnumGenerators.Generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ In general, we recommend installing the [NetEscapades.EnumGenerators](https://ww
* [Avoiding runtime dependencies](#avoiding-runtime-dependencies)
* [Choosing the correct packages for your scenario](#choosing-the-correct-packages-for-your-scenario)
* [Enabling automatic interception (experimental)](#enabling-automatic-interception-experimental)
* [Resolving overload ambiguity with `new()`](#resolving-overload-ambiguity-with-new)
* [Preserving usages of the `[EnumExtensions]` attribute](#preserving-usages-of-the-enumextensions-attribute)<!-- endToc -->

## Why use these packages?
Expand Down Expand Up @@ -639,6 +640,47 @@ public void CantIntercept()
```
<!-- endInclude -->

<!-- include: overload-resolution. path: /fragments/overload-resolution.include.md -->
## Resolving overload ambiguity with `new()`

The generated extension methods include overloads that accept option types such as `EnumParseOptions` and `SerializationOptions`. When using target-typed `new()` with these overloads, the C# compiler may report a CS0121 ambiguity error because it cannot determine which overload to call:

```csharp
// CS0121: The call is ambiguous between 'Parse(string, StringComparison)' and 'Parse(string, EnumParseOptions)'
var result = MyEnumExtensions.Parse("First", new());
```

On **.NET 9+**, this is resolved automatically using the `[OverloadResolutionPriority]` attribute. The generator detects that the attribute type is available in the compilation and includes it in the generated code.

On **older target frameworks** (with C# 13+ language version), you can get the same behavior by providing a polyfill of the `OverloadResolutionPriorityAttribute` type. The generator will automatically detect the polyfill and include the attribute — no additional configuration is needed.

The easiest way to add the polyfill is to use the [Polyfill](https://github.com/SimonCropp/Polyfill) NuGet package, which provides this and many other missing types for older frameworks.

Alternatively, you can add the attribute manually to your project:

```csharp
namespace System.Runtime.CompilerServices
{
[AttributeUsage(
AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property,
AllowMultiple = false, Inherited = false)]
sealed class OverloadResolutionPriorityAttribute : Attribute
{
public OverloadResolutionPriorityAttribute(int priority) => Priority = priority;
public int Priority { get; }
}
}
```

If you need to disable the `[OverloadResolutionPriority]` attribute in the generated code, define the `NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY` preprocessor symbol:

```xml
<PropertyGroup>
<DefineConstants>$(DefineConstants);NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY</DefineConstants>
</PropertyGroup>
```
<!-- endInclude -->

<!-- include: preserving-usages. path: /fragments/preserving-usages.include.md -->
## Preserving usages of the `[EnumExtensions]` attribute

Expand Down
2 changes: 2 additions & 0 deletions docs/NetEscapades.EnumGenerators.Generators.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,6 @@ This adds a `<PackageReference>` to your project. You can additionally mark the

include: interception-config

include: overload-resolution

include: preserving-usages
42 changes: 42 additions & 0 deletions docs/NetEscapades.EnumGenerators.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ To change this file edit the source file and then run MarkdownSnippets.
* [Avoiding runtime dependencies](#avoiding-runtime-dependencies)
* [Choosing the correct packages for your scenario](#choosing-the-correct-packages-for-your-scenario)
* [Enabling automatic interception (experimental)](#enabling-automatic-interception-experimental)
* [Resolving overload ambiguity with `new()`](#resolving-overload-ambiguity-with-new)
* [Preserving usages of the `[EnumExtensions]` attribute](#preserving-usages-of-the-enumextensions-attribute)<!-- endToc -->

## Why use these packages?
Expand Down Expand Up @@ -641,6 +642,47 @@ public void CantIntercept()
```
<!-- endInclude -->

<!-- include: overload-resolution. path: /fragments/overload-resolution.include.md -->
## Resolving overload ambiguity with `new()`

The generated extension methods include overloads that accept option types such as `EnumParseOptions` and `SerializationOptions`. When using target-typed `new()` with these overloads, the C# compiler may report a CS0121 ambiguity error because it cannot determine which overload to call:

```csharp
// CS0121: The call is ambiguous between 'Parse(string, StringComparison)' and 'Parse(string, EnumParseOptions)'
var result = MyEnumExtensions.Parse("First", new());
```

On **.NET 9+**, this is resolved automatically using the `[OverloadResolutionPriority]` attribute. The generator detects that the attribute type is available in the compilation and includes it in the generated code.

On **older target frameworks** (with C# 13+ language version), you can get the same behavior by providing a polyfill of the `OverloadResolutionPriorityAttribute` type. The generator will automatically detect the polyfill and include the attribute — no additional configuration is needed.

The easiest way to add the polyfill is to use the [Polyfill](https://github.com/SimonCropp/Polyfill) NuGet package, which provides this and many other missing types for older frameworks.

Alternatively, you can add the attribute manually to your project:

```csharp
namespace System.Runtime.CompilerServices
{
[AttributeUsage(
AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property,
AllowMultiple = false, Inherited = false)]
sealed class OverloadResolutionPriorityAttribute : Attribute
{
public OverloadResolutionPriorityAttribute(int priority) => Priority = priority;
public int Priority { get; }
}
}
```

If you need to disable the `[OverloadResolutionPriority]` attribute in the generated code, define the `NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY` preprocessor symbol:

```xml
<PropertyGroup>
<DefineConstants>$(DefineConstants);NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY</DefineConstants>
</PropertyGroup>
```
<!-- endInclude -->

<!-- include: preserving-usages. path: /fragments/preserving-usages.include.md -->
## Preserving usages of the `[EnumExtensions]` attribute

Expand Down
2 changes: 2 additions & 0 deletions docs/NetEscapades.EnumGenerators.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,6 @@ This adds a `<PackageReference>` to your project. You can additionally mark the

include: interception-config

include: overload-resolution

include: preserving-usages
42 changes: 42 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ To change this file edit the source file and then run MarkdownSnippets.
* [Avoiding runtime dependencies](#avoiding-runtime-dependencies)
* [Choosing the correct packages for your scenario](#choosing-the-correct-packages-for-your-scenario)
* [Enabling automatic interception (experimental)](#enabling-automatic-interception-experimental)
* [Resolving overload ambiguity with `new()`](#resolving-overload-ambiguity-with-new)
* [Preserving usages of the `[EnumExtensions]` attribute](#preserving-usages-of-the-enumextensions-attribute)<!-- endToc -->

## Why use these packages?
Expand Down Expand Up @@ -641,6 +642,47 @@ public void CantIntercept()
```
<!-- endInclude -->

<!-- include: overload-resolution. path: /fragments/overload-resolution.include.md -->
## Resolving overload ambiguity with `new()`

The generated extension methods include overloads that accept option types such as `EnumParseOptions` and `SerializationOptions`. When using target-typed `new()` with these overloads, the C# compiler may report a CS0121 ambiguity error because it cannot determine which overload to call:

```csharp
// CS0121: The call is ambiguous between 'Parse(string, StringComparison)' and 'Parse(string, EnumParseOptions)'
var result = MyEnumExtensions.Parse("First", new());
```

On **.NET 9+**, this is resolved automatically using the `[OverloadResolutionPriority]` attribute. The generator detects that the attribute type is available in the compilation and includes it in the generated code.

On **older target frameworks** (with C# 13+ language version), you can get the same behavior by providing a polyfill of the `OverloadResolutionPriorityAttribute` type. The generator will automatically detect the polyfill and include the attribute — no additional configuration is needed.

The easiest way to add the polyfill is to use the [Polyfill](https://github.com/SimonCropp/Polyfill) NuGet package, which provides this and many other missing types for older frameworks.

Alternatively, you can add the attribute manually to your project:

```csharp
namespace System.Runtime.CompilerServices
{
[AttributeUsage(
AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property,
AllowMultiple = false, Inherited = false)]
sealed class OverloadResolutionPriorityAttribute : Attribute
{
public OverloadResolutionPriorityAttribute(int priority) => Priority = priority;
public int Priority { get; }
}
}
```

If you need to disable the `[OverloadResolutionPriority]` attribute in the generated code, define the `NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY` preprocessor symbol:

```xml
<PropertyGroup>
<DefineConstants>$(DefineConstants);NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY</DefineConstants>
</PropertyGroup>
```
<!-- endInclude -->

<!-- include: preserving-usages. path: /fragments/preserving-usages.include.md -->
## Preserving usages of the `[EnumExtensions]` attribute

Expand Down
2 changes: 2 additions & 0 deletions docs/README.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,6 @@ This adds a `<PackageReference>` to your project. You can additionally mark the

include: interception-config

include: overload-resolution

include: preserving-usages
39 changes: 39 additions & 0 deletions docs/fragments/overload-resolution.include.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

## Resolving overload ambiguity with `new()`

The generated extension methods include overloads that accept option types such as `EnumParseOptions` and `SerializationOptions`. When using target-typed `new()` with these overloads, the C# compiler may report a CS0121 ambiguity error because it cannot determine which overload to call:

```csharp
// CS0121: The call is ambiguous between 'Parse(string, StringComparison)' and 'Parse(string, EnumParseOptions)'
var result = MyEnumExtensions.Parse("First", new());
```

On **.NET 9+**, this is resolved automatically using the `[OverloadResolutionPriority]` attribute. The generator detects that the attribute type is available in the compilation and includes it in the generated code.

On **older target frameworks** (with C# 13+ language version), you can get the same behavior by providing a polyfill of the `OverloadResolutionPriorityAttribute` type. The generator will automatically detect the polyfill and include the attribute — no additional configuration is needed.

The easiest way to add the polyfill is to use the [Polyfill](https://github.com/SimonCropp/Polyfill) NuGet package, which provides this and many other missing types for older frameworks.

Alternatively, you can add the attribute manually to your project:

```csharp
namespace System.Runtime.CompilerServices
{
[AttributeUsage(
AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property,
AllowMultiple = false, Inherited = false)]
sealed class OverloadResolutionPriorityAttribute : Attribute
{
public OverloadResolutionPriorityAttribute(int priority) => Priority = priority;
public int Priority { get; }
}
}
```

If you need to disable the `[OverloadResolutionPriority]` attribute in the generated code, define the `NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY` preprocessor symbol:

```xml
<PropertyGroup>
<DefineConstants>$(DefineConstants);NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY</DefineConstants>
</PropertyGroup>
```
15 changes: 11 additions & 4 deletions src/NetEscapades.EnumGenerators.Generators/EnumGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
.Select((x, _) =>
{
var wellKnownTypes = WellKnownTypes.GetOrCreate(x);
return ((x as CSharpCompilation)?.LanguageVersion,
var languageVersion = (x as CSharpCompilation)?.LanguageVersion;
var hasOverloadResolutionPriority =
languageVersion is not LanguageVersion.Preview and >= (LanguageVersion)1300 // C#13+
&& x.GetTypeByMetadataName(
"System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute") is not null;
return (languageVersion,
HasRuntimeDeps: wellKnownTypes.ExistsInCompilation(WellKnownTypeData.WellKnownType
.NetEscapades_EnumGenerators_EnumParseOptions));
.NetEscapades_EnumGenerators_EnumParseOptions),
HasOverloadResolutionPriority: hasOverloadResolutionPriority);
});

var defaults = compilationDetails.Combine(defaultConfiguration);
Expand Down Expand Up @@ -90,7 +96,7 @@ private static DefaultConfiguration GetDefaultConfigurations(AnalyzerConfigOptio

static void Execute(
in EnumToGenerate enumToGenerate,
(LanguageVersion? LanguageVersion, bool HasRuntimeDeps) compilationDetails,
(LanguageVersion? LanguageVersion, bool HasRuntimeDeps, bool HasOverloadResolutionPriority) compilationDetails,
DefaultConfiguration defaultValues,
SourceProductionContext context)
{
Expand All @@ -103,7 +109,8 @@ static void Execute(
useCollectionExpressions: useCollectionExpressions,
defaultMetadataSource: defaultValues.MetadataSource,
hasRuntimeDependencies: compilationDetails.HasRuntimeDeps,
forceInternal: forceInternal);
forceInternal: forceInternal,
hasOverloadResolutionPriority: compilationDetails.HasOverloadResolutionPriority);
context.AddSource(filename, SourceText.From(result, Encoding.UTF8));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public static (string Content, string HintName) GenerateExtensionClass(in EnumTo
bool useCollectionExpressions,
MetadataSource defaultMetadataSource,
bool hasRuntimeDependencies,
bool forceInternal)
bool forceInternal,
bool hasOverloadResolutionPriority)
{
var metadataSource = enumToGenerate.MetadataSource ?? defaultMetadataSource;
var isMetadataSourcesEnabled = metadataSource != MetadataSource.None;
Expand Down Expand Up @@ -165,6 +166,13 @@ public static string ToStringFast(this
/// <param name="value">The value to retrieve the string value for</param>
/// <param name="options">The options to use when serializing the enum</param>
/// <returns>The string representation of the value, using the provided options.</returns>
""");

AddOverloadResolutionPriority(sb, hasOverloadResolutionPriority);

sb.Append(
"""

public static string ToStringFast(this
""").Append(fullyQualifiedName).Append(
"""
Expand Down Expand Up @@ -952,6 +960,13 @@ public static
"""
" /> whose
/// value is represented by <paramref name="name"/></returns>
""");

AddOverloadResolutionPriority(sb, hasOverloadResolutionPriority);

sb.Append(
"""

public static
""").Append(fullyQualifiedName).Append(
"""
Expand Down Expand Up @@ -1146,6 +1161,13 @@ public static bool TryParse(
" />. This parameter is passed uninitialized.</param>
/// <param name="options">Options that control how the string value should be parsed.</param>
/// <returns><see langword="true"/> if the value parameter was converted successfully; otherwise, <see langword="false"/>.</returns>
""");

AddOverloadResolutionPriority(sb, hasOverloadResolutionPriority);

sb.Append(
"""

public static bool TryParse(
#if NETCOREAPP3_0_OR_GREATER
[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)]
Expand Down Expand Up @@ -1468,6 +1490,8 @@ public static

AddSystemMemoryWarning(sb, fullyQualifiedName, AlternativeMethodChoice.None, enumParseOptions);

AddOverloadResolutionPriority(sb, hasOverloadResolutionPriority);

sb.Append(
"""
public static
Expand All @@ -1489,6 +1513,13 @@ public static
"""
" /> whose
/// value is represented by <paramref name="name"/></returns>
""");

AddOverloadResolutionPriority(sb, hasOverloadResolutionPriority);

sb.Append(
"""

public static
""").Append(fullyQualifiedName).Append(
"""
Expand Down Expand Up @@ -1791,6 +1822,8 @@ public static bool TryParse(

AddSystemMemoryWarning(sb, fullyQualifiedName, AlternativeMethodChoice.None, enumParseOptions);

AddOverloadResolutionPriority(sb, hasOverloadResolutionPriority);

sb.Append(
"""
public static bool TryParse(
Expand All @@ -1817,6 +1850,13 @@ public static bool TryParse(
" />. This parameter is passed uninitialized.</param>
/// <param name="options">Options that control how the string value should be parsed.</param>
/// <returns><see langword="true"/> if the value parameter was converted successfully; otherwise, <see langword="false"/>.</returns>
""");

AddOverloadResolutionPriority(sb, hasOverloadResolutionPriority);

sb.Append(
"""

public static bool TryParse(
#endif
#if NETCOREAPP3_0_OR_GREATER
Expand Down Expand Up @@ -2195,6 +2235,7 @@ public enum SerializationTransform
""");
}


var content = sb.ToString();
sb.Clear();
var filename = sb
Expand Down Expand Up @@ -2249,6 +2290,23 @@ static void AddArrayCloser(StringBuilder sb, bool useCollectionExpressions)
}
}

static void AddOverloadResolutionPriority(StringBuilder sb, bool hasOverloadResolutionPriority)
{
if (!hasOverloadResolutionPriority)
{
return;
}

sb.Append(
"""

#if !NETESCAPADES_ENUMGENERATORS_OMIT_OVERLOAD_PRIORITY
[global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)]
#endif

""");
}

static void AddSystemMemoryWarning(StringBuilder sb, string fullyQualifiedName, AlternativeMethodChoice alternativeMethodChoice, string enumParseOptions)
{
sb.Append(
Expand Down
Loading
Loading