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

error FS1113 accessing constructor parameter with SRTP #15135

Closed
ingted opened this issue Apr 24, 2023 · 19 comments
Closed

error FS1113 accessing constructor parameter with SRTP #15135

ingted opened this issue Apr 24, 2023 · 19 comments
Labels
Bug Impact-Medium (Internal MS Team use only) Describes an issue with moderate impact on existing code. Regression
Milestone

Comments

@ingted
Copy link
Contributor

ingted commented Apr 24, 2023

Repro steps

type MyType<'T when ^T: (member o: int)> (t:^T) =
    member inline this.T = t

Expected behavior

Successfully executed.

Actual behavior

error FS1113: The value 'T' was marked inline but its implementation makes use of an internal or private function which is not sufficiently accessible

Known workarounds

None

Related information

  • Operating system: win 2019
  • .NET Runtime: .NET 7
  • fsi: 12.5.0.0 for F# 7.0
  • Editing Tools: Visual Studio
@ingted
Copy link
Contributor Author

ingted commented Apr 24, 2023

image
It was executable in F# 5.0

@ingted
Copy link
Contributor Author

ingted commented Apr 24, 2023

image
But not executable in F# 7.0

@0101
Copy link
Contributor

0101 commented Apr 24, 2023

A workaround for this may be to add a member val for the parameter from the constructor.

But we should definitely fix it.

@0101 0101 added the Impact-Medium (Internal MS Team use only) Describes an issue with moderate impact on existing code. label Apr 24, 2023
@ingted
Copy link
Contributor Author

ingted commented Jan 29, 2024

Is there any branch having this fixed? Or any progress?

@ingted
Copy link
Contributor Author

ingted commented Jan 29, 2024

Still not works in .NET 8. (FSharp.Core 8.0.100)

@vzarytovskii
Copy link
Member

Is there any branch having this fixed? Or any progress?

Nobody worked on it to my knowledge

@ingted
Copy link
Contributor Author

ingted commented Feb 17, 2024

@vzarytovskii If you could give me some hints, e.g. which part one could look into it, I would like to study it and try to fix it? (I mean it needs guidance for one who tries to change code of the compiler (stuff?!) the first time...)

@vzarytovskii
Copy link
Member

vzarytovskii commented Feb 17, 2024

@vzarytovskii If you could give me some hints, e.g. which part one could look into it, I would like to study it and try to fix it? (I mean it needs guidance for one who tries to change code of the compiler (stuff?!) the first time...)

That's not an easy part of compiler, how I would approach it is see if it changes with different language versions but same sdk (langversion flag, compile same project with different versions), if it is, I would debug in the place it throws an error and see what's different (or alternatively see which language feature causes it).

If it still doesn't work with different language versions, it's harder, will have to find what revision of sdk it was working in, check it out and again repeat - see what's different. Maybe even bisect it and see where it started breaking.

@Martin521
Copy link
Contributor

Martin521 commented Feb 18, 2024

I checked this with .net 5.0.408 and DO see the FS1113 reported by fsc. So it probably was there forever.
For fsi, it is reported only since .net 6. I don't know why it is not reported by fsi in .net 5. I assume some optimizations (and related checks) did not happen for fsi but only for fsc.

Furthermore, I suspect that the code is indeed invalid, only the error message is misleading. The right one might be the infamous FS0669 ("escape its scope"). Can some expert confirm this? It is happening here.

@ingted
Copy link
Contributor Author

ingted commented Feb 18, 2024

Why it is indeed invalid?... Logically it seems good to use.
Actually I want to wrap a type with the specified member (e.g. o:int) to have a method to work with the constructor parameter...

type MyType<'T when ^T: (member o: int)> (t:^T) =
    member this.T = t

With .net 8

> type MyType<'T when ^T: (member o: int)> (t:^T) =
    member this.T = t;;

      member this.T = t;;
  -----------^^^^^^

stdin(4,12): error FS0670: This code is not sufficiently generic. The type variable ^T when ^T: (member o: int) could not be generalized because it would escape its scope.

> 

However even in .net 5, without inline, it doesn't work... That's why I added inline in the example.

image

One of advantage of F# is using compiler to help us do type check/restriction during compilation and this kind of wrapper scinario is really useful...

@Martin521
Copy link
Contributor

Martin521 commented Feb 18, 2024

I think my guess above about the correct error message was wrong.
Because you get the error also in non-generic code like this type MyType (t: int) = member inline this.T = t.
I rather think now that for inline code all referenced values (like t) must have at least the accessibility of the function/member (T in this case). This makes sense because inlining means expanding the function/method at the call site. That would generate invalid IL as t is not accessible at the call site.

But I really don't know why your non-inlined example

type MyType<'T when 'T: (member o: int)> (t:'T) = member this.T = t

is shown as invalid, while

type MyType2<'T when 'T: comparison> (t:'T) = member this.T = t

compiles.

@Martin521
Copy link
Contributor

Martin521 commented Feb 19, 2024

The above, though, leads to a workaround for your problem:

type HasO =
    abstract member o : int
type MyType<'T when 'T :> HasO> (t: 'T) =
    member this.T = t

(At least for cases where you own the types that you want to instantiate 'T with)

@Martin521
Copy link
Contributor

I think we have established that the FS1113 error (re accessibility) in the original post is correct.
The other one that we have discussed above ("type could not be generalized") is a duplicate of #3302.

@ingted
Copy link
Contributor Author

ingted commented Feb 19, 2024

Oops... Seems like not applied to my scenario....

In .net 5, it is really powerful...
Screenshot_2024-02-19-17-58-36-77_2665fb67b16260a8d818298cef8dc107

But not in .net 8... (It's hard to make all types implemented a specific interface...)
Screenshot_2024-02-19-17-56-08-28_2665fb67b16260a8d818298cef8dc107

@ingted
Copy link
Contributor Author

ingted commented Feb 19, 2024

//working
type MyType6< ^T 
            when ^T: (member o :  int)
            > () = 
    static member inline get (t:^T) = t.o

//not working
type MyType7< ^T 
            when ^T: (member o :  int)
            > (t:^T) = 
    member inline this.get () = t.o

image

@ingted
Copy link
Contributor Author

ingted commented Feb 19, 2024

//working
type MyType6< ^T 
            when ^T: (member o :  int)
            > () = 
    static member inline get (t:^T) = t.o
    
    
//not working
type MyType6_2< ^T 
            when ^T: (member o :  int)
            > () = 
    static member val T = 123 
    static member inline get (t:^T) = t.o

image

@ingted
Copy link
Contributor Author

ingted commented Feb 19, 2024

//not working in .net 8
type MyType7< ^T 
            when ^T: (member o : int)
            > (t:^T) = 
    member inline this.get () = t.o

//working in .net 5
type MyType7_2< ^T 
            when ^T: (member public o : int)
            > (t:^T) = 
    member inline public this.get () = (^T :(member public o :  int)(t))

According to this, I would vote that FS1113 error message is valid as well...

@Martin521
Copy link
Contributor

After some further digging into my earlier question

But I really don't know why your non-inlined example type MyType<'T when 'T: (member o: int)> (t:'T) = member this.T = t is shown as invalid, while type MyType2<'T when 'T: comparison> (t:'T) = member this.T = t compiles.

I found the underlying reason. The CLR that handles generics recognizes a number of constraints, including type/interface constraints, but not explicit member constraints. The latter is a pure F# thing. This means (now I am speculating) the compiler has to express it in IL by an interface constraint, and that can be done only if MyType is a "head type", i.e. statically known at compile time. These complications are also the reason why the F# Language Reference says that explicit member constraints are "not intended for common use".

So, I think we have answered all questions now.

  • The error FS1113 in type MyType3<'T when 'T: (member o: int)> (t:'T) = member inline this.T: 'T = t is correct.
  • That this error was not reported by fsi in .net 5 was just a bug that has been fixed for .net 6
  • The error FS0670 in type MyType4<'T when 'T: (member o: int)> (t:'T) = member this.T = t is also correct.
  • This error message should be improved, but we have an issue already for this (Improve Error Reporting: "could not be generalized because it would escape its scope" #3302)
  • The fact that a comparison contraint works in the non-inlined case, but a member constraint does not work, is explained above.

So I guess this issue can be closed?

@ingted
Copy link
Contributor Author

ingted commented Feb 21, 2024

Wow!! Sounds reasonable, let me close it! I learned a lot!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Impact-Medium (Internal MS Team use only) Describes an issue with moderate impact on existing code. Regression
Projects
Archived in project
Development

No branches or pull requests

4 participants