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

Null pointer reference (NRE) when referencing a point free function (FSharpFunc?) from a console app without a main method #18259

Open
bjartwolf opened this issue Jan 22, 2025 · 5 comments

Comments

@bjartwolf
Copy link

bjartwolf commented Jan 22, 2025

Repro steps

I added a repository here to describe the issue, without xunit for clarity. This is a stranger use-case than with a test project, but it makes it clear that xunit is not involved https://github.com/bjartwolf/fsharpnull

Part of the reason for this to show now, I guess, is that console apps now have no main method by default.

Instructions similar to this below, but using xunit as that is a more typical use-case.

  1. dotnet new console -lang "F#" -o consoleapp
  2. dotnet new xunit -lang "F#" -o test
  3. cd test
  4. dotnet add reference ..\consoleapp\consoleapp.fsproj
  5. Add a point free function like
let parseLine (s:string) = s
let parseLines = Array.map parseLine

and a test like

module Tests

open System
open Xunit
open Program
[<Fact>]
let ``My test`` () =
    let foo = parseLines [|"foo"|]
    Assert.True(true)

Expected behavior

I expect it to work, or perhaps some warning that point free functions can cause issues across assemblies?

Actual behavior
Null pointer reference when invoking the function (as shown in the repository, this happens also without xunit, but the use-case where this happens is typically a console app with xunit or some other test framework. This can make it a bit more tricky to see what is going on, it is easy to blame the testrunner for the behavoir.)

Image

Known workarounds

Do not use point-free function, add a named argument to the function when exposing it outside assemblies, as pr language guidelines. This avoids the FSharpFunc
Image

or

Add a main method like so in the console app. This somehow causes something to initialize differently?

[<EntryPoint>]
let main args =
    0

or

Add the code to a classlib and reference that in the console app. Classlibs work too.

Related information
Asked for some input in the discord first https://discord.com/channels/196693847965696000/441274967607214091/1331376422555746347 and was asked to submit. Nice to have someone to discuss with before posting issues directly. Seems like from the comment that it might has been observed before (or maybe that was something else...)

@T-Gro
Copy link
Member

T-Gro commented Jan 22, 2025

The issue stems from missing module-level initialization - the function is compiled as a property, but it does not get initialized.
Why it does not get initialized? It's a combination of using the test project (any framework really, you can also simulate without it) referencing a non-library project (in your case a console app) and the function in question being in the very last file.

That does sound strange, why does placement of the file matter?

For a console project, the necessary initialization for the last file comes from invoking the main method. If no main present, contents of the last file act similar to it.

However, when a test project references your app project, it does not call main - it only accesses members as if it was a library.
And the initializer is never called, hence accessing the members leads to a NRE.

What do point-free functions have to do with it?
If the function would be written as a regular function, it will get compiled as a plain .NET method, and not a property of the FSharpFunc type - hence not needing any init to happen.

To fix this we would need to change initialization of properties for last file of a console app - to make it work even when the code is not executed, but just referenced.( without breaks or performance regressions for the dominant use case of using console apps, which should be "running them" :) )

@T-Gro
Copy link
Member

T-Gro commented Jan 22, 2025

More at page 204 and 205 of the F# spec:
https://fsharp.org/specs/language-spec/4.1/[FSharpSpec-4.1-latest.pdf](https://fsharp.org/specs/language-spec/4.1/FSharpSpec-4.1-latest.pdf)

@bjartwolf
Copy link
Author

bjartwolf commented Jan 22, 2025

Yes, I see, if it is in a module it even works, so it has to be the last file AND no main method.
This also means the possibility of stumbling into this could also be reduced by a different template for the console app in F# (Not saying/proposing that as a solution, but that was why I stumbled in it). At least in my case, I was just quickly just showing something to demonstrate how easy F# is by throwing up a console app and test project and then suddenly I got this error. It seems like something that would not happen in a "real" project, as one would organize the code differently anyway. But it sounds like something that could be possible to mitigate in tooling or templates...

@T-Gro
Copy link
Member

T-Gro commented Jan 22, 2025

That is an interesting idea.

Let's see if someone can propose a template change that would not reduce simplicity of it (it can have comments!), while making it more difficult to get into this problem.

@bjorn-einar-bjartnes-4ss
Copy link

bjorn-einar-bjartnes-4ss commented Jan 22, 2025

As I re-read the comments in the console app template, of course I found that the link points to descriptions of the implicit style of console apps, and that the explicit version does mention it is suited for unit testing the last file in a console apps...

I don't know what such a template could say, but I like the "Are your lights on?" approach which is to ask if you have checked, not telling a driver what to do (if night and then tunnel then keep lights on, if day and driving lights are on then turn lights etc...) https://www.amazon.com/Are-Your-Lights-Figure-Problem/dp/0932633161

II think there are tweaks that could make it more clear, I could propose some changes and see. I think typically the language is often positive in docs, saying that "this method is suited for unit testing the last file in the console app" and the implicit does not say that "this will have issues with for example unit testing the last file in the app, as special rules apply to the initialization of the last file in a console app with implicit entry points", which I think would make pitfalls slightly more clear.

Just stating something in a comment that is true (what style the template is in) and ask the user if this is what they want and then the docs could have more details on pros and cons... I don't know, but I am trying to think on something that would be universally useful and not targets this specific issue, and even if this would be a small change, perhaps it would be a tiny improvement.

The docs could have more on pitfalls specific for implicit entry points or weird edge cases, also mention this issue as a con.

// This is an implicit entry point, is this what you want?  Read more about console apps at https://aka.ms/fsharp-console-apps
Image

Ideally of course it would just not fail, but I think links to specific docs is universally a good idea and would be good even if the issue got fixed later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: New
Development

No branches or pull requests

3 participants