fsharp icon indicating copy to clipboard operation
fsharp copied to clipboard

Nullness issue - `match` control flow for reference types

Open Smaug123 opened this issue 8 months ago • 4 comments

Issue description

let bar =
    let getEnvironmentVariable : string -> _ = failwith ""

    match "ENVVAR" |> getEnvironmentVariable with
    | null ->
        failwith ""
    | x ->
        x // inferred incorrectly as `obj | null` rather than `obj`

We've just pattern-matched, so it's locally provable that x is not null.

It's not explicitly stated in the RFC that this flow analysis is banned - the examples all use the new active pattern Null - but it's a big annoyance; I don't think it's possible for me to write polyglot F#-with-nullability and F#-before-v9 without this feature and without using unsafe casts? Is there a way to inject System.GetEnvironmentVariable as an argument to a function, while also satisfying the nullability checker, without breaking backward-compatibility and without calling unbox<string> (s : string | null) or similar?

My context is that I'm writing a source generator, so I must produce source which is compatible with any F# version (or else duplicate large amounts of code by providing two different generators). (https://github.com/Smaug123/WoofWare.Myriad/issues/364)

Choose one or more from the following categories of impact

  • [x] Unexpected nullness warning (false positive in nullness checking, code uses --checknulls and langversion:preview).
  • [ ] Missing nullness warning in a case which can produce nulls (false negative, code uses --checknulls and langversion:preview).
  • [ ] Breaking change related to older null constructs in code not using the checknulls switch.
  • [ ] Breaking change related to generic code and explicit type constraints (null, not null).
  • [ ] Type inference issue (i.e. code worked without type annotations before, and applying the --checknulls enforces type annotations).
  • [ ] C#/F# interop issue related to nullness metadata.
  • [ ] Other (none of the categories above apply).

Operating System

macOS

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

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

.NET Runtime/SDK version

9.0.203

Reproducible code snippet and actual behavior

No response

Possible workarounds

Use |> unbox<string>.

Smaug123 avatar Apr 20 '25 17:04 Smaug123

Just specify _?

charlesroddie avatar Apr 25 '25 14:04 charlesroddie

Sorry, I don't understand where. Is there a way to get x of type obj, and not obj or null?

Smaug123 avatar Apr 25 '25 15:04 Smaug123

I just tested it on latest preview. Without having any further clue, the code is fully generic with 'a having the null constraint.

As soon as type is inferred from elsewhere or specified (e.g. by replacing _ with objnull or by replacing the pattern matched null to (null:objnull)), I am getting correct flow-analyzed elimination of null and x becomes System.Object.

Image

T-Gro avatar Apr 25 '25 17:04 T-Gro

If I use the real System.Environment.GetEnvironmentVariable, x also becomes full String instead of string|null after the null handling case.

T-Gro avatar Apr 25 '25 17:04 T-Gro