fsharp
fsharp copied to clipboard
Improve Error Reporting: "could not be generalized because it would escape its scope"
What
This error crops up when creating SRTP members but forgetting to inline them (example here taken from this SO question): -
type MyType<'T when ^T: (static member ( + ) : ^T * ^T -> ^T)> =
member this.F a b = a + b
leads to
error FS0670: This code is not sufficiently generic. The type variable ^T when ^T : (static member ( + ) : ^T * ^T -> ^T) could not be generalized because it would escape its scope.
Why
It seems that the error could not be generalized because it would escape its scope
normally can be solved by inlining the member definition. There are a few posts / questions out there on SO etc. regarding this, all with that answer.
I know that SRTP is a somewhat advanced F# topic but nonetheless the error could be improved since there's often the same fix that can be applied. Also, the current error message could explain itself a little better - what does "it would escape its scope" mean in this context? Why would inlining fix it? etc.
How
I'm not entirely sure, but perhaps a start would be to at least offer the solution: -
error FS0670: This code is not sufficiently generic. The type variable ^T when ^T : (static member ( + ) : ^T * ^T -> ^T) could not be generalized because it would escape its scope. Consider inlining the member to fix this error e.g. "member inline this.F" ...
.
However, this still doesn't clearly explain the "why"; someone seeing this for the first (or second!) time won't know why the error is occuring, but rather than a tried-and-tested solution that makes the error go away. Other ideas include: -
- Showing an alternate way to define the member which (apparently?) gets the constraint for free:-
type MyType() =
member inline this.F a b = a + b
- Another answer that I rather like from the SO post above contains a much more : - "Member constrains need statically resolved type parameters. But statically resolved type parameters are not allowed on types, only for inline functions and inline methods.". This at least tries to address the why the error occurs, although it could still be improved upon IMHO.
Here is another case of this error message (on B<'T>
below), where also the above mentioned improvements are not helping.
type A<'T>() = class end
module AA = let CreateA<'T>() = A()
type B<'T>() = let a: A<'T> = AA.CreateA()
Edit, six years later, accidentally coming across this old post of mine. The above is actually simply fixed by adding another annotation.
type A<'T>() = class end
module AA = let CreateA<'T>() = A<'T>()
type B<'T>() = let a: A<'T> = AA.CreateA()
Same with the following function:
let draw<'T when 'T : (member Draw : unit -> unit)> (x: 'T) =
x.Draw()
or
let draw (x: 'T when 'T : (member Draw : unit -> unit)) =
x.Draw()
Bonus question/suggestion: maybe the language could be improved to not require inlining in these cases? Maybe the compiler could generate proxy functions or types that would be "inlined" under the hood to satisfy Statically Resolved Type Parameters, but allow us to use non-inlined functions as well? Inlining may not always be desirable.
Consider this example:
// Business logic processors
type FirstTypeThatCanDraw() =
member x.Draw() = printfn "I'm drawing!"
type AntherTypeThatCanDraw() =
member x.Draw() = printfn "I'm drawing, too!"
member x.Bark() = printfn "Barking!"
let o = new FirstTypeThatCanDraw()
let p = new AntherTypeThatCanDraw()
// The new features in question (not working today without "inline")
// let draw (x: 'T when 'T : (member Draw : unit -> unit)) =
// x.Draw()
// The new compiler would generate this under the hood (not inlined),
// one for each concrete argument type.
let ``draw$generated_proxy_for_FirstTypeThatCanDraw`` (x: 'T when 'T : (member Draw : unit -> unit)) =
x.Draw();
let ``draw$generated_proxy_for_AntherTypeThatCanDraw`` (x: 'T when 'T : (member Draw : unit -> unit)) =
x.Draw();
// my code would be:
// draw o
// draw p
// but under the hood it would get converted to:
``draw$generated_proxy_for_FirstTypeThatCanDraw`` o
``draw$generated_proxy_for_AntherTypeThatCanDraw`` p
The difference is that we would have one body of the function per type instead of one body per call site.
Maybe we could use an attribute on the function with constraints to hint compiler about our intent? I'm not sure.
The above code generates 2 warnings of the kind: "Warning: this construct causes code to be less generic than indicated by the type annotations. The type variable 'T has been constrained to FirstTypeThatCanDraw." but I hope compiler could handle that, too.
Thanks.