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

Config binder source-gen: .NET 7 app fails to compile with .NET 8 preview 4 package #86348

Closed
andrewlock opened this issue May 16, 2023 · 10 comments
Assignees
Labels
area-Extensions-Configuration bug source-generator Indicates an issue with a source generator feature
Milestone

Comments

@andrewlock
Copy link
Contributor

Description

I was testing out the new config source generator. In .NET Preview 3, I can add the NuGet package to a .NET 7 app, and the app can build and use the source generated code as expected.

With the preview 4 version of the generator I get a compiler error:

error CS8030: Anonymous function converted to a void returning delegate cannot return a value

Reproduction Steps

Program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<TestOptions>(builder.Configuration);

csproj

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <Nullable>enable</Nullable>
    <!--    👇 Note that this is required, otherwise it can't find Dictionary<> values-->
    <!--    👇 Should be fixed too 😉-->
    <ImplicitUsings>enable</ImplicitUsings> 
    <LangVersion>latest</LangVersion>
    <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0-preview.4.*" />
  </ItemGroup>
</Project>

Building gives the following error:

CS8030: Anonymous function converted to a void returning delegate cannot return a value

The generated code looks like this (error location highlighted):

// <auto-generated/>
#nullable enable

internal static class GeneratedConfigurationBinder
{
    public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<T>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::Microsoft.Extensions.Configuration.IConfiguration configuration)
    {
        if (configuration is null)
        {
            throw new global::System.ArgumentNullException(nameof(configuration));
        }

        if (typeof(T) == typeof(global::TestOptions))
        {
            return services.Configure<global::TestOptions>(obj =>
            {
                if (!global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.HasValueOrChildren(configuration))
                {
                    // 👇 Anonymous function converted to a void returning delegate cannot return a value 
                    return default;
                }

                global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration, ref obj);
            });
        }

        throw new global::System.NotSupportedException($"Unable to bind to type '{typeof(T)}': 'Generator parser did not detect the type as input'");
    }
}

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
    using System;
    using System.Globalization;
    using Microsoft.Extensions.Configuration;

    internal static class Helpers
    {
        public static void BindCore(IConfiguration configuration, ref TestOptions obj)
        {
            if (obj is null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            if (configuration["String"] is string stringValue1)
            {
                obj.String = stringValue1;
            }
        }

        public static bool HasValueOrChildren(IConfiguration configuration)
        {
            if ((configuration as IConfigurationSection)?.Value is not null)
            {
                return true;
            }
            return HasChildren(configuration);
        }

        public static bool HasChildren(IConfiguration configuration)
        {
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                return true;
            }
            return false;
        }
    }
}

Expected behavior

No build errors. With preview 3 there are no build errors, because it's not returning a value. This is the generated code using the preview 3 package:

// <auto-generated/>
#nullable enable

using System.Linq;

internal static class GeneratedConfigurationBinder
{
    public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<T>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::Microsoft.Extensions.Configuration.IConfiguration configuration)
    {
        if (typeof(T) == typeof(TestOptions))
        {
            return services.Configure<TestOptions>(obj =>
            {
                BindCore(configuration, ref obj);
            });
        }

        throw new global::System.NotSupportedException($"Unable to bind to type '{typeof(T)}': 'Generator parser did not detect the type as input'");
    }

    private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref TestOptions obj)
    {
        if (obj is null)
        {
            throw new global::System.ArgumentNullException(nameof(obj));
        }

        if (configuration["String"] is string stringValue1)
        {
            obj.String = stringValue1;
        }

    }

    public static bool HasChildren(global::Microsoft.Extensions.Configuration.IConfiguration configuration)
    {
        foreach (global::Microsoft.Extensions.Configuration.IConfigurationSection section in configuration.GetChildren())
        {
            return true;
        }
        return false;
    }
}
``

### Actual behavior

error CS8030: Anonymous function converted to a void returning delegate cannot return a value


### Regression?

Yes, this worked in the .NET 8 preview 3 Microsoft.Extensions.Configuration.Binder package (tested with .NET 7)

### Known Workarounds

_No response_

### Configuration

.NET SDK:
Version: 7.0.202
Commit: 6c74320bc3

Runtime Environment:
OS Name: Windows
OS Version: 10.0.19044
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\7.0.202\

Host:
Version: 7.0.5
Architecture: x64
Commit: 8042d61


Not tested on anything else yet

### Other information

_No response_
@ghost ghost added the untriaged New issue has not been triaged by the area owner label May 16, 2023
@andrewlock andrewlock changed the title Config binder source-gen: .7 app fails to compile with .NET 8 preview 4 package Config binder source-gen: .NET 7 app fails to compile with .NET 8 preview 4 package May 16, 2023
@ghost
Copy link

ghost commented May 16, 2023

Tagging subscribers to this area: @dotnet/area-extensions-configuration
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

I was testing out the new config source generator. In .NET Preview 3, I can add the NuGet package to a .NET 7 app, and the app can build and use the source generated code as expected.

With the preview 4 version of the generator I get a compiler error:

error CS8030: Anonymous function converted to a void returning delegate cannot return a value

Reproduction Steps

Program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<TestOptions>(builder.Configuration);

csproj

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <Nullable>enable</Nullable>
    <!--    👇 Note that this is required, otherwise it can't find Dictionary<> values-->
    <!--    👇 Should be fixed too 😉-->
    <ImplicitUsings>enable</ImplicitUsings> 
    <LangVersion>latest</LangVersion>
    <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0-preview.4.*" />
  </ItemGroup>
</Project>

Building gives the following error:

CS8030: Anonymous function converted to a void returning delegate cannot return a value

The generated code looks like this (error location highlighted):

// <auto-generated/>
#nullable enable

internal static class GeneratedConfigurationBinder
{
    public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<T>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::Microsoft.Extensions.Configuration.IConfiguration configuration)
    {
        if (configuration is null)
        {
            throw new global::System.ArgumentNullException(nameof(configuration));
        }

        if (typeof(T) == typeof(global::TestOptions))
        {
            return services.Configure<global::TestOptions>(obj =>
            {
                if (!global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.HasValueOrChildren(configuration))
                {
                    // 👇 Anonymous function converted to a void returning delegate cannot return a value 
                    return default;
                }

                global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration, ref obj);
            });
        }

        throw new global::System.NotSupportedException($"Unable to bind to type '{typeof(T)}': 'Generator parser did not detect the type as input'");
    }
}

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
    using System;
    using System.Globalization;
    using Microsoft.Extensions.Configuration;

    internal static class Helpers
    {
        public static void BindCore(IConfiguration configuration, ref TestOptions obj)
        {
            if (obj is null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            if (configuration["String"] is string stringValue1)
            {
                obj.String = stringValue1;
            }
        }

        public static bool HasValueOrChildren(IConfiguration configuration)
        {
            if ((configuration as IConfigurationSection)?.Value is not null)
            {
                return true;
            }
            return HasChildren(configuration);
        }

        public static bool HasChildren(IConfiguration configuration)
        {
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                return true;
            }
            return false;
        }
    }
}

Expected behavior

No build errors. With preview 3 there are no build errors, because it's not returning a value. This is the generated code using the preview 3 package:

// <auto-generated/>
#nullable enable

using System.Linq;

internal static class GeneratedConfigurationBinder
{
    public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<T>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::Microsoft.Extensions.Configuration.IConfiguration configuration)
    {
        if (typeof(T) == typeof(TestOptions))
        {
            return services.Configure<TestOptions>(obj =>
            {
                BindCore(configuration, ref obj);
            });
        }

        throw new global::System.NotSupportedException($"Unable to bind to type '{typeof(T)}': 'Generator parser did not detect the type as input'");
    }

    private static void BindCore(global::Microsoft.Extensions.Configuration.IConfiguration configuration, ref TestOptions obj)
    {
        if (obj is null)
        {
            throw new global::System.ArgumentNullException(nameof(obj));
        }

        if (configuration["String"] is string stringValue1)
        {
            obj.String = stringValue1;
        }

    }

    public static bool HasChildren(global::Microsoft.Extensions.Configuration.IConfiguration configuration)
    {
        foreach (global::Microsoft.Extensions.Configuration.IConfigurationSection section in configuration.GetChildren())
        {
            return true;
        }
        return false;
    }
}
``

### Actual behavior

error CS8030: Anonymous function converted to a void returning delegate cannot return a value


### Regression?

Yes, this worked in the .NET 8 preview 3 Microsoft.Extensions.Configuration.Binder package (tested with .NET 7)

### Known Workarounds

_No response_

### Configuration

.NET SDK:
Version: 7.0.202
Commit: 6c74320bc3

Runtime Environment:
OS Name: Windows
OS Version: 10.0.19044
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\7.0.202\

Host:
Version: 7.0.5
Architecture: x64
Commit: 8042d61


Not tested on anything else yet

### Other information

_No response_

<table>
  <tr>
    <th align="left">Author:</th>
    <td>andrewlock</td>
  </tr>
  <tr>
    <th align="left">Assignees:</th>
    <td>-</td>
  </tr>
  <tr>
    <th align="left">Labels:</th>
    <td>

`untriaged`, `area-Extensions-Configuration`

</td>
  </tr>
  <tr>
    <th align="left">Milestone:</th>
    <td>-</td>
  </tr>
</table>
</details>

@andrewlock
Copy link
Contributor Author

FYI I've just tested by upgrading the app to .NET 8, using the .NET 8 preview 4, and I get the same error

.NET SDK:
 Version:   8.0.100-preview.4.23260.5
 Commit:    2268e7b15c

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.19044
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\8.0.100-preview.4.23260.5\

.NET workloads installed:
There are no installed workloads to display.

Host:
  Version:      8.0.0-preview.4.23259.5
  Architecture: x64
  Commit:       84a3d0e37e

@tarekgh
Copy link
Member

tarekgh commented May 16, 2023

Thanks @andrewlock for the report. we'll look at that.

@tarekgh tarekgh added this to the 8.0.0 milestone May 16, 2023
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label May 16, 2023
@tarekgh tarekgh added the source-generator Indicates an issue with a source generator feature label May 16, 2023
@layomia layomia added the bug label May 17, 2023
@layomia
Copy link
Contributor

layomia commented May 17, 2023

Thanks for the report. The compilation issue with the Configure method was tangentially fixed for preview 5 in #85843. The implementation will be bolstered and further tested as part of #83600.

I'll take a look at the issue with implicit usings.

@tarekgh
Copy link
Member

tarekgh commented May 17, 2023

@layomia I assume we have a test covering this case now. Right?

@layomia
Copy link
Contributor

layomia commented May 17, 2023

Yes we have a regression test.

public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<T>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::Microsoft.Extensions.Configuration.IConfiguration configuration)
{
if (configuration is null)
{
throw new global::System.ArgumentNullException(nameof(configuration));
}
if (typeof(T) == typeof(global::Program.MyClass))
{
return services.Configure<global::Program.MyClass>(obj =>
{
if (!global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.HasValueOrChildren(configuration))
{
return;
}
global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration, ref obj, binderOptions);
});
}
throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
}

@Tornhoof
Copy link
Contributor

In my experience with SrcGen tests, I highly recommend disabling Implicit usings in the test projects, you don't gain much and it produces errors which do not show up in the CI pipeline.

@andrewlock
Copy link
Contributor Author

FYI, I have just installed .NET preview 5, and the source generator still appears to be broken. For example:

I've highlighted error locations.

Program.cs

var builder = WebApplication.CreateBuilder(args);

IConfigurationSection section = builder.Configuration.GetSection("AllOptions");
builder.Services.Configure<BindableOptions>(section);
builder.Services.Configure<UnbindableOptions>(section);

var settings = new BindableOptions();
section.Bind(settings);
builder.Services.AddSingleton(settings);

Generated Code:

// <auto-generated/>
#nullable enable

internal static class GeneratedConfigurationBinder
{
    public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<T>(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::Microsoft.Extensions.Configuration.IConfiguration configuration)
    {
        if (configuration is null)
        {
            throw new global::System.ArgumentNullException(nameof(configuration));
        }

        if (typeof(T) == typeof(global::BindableOptions))
        {
            // 👇 Argument type 'lambda expression' is not assignable to parameter type 'Microsoft.Extensions.Configuration.IConfiguration'
            return services.Configure<global::BindableOptions>(obj =>
            {
                if (!global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.HasValueOrChildren(configuration))
                {
                    return;
                }

                // 👇 binderOptions variable does not exist
                global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration, ref obj, binderOptions);
            });
        }

        // 👇 Variable 'type' does not exist
        throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input.");
    }

    public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::BindableOptions obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration, ref obj, binderOptions: null);
}

@layomia
Copy link
Contributor

layomia commented Jun 13, 2023

Thanks. I'm going to open a PR for #83600 shortly which will comprehensively fix these compilation issues.

@layomia
Copy link
Contributor

layomia commented Jun 20, 2023

Fixed in #86348.

@layomia layomia closed this as completed Jun 20, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Jul 21, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-Extensions-Configuration bug source-generator Indicates an issue with a source generator feature
Projects
None yet
Development

No branches or pull requests

4 participants