fsharp icon indicating copy to clipboard operation
fsharp copied to clipboard

Inline methods break in types with self identifier with misleading error message

Open steveisaac opened this issue 4 months ago • 2 comments

Repro steps

type Test() as _self =
    member inline this.InlineMethod() = ()

Expected behavior

The above compiles or emits a useful error message.

Actual behavior The following error is emitted The value 'InlineMethod' was marked inline but its implementation makes use of an internal or private function which is not sufficiently accessible

Related information .NET 9.0

steveisaac avatar Oct 08 '25 12:10 steveisaac

type Test() as _self =
    class end
    
type Test with
    member inline this.InlineMethod() = ()

The above functions as a workaround

steveisaac avatar Oct 08 '25 13:10 steveisaac

This is the same as https://github.com/dotnet/fsharp/issues/17899. There are a number of related issues, e.g., https://github.com/dotnet/fsharp/issues/8349, etc.

Explanation

It looks like the cause is that the self-identifier causes the compiler to emit an initialization check that uses an internal field (SharpLab):

type Test() as _self =
    member this.InlineMethod() = ()
using System;
using System.Reflection;
using Microsoft.FSharp.Core;

[assembly: FSharpInterfaceDataVersion(2, 0, 0)]
[assembly: AssemblyVersion("0.0.0.0")]

[CompilationMapping(SourceConstructFlags.Module)]
public static class @_
{
    [Serializable]
    [CompilationMapping(SourceConstructFlags.ObjectType)]
    public class Test
    {
        internal int init@1;

        public Test()
        {
            FSharpRef<Test> fSharpRef = new FSharpRef<Test>(null);
            base..ctor();
            fSharpRef.contents = this;
            init@1 = 1;
        }

        public void InlineMethod()
        {
            if (init@1 < 1)
            {
                LanguagePrimitives.IntrinsicFunctions.FailInit();
            }
        }
    }
}

namespace <StartupCode$_>
{
    internal static class $_
    {
        public static void main@()
        {
        }
    }
}

This is done to support this behavior outlined in the spec:

Object References in Primary Constructors

For types that have a primary constructor, the name of the object parameter can be bound and used in the non-static function, value, and member definitions of the type definition as follows:

type X(a:int) as x =
    let mutable currentA = a
    let mutable currentB = 0
    do x.B <- x.A + 3
    member self.GetResult()= currentA + currentB
    member self.A with get() = currentA and set v = currentA <- v
    member self.B with get() = currentB and set v = currentB <- v

During construction, no member on the type may be called before the last value or function definition in the type has completed; such a call results in an InvalidOperationException. For example, the following code raises this exception:

type C() as self =
    let f = (fun (x:C) -> x.F())
    let y = f self
    do printfn "construct"
    member this.F() = printfn "hi, y = %A" y

let r = new C() // raises InvalidOperationException

The exception is raised because an attempt may be made to access the value of the field y before initialization is complete.

Action?

At the very least, it does seem like the compiler should (and could?) emit a better error message when the internal/private value is itself compiler-generated and invisible to the end-user.

brianrourkeboll avatar Oct 08 '25 13:10 brianrourkeboll