Backwards compatibility broken
The following code is compiled differently in F#9, depending on which SDK is used ( 8 vs 9, in both cases targeting net8 )
type Default3 = class end
type Default2 = class inherit Default3 end
type Default1 = class inherit Default2 end
type IsAltLeftZero =
inherit Default1
static member inline IsAltLeftZero (_: ref<'T> when 'T : struct , _mthd: Default3) = false
static member inline IsAltLeftZero (_: ref<'T> when 'T : not struct, _mthd: Default2) = false
static member inline IsAltLeftZero (t: ref<'At , _mthd: Default1) = (^At : (static member IsAltLeftZero : _ -> _) t.Value)
static member inline IsAltLeftZero (_: ref< ^t> when ^t: null and ^t: struct , _mthd: Default1) = ()
static member IsAltLeftZero (t: ref<option<_> > , _mthd: IsAltLeftZero) = Option.isSome t.Value
static member IsAltLeftZero (t: ref<voption<_> >, _mthd: IsAltLeftZero) = ValueOption.isSome t.Value
static member IsAltLeftZero (t: ref<Result<_,_>> , _mthd: IsAltLeftZero) = match t.Value with (Ok _ ) -> true | _ -> false
static member IsAltLeftZero (t: ref<Choice<_,_>> , _mthd: IsAltLeftZero) = match t.Value with (Choice1Of2 _) -> true | _ -> false
static member inline Invoke (x: 'At) : bool =
let inline call (mthd : ^M, input: ^I) =
((^M or ^I) : (static member IsAltLeftZero : _*_ -> _) (ref input), mthd)
call(Unchecked.defaultof<IsAltLeftZero>, x)
Looking at the produced type for IsAltLeftZero there are some diffs in the Invoke$W method (I guess W stands for witness)
// F#8 => Boolean Invoke$W[At](FSharpFunc`2[At,Boolean], FSharpFunc`2[FSharpRef`1[At],FSharpFunc`2[IsAltLeftZero,Boolean]], At);
// F#9 => Boolean Invoke$W[At]( FSharpFunc`2[FSharpRef`1[At],FSharpFunc`2[IsAltLeftZero,Boolean]], At);
At the end of the day, the problem is when using the produced dll, from F# 9, the following call will fail when compiled with sdk 8
> IsAltLeftZero.Invoke None ;;
IsAltLeftZero.Invoke None ;;
---------------------^^^^
stdin(4,22): error FS0001: '.Invoke' does not support the type ''a option', because the latter lacks the required (real or built-in) member 'IsAltLeftZero'
Known workarounds
The only workaround is recompile everything in F#9
Note: I this is a major breaking change. Running SRTP code from FSharpPlus breaks in so many places (See https://github.com/fsprojects/FSharpPlus/issues/613) that I will be really surprised if this affects only F#+ code.
To summarize: Lib 9, Consumer 8 -> this is the main scenario detected as being broken Lib 9, Consumer also 9 -> works Lib 8, Consumer 9 -> works
And the problem arises for consumers passing in Option<> only ?
(because my first-glance hypothesis is this affects types that UseNullAsTrueValue)
No, I think it's rather:
Lib 8, Consumer 9 -> this is the main scenario detected as being broken Lib 9, Consumer also 9 -> works Lib 9, Consumer 8 -> works (although not 100% sure about this one)
Is there any update on this? I'm under the impression that if this is not fixed in time, it will be harder as there might be more dlls compiled with the incorrect code around.
Hi @gusty , this is not fixed yet.
I tried investigating it a few times, but so far not identified an approach for fixing that. It still remains a priority item to get addressed.
@gusty :
Can I please ask for your help for correctly reproducing and verifying this?
// Picking 1.6.1. to make sure it has been built with older SDK
#r "nuget: FSharpPlus, 1.6.1"
FSharpPlus.Control.IsAltLeftZero.Invoke None
This should follow the Lib 8, Consumer 9 -> this is the main scenario detected as being broken scenario
@T-Gro I just managed to reproduce it. Steps:
-
Create a new project, an F# class lib should be enough. Name it something like ReproFailure, target .net8, after the namespace paste the code from the repro.
-
Create a global.json like this:
{
"sdk": {
"version": "8.0.0",
"rollForward": "latestFeature",
"allowPrerelease": true
},
"additionalSdks": [
"5.0.405",
"6.0.201",
"7.0.100"
]
}
-
Compile it
-
Now try this from an fsi with langversion 9
#r @"C:\Repos\ReproFailure\bin\Debug\net8.0\ReproFailure.dll"
open ReproFailure
let x = IsAltLeftZero.Invoke None
Then you'll see the error I gave you in the description of this issue. Hope this helps, otherwise please let me know.
THanks for the steps, @gusty .
I have created the repro with the mentioned global.json, dotnet --version in that folder states 8.0.412.
I then tried to reproduce the issue by loading it from fsi, trying both F# Interactive version 13.9.300.0 for F# 9.0 which comes with .NET SDK 9.0.302.
And also with the latest, F# Interactive version 14.0.100.0 for F# 10.0 from .NET 10.0.100-preview.6.25358.103.
For both of these, the code snippet runs just fine with:
> let x = IsAltLeftZero.Invoke None;;
val x: bool = false
I find it suspicious that the bug would be fixed by accident by one of the other bugfixes I did for nullness, but I cannot rule it out.
Please
For your langversion=9 repro, can you please let me know your exact dotnet --version ? I will then try with the same one.
Bug
I definitely believe there is an issue with importing older assemblies, I do have a hypothesis related to "null ambivalent handling" and a fix prepared - but it bothers my that I am unable to repro the original issue on my end ;; to verify the fix addresses the right thing.
I have created the repro with the mentioned global.json, dotnet --version in that folder states 8.0.412.
I have 8.0.303
Then on the interactive (with langversion=9) I have:
Microsoft (R) F# Interactive version 12.9.101.0 for F# 9.0
I can send you the dll if you want.
Also, you can have a look at your compiled dll and see if looks like the F#8 I reported in the original message, or the F#9 one. That will give you a hint about the problem trying to get the same repro and will point to the dll generation or the dll reading, being different that what I have.
F# Interactive version 12.9.101.0 for F# 9.0 has not been updated, is it possible this is a very early .NET 9 SDK (or Visual Studio from that time) which did not receive any further updates?
If you have that (v8 created) .dll at hand and could send it to me, I could verify it at latest available SDK as well as against the code currently in main.
The .dll created by my 8.0.412 version for the repro also has different .dll, so I would prefer if I could have your .dll exactly.
public static bool Invoke$W<At>(FSharpFunc<FSharpRef<At>, FSharpFunc<IsAltLeftZero, bool>> isAltLeftZero, At x)
{
object call = $Library.call@23-2.@_instance;
Tuple<IsAltLeftZero, At> tuple = new Tuple<IsAltLeftZero, At>(null, x);
IsAltLeftZero item = tuple.Item1;
At item2 = tuple.Item2;
return FSharpFunc<FSharpRef<At>, simple_repro.IsAltLeftZero>.InvokeFast(isAltLeftZero, Operators.Ref(item2), item);
}
Or, if there is a specific version of F#+ that already has this problem, then I could #r "nuget: that version - I could then make it part of our regular test suite to keep it there, to verify behavior of new code against older F#+ (unlike a .dll which I cannot keep around in the repo)
Please send me your email address by slack ( I just messaged you ) so I can send you the dll.
Regarding F#+ I think all versions have this problem, as we didn't move from .net 8 at the moment. This was discovered with the latest version, which is 1.7.0 which was built using Github's CI.
I just bumped a giraffe project with lots of F#+ from TF net8.0 to TF net10.0 and saw lots of compiler errors around Reader.map and Seq.traverse pipes composed in functions without type annotations. Warnings are present with both F#+ 1.6.1 and 1.7.0. Something like below, but hundreds of these:
A unique overload for method 'Delay' could not be determined based on type information prior to this program point. A type annotation may be needed.
Known return type: Reader<HttpContext,NestedElements<RoomViewModel,ParallelElements<XmlNode>>>
Known type parameters: < Control.Delay , (unit -> Reader<HttpContext,NestedElements<RoomViewModel,ParallelElements<XmlNode>>>) , Control.Delay >
Candidates:
- static member Control.Delay.Delay: _mthd: Internals.Default1 * (unit -> ^t) * 'a1 -> unit when ^t: null and ^t: struct
- static member Control.Delay.Delay: _mthd: Internals.Default1 * x: (unit -> ^I) * Control.Delay -> ^I when ^I: (static member Delay: (unit -> ^I) -> ^I)
- static member Control.Delay.Delay: _mthd: Internals.Default3 * x: (unit -> ^Monad<'T>) * Internals.Default1 -> ^Monad<'T> when (Control.Bind or ^a1 or ^Monad<'T>) : (static member (>>=) : ^a1 * (unit -> ^Monad<'T>) -> ^Monad<'T>) and (Control.Return or ^a1) : (static member Return: ^a1 * Control.Return -> (unit -> ^a1))
A unique overload for method '<*>' could not be determined based on type information prior to this program point. A type annotation may be needed.
Known return type: FSharpPlus.Data.Reader<System.IServiceProvider,Giraffe.ViewEngine.HtmlElements.XmlNode Microsoft.FSharp.Collections.list>
Known type parameters: < struct (FSharpPlus.Data.Reader<System.IServiceProvider,(Giraffe.ViewEngine.HtmlElements.XmlNode Microsoft.FSharp.Collections.list -> Giraffe.ViewEngine.HtmlElements.XmlNode Microsoft.FSharp.Collections.list)> * FSharpPlus.Data.Reader<System.IServiceProvider,Giraffe.ViewEngine.HtmlElements.XmlNode Microsoft.FSharp.Collections.list>) , FSharpPlus.Data.Reader<System.IServiceProvider,Giraffe.ViewEngine.HtmlElements.XmlNode Microsoft.FSharp.Collections.list> , FSharpPlus.Control.Apply >
Candidates:
- static member FSharpPlus.Control.Apply.``<*>`` : struct (^Applicative<'T->'U> * ^Applicative<'T>) * _output: ^Applicative<'U> * [<System.Runtime.InteropServices.Optional>] _mthd: FSharpPlus.Internals.Default1 -> ^Applicative<'U> when (^Applicative<'T->'U> or ^Applicative<'T> or ^Applicative<'U>) : (static member (<*>) : ^Applicative<'T->'U> * ^Applicative<'T> -> ^Applicative<'U>)
- static member FSharpPlus.Control.Apply.``<*>`` : struct (^Monad<'T->'U> * ^Monad<'T>) * _output: ^Monad<'U> * [<System.Runtime.InteropServices.Optional>] _mthd: FSharpPlus.Internals.Default2 -> ^Monad<'U> when (^Monad<'T->'U> or ^Monad<'U>) : (static member (>>=) : ^Monad<'T->'U> * (('T -> 'U) -> ^Monad<'U>) -> ^Monad<'U>) and (^Monad<'T> or ^Monad<'U>) : (static member (>>=) : ^Monad<'T> * ('T -> ^Monad<'U>) -> ^Monad<'U>) and ^Monad<'U> : (static member Return: 'U -> ^Monad<'U>)
- static member FSharpPlus.Control.Apply.``<*>`` : struct (^t * ^u) * _output: ^r * _mthd: FSharpPlus.Internals.Default1 -> ('a3 -> 'a3) when ^t: null and ^t: struct and ^u: null and ^u: struct and ^r: null and ^r: struct
dotnet --info
.NET SDK:
Version: 10.0.100-preview.7.25380.108
Commit: 30000d883e
Workload version: 10.0.100-manifests.613fefda
MSBuild version: 17.15.0-preview-25380-108+30000d883
FSharp.Core is pinned to 10.0.100-preview7.25380.108
It seems relevant to me eye, but please let me know if I'm posting to a wrong thread.
At first sight it seems related to this bug report. Is there a way you could create a min repro?
I tried with a simple fsx, but couldn't find a reproduction case yet. Will try again at another time.
Thanks, it would help for sure.
An .fsx with a #r "nuget:.." reference to F#+ would work great, as it is something we can also keep around as a regression test.
Indeed, the static member FSharpPlus.Control.Apply.``<*>`` : struct (^t * ^u) * _output: ^r * _mthd: FSharpPlus.Internals.Default1 -> ('a3 -> 'a3) when ^t: null and ^t: struct and ^u: null and ^u: struct and ^r: null and ^r: struct part seems related.