Fsharp: Lookup on object of Action<T> is broken

Created on 30 Nov 2018  路  6Comments  路  Source: dotnet/fsharp

Somehow compiler cannot get member of successfully inferred type:
image

Repro steps

Provide the steps required to reproduce the problem

  1. Create project file:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="Program.fs" />
    <PackageReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>
</Project>
  1. Create Program.fs
module WebApplication2

open Microsoft.AspNetCore
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.DependencyInjection

type Foo() = 
    member val Bar = "" with get, set

type Startup private () =
    member __.ConfigureServices(services: IServiceCollection) =

        services.Configure<Foo>(fun foo -> printfn "%s" foo.Bar) 
        |> ignore

[<EntryPoint>]
let main args =
    WebHost
        .CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .Build()
        .Run()
    0
  1. dotnet build

These two files in attach:
WebApplication2.zip

Expected behavior

It should compile

Actual behavior

Compiler actually inferred foo type in lambda correctly, but fails to access type properties

Known workarounds

  1. Add type annotation
  2. Explicitly wrap lambda in Action ctor

Related information

Provide any related information

  • Win 10
  • .NET Core 2.1.500
  • Visual Studio 15.9.3
Resolution-By Design

All 6 comments

Inference with class members never works (though arguably it could work perhaps on sealed classes, but to the best of my knowledge that has not been implemented). If you change your poco to a record or DU, type inference will work correctly.

@abelbraaksma If I just create C# library with this method
photo_2018-11-30_15-06-59
and call it like this (with same class definition)
photo_2018-11-30_17-25-56

It works OK. So there is something else beside being Foo a class

I spoke too soon, you're right. Though I'm unsure whether both examples are fully interchangeable, and it's a common nuisance I have that with class members type inference often fails. But with the syntax you use, it ought to work correctly, I agree.

Definitely feels like a bug in the compiler if it works for a generic action you define yourself.

A workaround is this:

    member this.ConfigureServices(services: IServiceCollection) =
        services.Configure<Foo>(configureOptions = fun foo -> printfn "%s" foo.Bar)
        |> ignore

Annotating the parameter with configureOptions seems to resolve it.

I believe this might have something to do with overloading, because I can reproduce it when I add an overload method:

type Foo() = 
    member val Bar = "" with get, set

type TestClass() =

    member __.Test<'T when 'T : not struct>(f: System.Action<'T>) = ()
    member __.Test<'T when 'T : not struct>(name: string) = ()

    member this.Do() =
        this.Test<Foo>(fun foo -> printfn "%s" foo.Bar) // same error
        this.Test<Foo>(f = fun foo -> printfn "%s" foo.Bar) // works

Removing the overload, everything works.

type Foo() = 
    member val Bar = "" with get, set

type TestClass() =

    member __.Test<'T when 'T : not struct>(f: System.Action<'T>) = ()

    member this.Do() =
        this.Test<Foo>(fun foo -> printfn "%s" foo.Bar) // works
        this.Test<Foo>(f = fun foo -> printfn "%s" foo.Bar) // works

Feels like a bug to me. If it's by design then we should probably improve the behavior.

It's by design. For overloaded methods, the type is propagated into lambdas if all method overloads accept a lambda. However in this case the overloads take string and Action, and the type is not pre-inferred. Just add the type annotation.

Was this page helpful?
0 / 5 - 0 ratings