dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.93k stars 787 forks source link

Nullness issue - compilation broken by FS0001 when enabling nullness checks #18034

Open johnnyggalt opened 4 days ago

johnnyggalt commented 4 days ago

Issue description

A specific scenario is broken by enabling nullness checking, manifesting as FS0001.

Choose one or more from the following categories of impact

Operating System

Windows (Default)

What .NET runtime/SDK kind are you seeing the issue on

.NET SDK (.NET Core, .NET 5+)

.NET Runtime/SDK version

.NET 9

Reproducible code snippet and actual behavior

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="Program.fs" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="FSharpPlus" Version="1.6.1" />
  </ItemGroup>

</Project>
open FSharpPlus

let parseInt () : Result<int, string> =
    Ok 42

type SomeRecordWrapper = {
    Value: int
}

let repro () =
    let result =
        monad {
            let! parsed = parseInt ()
            return {
                Value = parsed
            }
        }

    match result with
    | Ok _ ->
        ()
    | Error _ ->
        ()

With Nullable enabled, this results in:

0>Program.fs(20,7): Error FS0001 : '.Delay' does not support the type 'Result<SomeRecordWrapper,'a>', because the latter lacks the required (real or built-in) member 'Delay'
0>Program.fs(19,11): Warning FS0025 : Incomplete pattern matches on this expression.

With Nullable disabled, the code compiles fine.

Possible workarounds

None that I know of.

T-Gro commented 4 days ago

I am confused about the source line of the error, like 20 is the " | Ok " match clause, isn't it?

johnnyggalt commented 4 days ago

@T-Gro that's correct. It seems to me that the combination of null checking and the specifics of my scenario is causing the compiler to get confused.

T-Gro commented 4 days ago

Some previous defaults of type inference, such as falling back to 'obj' in some cases, are no longer possible with nullness due to the difference between obj and objnull. This should be validate with trying similar code with and without explicit annotations for the usages of the Result.

Looking at the code, it should be able to derive type of Result to be <int,string> (non nullable string) .

johnnyggalt commented 1 day ago

@T-Gro if I explicitly type the result, it does compile:

    let result: Result<SomeRecordWrapper, string> =
        ...

Whilst this should get me past my issue, it feels off to me. At the very least, the compiler error message is hella confusing. But on top of that, if I don't explicitly type result it still correctly inferred, so the explicit typing feels unnecessary from that perspective:

Image