Skip to content

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

Closed

Description

@andrewlock

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_

Activity

  1. ghost added
    untriagedNew issue has not been triaged by the area owner
    on May 16, 2023
  2. 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[/+] on May 16, 2023
  3. andrewlock commented on May 16, 2023

    @andrewlock
    ContributorAuthor

    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
    
  4. tarekgh commented on May 16, 2023

    @tarekgh
    Member

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

  5. added this to the 8.0.0 milestone on May 16, 2023
  6. ghost removed
    untriagedNew issue has not been triaged by the area owner
    on May 16, 2023
  7. layomia commented on May 17, 2023

    @layomia
    Contributor

    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.

  8. tarekgh commented on May 17, 2023

    @tarekgh
    Member

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

  9. layomia commented on May 17, 2023

    @layomia
    Contributor

    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.");
    }

  10. Tornhoof commented on May 17, 2023

    @Tornhoof
    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.

  11. andrewlock commented on Jun 13, 2023

    @andrewlock
    ContributorAuthor

    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);
    }
  12. layomia commented on Jun 13, 2023

    @layomia
    Contributor

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

  13. layomia commented on Jun 20, 2023

    @layomia
    Contributor

    Fixed in #86348.

  14. ghost locked as resolved and limited conversation to collaborators on Jul 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions